/[smecontribs]/rpms/smeserver-mailstats/contribs9/smeserver-mailstats-1.1.bz10858_10327.cleanup_and_email_truncate.patch
ViewVC logotype

Annotation of /rpms/smeserver-mailstats/contribs9/smeserver-mailstats-1.1.bz10858_10327.cleanup_and_email_truncate.patch

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.1 - (hide annotations) (download)
Tue Jan 7 10:12:12 2020 UTC (4 years, 10 months ago) by brianr
Branch: MAIN
CVS Tags: smeserver-mailstats-1_1-12_el6_sme, smeserver-mailstats-1_1-11_el6_sme, HEAD
*** empty log message ***

1 brianr 1.1 diff -urN smeserver-mailstats-1.1.old/root/etc/cron.d/mailstats.cron smeserver-mailstats-1.1/root/etc/cron.d/mailstats.cron
2     --- smeserver-mailstats-1.1.old/root/etc/cron.d/mailstats.cron 2020-01-03 09:09:24.548263432 +0000
3     +++ smeserver-mailstats-1.1/root/etc/cron.d/mailstats.cron 2020-01-03 12:35:36.668239291 +0000
4     @@ -1,2 +1,2 @@
5     -0 0 * * * root sleep $[ $RANDOM \% 3600 ]; perl /usr/bin/spamfilter-stats-7.pl /var/log/qpsmtpd/\@* /var/log/qpsmtpd/current /var/log/sqpsmtpd/\@* /var/log/sqpsmtpd/current
6     +0 0 * * * root sleep $[ $RANDOM \% 3600 ]; perl /usr/bin/mailstats.pl /var/log/qpsmtpd/\@* /var/log/qpsmtpd/current /var/log/sqpsmtpd/\@* /var/log/sqpsmtpd/current
7    
8     diff -urN smeserver-mailstats-1.1.old/root/usr/bin/mailstats.pl smeserver-mailstats-1.1/root/usr/bin/mailstats.pl
9     --- smeserver-mailstats-1.1.old/root/usr/bin/mailstats.pl 1970-01-01 01:00:00.000000000 +0100
10     +++ smeserver-mailstats-1.1/root/usr/bin/mailstats.pl 2020-01-03 12:26:47.043199450 +0000
11     @@ -0,0 +1,1896 @@
12     +#!/usr/bin/perl -w
13     +
14     +#############################################################################
15     +#
16     +# This script provides daily SpamFilter statistics.
17     +#
18     +# This script was originally developed
19     +# by Jesper Knudsen at http://sme.swerts-knudsen.dk
20     +# and re-written by brian read at bjsystems.co.uk (with some help from the community - thanks guys)
21     +#
22     +# bjr - 02sept12 - Add in qpsmtpd failure code auth::auth_cvm_unix_local as per Bug 7089
23     +# bjr - 10Jun15 - Sort out multiple files as input parameters as per bug 5613
24     +# - Sort out geoip failure status as per Bug 4262
25     +# - change final message about the DB (it is created automatically these days by the rpm)
26     +# bjr - 17Jun15 - Add annotation showing Badcountries being eliminated
27     +# - correct Spamfilter details extract, as per Bug 8656
28     +# - Add analysis table of Geoip results
29     +# bjr - 19Jun15 - Add totals for the League tables
30     +# bjr and Unnilennium - 08Apr16 - Add in else for unrecognised plugin detection
31     +# bjr - 08Apr16 - Add in link for SaneSecurity "extra" virus detection
32     +# bjr - 14Jun16 - make compatible with qpsmtpd 0.96
33     +# bjr - 16Jun16 - Add code to create an html equivalent of the text email (v0.7)
34     +# bjr - 04Aug16 - Add code to log and count the blacklist RBL urls that have triggered, this (NFR) is Bugzilla 9717
35     +# bjr - 04Aug16 - Add code to expand the junkmail table to include daily ham and spam and deleted spam for each user - (NFR bugzilla 9716)
36     +# bjr - 05Aug16 - Add code to log remote relay incoming emails
37     +# bjr - 10Oct16 - Add code to show stats for the smeoptimizer package
38     +# bjr - 16dec16 - Fix dnsbl code to deal with psbl.surriel.com - Bug 9717
39     +# bjr - 16Dec16 - Change geopip table code to show even if no exclusions found (assuming geoip data found) - Bug 9888
40     +# bjr - 30Apr17 - Change Categ index code - Bug 9888 again
41     +# bjr - 18Dec19 - Sort out a few format problems and also remove some debugging crud - Bug 10858
42     +# bjr - 18Dec19 - change to fix truncation of email address in by email table - bug 10327
43     +#
44     +#############################################################################
45     +#
46     +# SMEServer DB usage
47     +# ------------------
48     +#
49     +# mailstats / Status ("enabled"|"disabled")
50     +# / <column header> ("yes"|"no"|"auto") - enable, supress or only show if nonzero
51     +# / QpsmtpdCodes ("enabled"|"disabled")
52     +# / SARules ("enabled"|"disabled")
53     +# / GeoipTable ("enabled"|"disabled")
54     +# / GeoipCutoffPercent (0.5%) - threshold to show Geoip country in league table
55     +# / JunkMailList ("enabled"|"disabled")
56     +# / SARulePercentThreshold (0.5) - threshold of SArules percentage for report cutoff
57     +# / Email (admin) - email to send report
58     +# / SaveDataToMySQL - save data to MySQL database (default is "no")
59     +# / ShowLeagueTotals - Show totals row after league tables - (default is "yes")
60     +# / DBHost - MySQL server hostname (default is "localhost").
61     +# / DBPort - MySQL server post (default is "3306")
62     +# / Interval - "daily", "weekly", "fortnightly", "monthly", "99999" - last is number of hours (default is daily)
63     +# / Base - "Midnight", "Midday", "Now", "99" hour (0-23) (default is midnight)
64     +# / HTMLEmail - "yes", "no", "both" - default is "No" - Send email in HTML
65     +# NOT YET INUSE - WIP!
66     +# / HTMLPage - "yes" / "no" - default is "yes" if HTMLEmail is "yes" or "both" otherwise "no"
67     +#
68     +#############################################################################
69     +#
70     +#
71     +# TODO
72     +#
73     +# 1. Delete loglines records from any previous run of same table
74     +# 2. Add tracking LogId for each cont in the table
75     +# 3. Use link directory file to generate h1 / h2 tags for title and section headings
76     +# 4. Ditto for links to underlying data
77     +#
78     +
79     +# internal modules (part of core perl distribution)
80     +use strict;
81     +use warnings;
82     +use Getopt::Long;
83     +use Pod::Usage;
84     +use POSIX qw/strftime floor/;
85     +use Time::Local;
86     +use Date::Parse;
87     +use Time::TAI64;
88     +use esmith::ConfigDB;
89     +use esmith::DomainsDB;
90     +use Sys::Hostname;
91     +use Switch;
92     +use DBIx::Simple;
93     +use URI::URL;
94     +
95     +#use CGI;
96     +#use HTML::TextToHTML;
97     +
98     +my $hostname = hostname();
99     +my $cdb = esmith::ConfigDB->open_ro or die "Couldn't open ConfigDB : $!\n";
100     +
101     +my $true = 1;
102     +my $false = 0;
103     +#and see if mailstats are disabled
104     +my $disabled;
105     +if ($cdb->get('mailstats')){
106     + $disabled = !(($cdb->get('mailstats')->prop('Status') || 'enabled') eq 'enabled');
107     +} else {
108     + my $db = esmith::ConfigDB->open; my $record = $db->new_record('mailstats', { type => 'report', Status => 'enabled', Email => 'admin' });
109     + $cdb = esmith::ConfigDB->open_ro or die "Couldn't open ConfigDB : $!\n"; #Open up again to pick up new record
110     + $disabled = $false;
111     +}
112     +
113     +#Configuration section
114     +my %opt = (
115     + version => '0.7.13', # please update at each change.
116     + debug => 0, # guess what ?
117     + sendmail => '/usr/sbin/sendmail', # Path to sendmail stub
118     + from => 'spamfilter-stats', # Who is the mail from
119     + mail => $cdb->get('mailstats')->prop('Email') || 'admin', # mailstats email recipient
120     + timezone => `date +%z`,
121     +);
122     +
123     +my $FetchmailIP = '127.0.0.200'; #Apparent Ip address of fetchmail deliveries
124     +my $WebmailIP = '127.0.0.1'; #Apparent Ip of Webmail sender
125     +my $localhost = 'localhost'; #Apparent sender for webmail
126     +my $FETCHMAIL = 'FETCHMAIL'; #Sender from fetchmail when Ip address not 127.0.0.200 - when qpsmtpd denies the email
127     +my $MAILMAN = "bounces"; #sender when mailman sending when orig is localhost
128     +my $DMARCDomain="dmarc"; #Pattern to recognised DMARC sent emails (this not very reliable, as the email address could be anything)
129     +my $DMARCOkPattern="dmarc: pass"; #Pattern to use to detect DMARC approval
130     +my $localIPregexp = ".*((127\.)|(10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(192\.168\.)).*";
131     +my $MinCol = 6; #Minimum column width
132     +my $HourColWidth = 16; #Date and time column width
133     +
134     +my $SARulethresholdPercent = 10; #If Sa rules less than this of total emails, then cutoff reduced
135     +my $maxcutoff = 1; #max percent cutoff applied
136     +my $mincutoff = 0.2; #min percent cutoff applied
137     +
138     +my $tstart = time;
139     +
140     +#Local variables
141     +my $YEAR = ( localtime(time) )[5]; # this is years since 1900
142     +
143     +my $total = 0;
144     +my $spamcount = 0;
145     +my $spamavg = 0;
146     +my $spamhits = 0;
147     +my $hamcount = 0;
148     +my $hamavg = 0;
149     +my $hamhits = 0;
150     +my $rejectspamavg = 0;
151     +my $rejectspamhits= 0;
152     +
153     +my $Accepttotal = 0;
154     +my $localAccepttotal = 0; #Fetchmail connections
155     +my $localsendtotal = 0; #Connections from local PCs
156     +my $totalexamined = 0; #total download + RBL etc
157     +my $WebMailsendtotal = 0; #total from Webmail
158     +my $mailmansendcount = 0; #total from mailman
159     +my $DMARCSendCount = 0; #total DMARC reporting emails sent (approx)
160     +my $DMARCOkCount = 0; #Total emails approved through DMARC
161     +
162     +
163     +
164     +my %found_viruses = ();
165     +my %found_qpcodes = ();
166     +my %found_SARules = ();
167     +my %junkcount = ();
168     +my %unrecog_plugin = ();
169     +my %blacklistURL = (); #Count of use of each balcklist rhsbl
170     +my %usercounts = (); #Count per received email of sucessful delivery, queued spam and deleted Spam, and rejected
171     +
172     +# replaced by...
173     +my %counts = (); #Hold all counts in 2-D matrix
174     +my @display = (); #used to switch on and off columns - yes, no or auto for each category
175     +my @colwidth = (); #width of each column
176     + #(auto means only if non zero) - populated from possible db entries
177     +my @finaldisplay = (); #final decision on display or not - true or false
178     +
179     +#count column names, used for headings - also used for DB mailstats property names
180     +my $CATHOUR='Hour';
181     +my $CATFETCHMAIL='Fetchmail';
182     +my $CATWEBMAIL='WebMail';
183     +my $CATMAILMAN='Mailman';
184     +my $CATLOCAL='Local';
185     +my $CATRELAY="Relay";
186     +# border between where it came from and where it ended..
187     +my $countfromhere = 6; #Temp - Check this not moved!!
188     +
189     +my $CATVIRUS='Virus';
190     +my $CATRBLDNS='RBL/DNS';
191     +my $CATEXECUT='Execut.';
192     +my $CATNONCONF='Non.Conf.';
193     +my $CATBADCOUNTRIES='Geoip.';
194     +my $CATKARMA="Karma";
195     +
196     +my $CATSPAMDEL='Del.Spam';
197     +my $CATSPAM='Qued.Spam?';
198     +my $CATHAM='Ham';
199     +my $CATTOTALS='TOTALS';
200     +my $CATPERCENT='PERCENT';
201     +my $CATDMARC="DMARC Rej.";
202     +my $CATLOAD="Rej.Load";
203     +my @categs = ($CATHOUR,$CATFETCHMAIL,$CATWEBMAIL,$CATMAILMAN,$CATLOCAL,$CATRELAY,$CATDMARC,$CATVIRUS,$CATRBLDNS,$CATEXECUT,$CATBADCOUNTRIES,$CATNONCONF,$CATLOAD,$CATKARMA,$CATSPAMDEL,$CATSPAM,$CATHAM,$CATTOTALS,$CATPERCENT);
204     +my $GRANDTOTAL = '99'; #subs for count arrays, for grand total
205     +my $PERCENT = '98'; # for column percentages
206     +
207     +my $categlen = @categs-2; #-2 to avoid the total and percent column
208     +
209     +#
210     +# Index for certain columns - check these do not move if we add columns
211     +#
212     +#my $BadCountryCateg=9;
213     +#my $DMARCcateg = 5; #Not used.
214     +#my $KarmaCateg=$BadCountryCateg+3;
215     +
216     +my %categindex;
217     +@categindex{@categs} = (0..$#categs);
218     +my $BadCountryCateg=$categindex{$CATBADCOUNTRIES};
219     +my $DMARCcateg = $categindex{$CATDMARC}; #Not used.
220     +my $KarmaCateg=$categindex{$CATKARMA};
221     +
222     +my $above15 = 0;
223     +my $RBLcount = 0;
224     +my $MiscDenyCount = 0;
225     +my $PatternFilterCount = 0;
226     +my $noninfectedcount = 0;
227     +my $okemailcount = 0;
228     +my $infectedcount = 0;
229     +my $warnnoreject = " ";
230     +my $rblnotset = ' ';
231     +
232     +my %found_countries = ();
233     +my $total_countries = 0;
234     +my $BadCountries = ""; #From the DB
235     +
236     +my $FS = "\t"; # field separator used by logterse plugin
237     +my %log_items = ( "", "", "", "", "", "", "", "" );
238     +my $score;
239     +my %timestamp_items = ();
240     +my $localflag = 0; #indicate if current email is local or not
241     +my $WebMailflag = 0; #indicate if current mail is send from webmail
242     +
243     +# some storage for by recipient domains stats (PS)
244     +# my bad : I have to deal with multiple simoultaneous connections
245     +# will play with the process number.
246     +# my $currentrcptdomain = '' ;
247     +my %currentrcptdomain ; # temporay store the recipient domain until end of mail processing
248     +my %byrcptdomain ; # Store 'by domains stats'
249     +my @extdomain ; # only useful in some MX-Backup case, when any subdomains are allowed
250     +my $morethanonercpt = 0 ; # count every 'second' recipients for a mail.
251     +my $recipcount = 0; # count every recipient email address received.
252     +
253     +#
254     +#Load up the emails curreently stored for DMARC reporting - so that we cna spot the reports being sent.
255     +#Held in an slqite db, created by the DMARC perl lib.
256     +#
257     +my $dsn = "dbi:SQLite:dbname=/var/lib/qpsmtpd/dmarc/reports.sqlite"; #Taken from /etc/mail-dmarc.ini
258     +# doesn't seem to need
259     +my $user = "";
260     +my $pass = "";
261     +my $DMARC_Report_emails = ""; #Flat string of all email addresses
262     +
263     + if (my $dbix = DBIx::Simple->connect( $dsn, $user, $pass )){
264     + my $result = $dbix->query("select rua from report_policy_published;");
265     + $result->bind(my ($emailaddress));
266     + while ($result->fetch){
267     + #remember email from logterse entry has chevrons round it - so we add them here to guarantee the alighment of the match
268     + #Remove the mailto:
269     + $emailaddress =~ s/mailto://g;
270     + # and map any commas to ><
271     + $emailaddress =~ s/,/></g;
272     + $DMARC_Report_emails .= "<".$emailaddress.">\n"
273     + }
274     + $dbix->disconnect();
275     + } else { $DMARC_Report_emails = "None found - DB not opened"}
276     +
277     +
278     +
279     +# and setup list of local domains for spotting the local one in a list of email addresses (Remote station processing)
280     +use esmith::DomainsDB;
281     +my $d = esmith::DomainsDB->open_ro();
282     +my @domains = $d->keys();
283     +my $alldomains = "(";
284     +foreach my $dom (@domains){$alldomains .= $dom."|"}
285     +$alldomains .= ")";
286     +
287     +# Saving the Log lines processed
288     +my %LogLines = (); #Save all the log lines processed for writing to the DB
289     +my %LogId = (); #Save the Log Ids.
290     +my $CurrentLogId = "";
291     +my $Sequence = 0;
292     +
293     +
294     +# store the domain of interest. Every other records are stored in a 'Other' zone
295     +my $ddb = esmith::DomainsDB->open_ro or die "Couldn't open DomainsDB : $!\n";
296     +
297     +foreach my $domain( $ddb->get_all_by_prop( type => "domain" ) ) {
298     + $byrcptdomain{ $domain->key }{ 'type' }='local';
299     +}
300     +$byrcptdomain{ $cdb->get('SystemName')->value . "."
301     + . $cdb->get('DomainName')->value }{ 'type' } = 'local';
302     +
303     +# is this system a MX-Backup ?
304     +if ($cdb->get('mxbackup')){
305     + if ( ( $cdb->get('mxbackup')->prop('status') || 'disabled' ) eq 'enabled' ) {
306     + my %MXValues = split( /,/, ( $cdb->get('mxbackup')->prop('name') || '' ) ) ;
307     + foreach my $data ( keys %MXValues ) {
308     + $byrcptdomain{ $data }{ 'type' } = "mxbackup-$MXValues{ $data }" ;
309     + if ( $MXValues{ $data } == 1 ) { # subdomains allowed, must take care of this
310     + push @extdomain, $data ;
311     + }
312     + }
313     + }
314     +}
315     +
316     +my ( $start, $end ) = analysis_period();
317     +
318     +
319     +#
320     +# First check current configuration for logging, DNS enable and Max threshold for spamassassin
321     +#
322     +
323     +my $LogLevel = $cdb->get('qpsmtpd')->prop('LogLevel');
324     +my $HighLogLevel = ( $LogLevel > 6 );
325     +
326     +my $RHSenabled =
327     + ( $cdb->get('qpsmtpd')->prop('RHSBL') eq 'enabled' );
328     +my $DNSenabled =
329     + ( $cdb->get('qpsmtpd')->prop('DNSBL') eq 'enabled' );
330     +my $SARejectLevel =
331     + $cdb->get('spamassassin')->prop('RejectLevel');
332     +my $SATagLevel =
333     + $cdb->get('spamassassin')->prop('TagLevel');
334     +my $DomainName =
335     + $cdb->get('DomainName')->value;
336     +
337     +# check that logterse is in use
338     +#my pluginfile = '/var/service/qpsmtpd/config/peers/0';
339     +
340     +if ( !$RHSenabled || !$DNSenabled ) {
341     + $rblnotset = '*';
342     +}
343     +
344     +if ( $SARejectLevel == 0 ) {
345     +
346     + $warnnoreject = "(*Warning* 0 = no reject)";
347     +
348     +}
349     +
350     +# get enable/disable subsections
351     +my $enableqpsmtpdcodes;
352     +my $enableSARules;
353     +my $enableGeoiptable;
354     +my $enablejunkMailList;
355     +my $savedata;
356     +my $enableblacklist; #Enabled according to setting in qpsmtpd
357     +if ($cdb->get('mailstats')){
358     + $enableqpsmtpdcodes = ($cdb->get('mailstats')->prop("QpsmtpdCodes") || "enabled") eq "enabled" || $false;
359     + $enableSARules = ($cdb->get('mailstats')->prop("SARules") || "enabled") eq "enabled" || $false;
360     + $enablejunkMailList = ($cdb->get('mailstats')->prop("JunkMailList") || "enabled") eq "enabled" || $false;
361     + $enableGeoiptable = ($cdb->get('mailstats')->prop("Geoiptable") || "enabled") eq "enabled" || $false;
362     + $savedata = ($cdb->get('mailstats')->prop("SaveDataToMySQL") || "no") eq "yes" || $false;
363     + } else {
364     + $enableqpsmtpdcodes = $true;
365     + $enableSARules = $true;
366     + $enablejunkMailList = $true;
367     + $enableGeoiptable = $true;
368     + $savedata = $false;
369     + }
370     + $enableblacklist = ($cdb->get('qpsmtpd')->prop("RHSBL") || "disabled") eq "enabled" || ($cdb->get('qpsmtpd')->prop("URIBL") || "disabled") eq "enabled";
371     +
372     +my $makeHTMLemail = "no";
373     +#if ($cdb->get('mailstats')){$makeHTMLemail = $cdb->get('mailstats')->prop('HTMLEmail') || "no"} #TEMP!!
374     +my $makeHTMLpage = "no";
375     +#if ($makeHTMLemail eq "yes" || $makeHTMLemail eq "both") {$makeHTMLpage = "yes"}
376     +#if ($cdb->get('mailstats')){$makeHTMLpage = $cdb->get('mailstats')->prop('HTMLPage') || "no"}
377     +
378     +
379     +# Init the hashes
380     +my $nhour = floor( $start / 3600 );
381     +my $ncateg;
382     +while ( $nhour < $end / 3600 ) {
383     + $counts{$nhour}=();
384     + $ncateg = 0;
385     + while ( $ncateg < @categs) {
386     + $counts{$nhour}{$categs[$ncateg-1]} = 0;
387     + $ncateg++
388     + }
389     + $nhour++;
390     +}
391     +# and grand totals, percent and display status from db entries, and column widths
392     +$ncateg = 0;
393     +my $colpadding = 0;
394     +while ( $ncateg < @categs) {
395     + $counts{$GRANDTOTAL}{$categs[$ncateg]} = 0;
396     + $counts{$PERCENT}{$categs[$ncateg]} = 0;
397     +
398     + if ($cdb->get('mailstats')){
399     + $display[$ncateg] = lc($cdb->get('mailstats')->prop($categs[$ncateg])) || "auto";
400     + } else {
401     + $display[$ncateg] = 'auto'
402     + }
403     + if ($ncateg == 0) {
404     + $colwidth[$ncateg] = $HourColWidth + $colpadding;
405     + } else {
406     + $colwidth[$ncateg] = length($categs[$ncateg])+1+$colpadding;
407     + }
408     + if ($colwidth[$ncateg] < $MinCol) {$colwidth[$ncateg] = $MinCol + $colpadding}
409     + $ncateg++
410     +}
411     +
412     +my $starttai = Time::TAI64::unixtai64n($start);
413     +my $endtai = Time::TAI64::unixtai64n($end);
414     +my $sum_SARules = 0;
415     +
416     +# we remove non valid files
417     +my @ARGV2;
418     +foreach ( map { glob } @ARGV){
419     + push(@ARGV2,($_));
420     +}
421     +@ARGV=@ARGV2;
422     +
423     +my $count = -1; #for loop reduction in debugging mode
424     +
425     +#
426     +#---------------------------------------
427     +# Scan the qpsmtpd log file(s)
428     +#---------------------------------------
429     +
430     +
431     +my $CurrentMailId = "";
432     +
433     +LINE: while (<>) {
434     +
435     + next LINE if !(my($tai,$log) = split(' ',$_,2));
436     +
437     +
438     + #If date specified, only process lines matching date
439     + next LINE if ( $tai lt $starttai );
440     + next LINE if ( $tai gt $endtai );
441     +
442     + #Count lines and skip out if debugging
443     + $count++;
444     + #last LINE if ($opt{debug} && $count >= 100);
445     +
446     +
447     + #Loglines to Saved String for later DB write
448     + if ($savedata) {
449     + my $CurrentLine = $_;
450     + $CurrentLine = /^\@([0-9a-z]*) ([0-9]*) .*$/;
451     + my $l = length($CurrentLine);
452     + if ($l != 0){
453     + if (defined($2)){
454     + if ($2 ne $CurrentMailId) {
455     + print "CL:$CurrentLine*\n" if !defined($1);
456     + $CurrentLogId = $1."-".$2;
457     + $CurrentMailId = $2;
458     + $Sequence = 0;
459     + } else {$Sequence++}
460     + #$CurrentLogId .=":".$Sequence;
461     + $LogLines{$CurrentLogId.":".$Sequence} = $_;
462     + }
463     + }
464     + }
465     +
466     +
467     + # pull out spamasassin rule lists
468     + if ( $_ =~m/spamassassin: pass, Ham,(.*)</ )
469     + #if ( $_ =~m/spamassassin plugin.*: check_spam:.*hits=(.*), required.*tests=(.*)/ )
470     + {
471     + #New version does not seem to have spammassasin tests in logs
472     + #if (exists($2){
473     + #my (@SAtests) = split(',',$2);
474     + #foreach my $SAtest (@SAtests) {
475     + #if (!$SAtest eq "") {
476     + #$found_SARules{$SAtest}{'count'}++;
477     + #$found_SARules{$SAtest}{'totalhits'} += $1;
478     + #$sum_SARules++
479     + #}
480     + #}
481     + #}
482     +
483     + }
484     +
485     +
486     + #Pull out Geoip countries for analysis table
487     + if ( $_ =~m/check_badcountries: GeoIP Country: (.*)/ )
488     + {
489     + $found_countries{$1}++;
490     + $total_countries++;
491     + }
492     +
493     + #Pull out DMARC approvals
494     + if ( $_ =~m/.*$DMARCOkPattern.*/ )
495     + {
496     + $DMARCOkCount++;
497     + }
498     +
499     +
500     + #only select Logterse output
501     + next LINE unless m/logging::logterse:/;
502     +
503     + my $abstime = Time::TAI64::tai2unix($tai);
504     + my $abshour = floor( $abstime / 3600 ); # Hours since the epoch
505     +
506     +
507     + my ($timestamp_part, $log_part) = split('`',$_,2); #bjr 0.6.12
508     + my (@log_items) = split $FS, $log_part;
509     +
510     + my (@timestamp_items) = split(' ',$timestamp_part);
511     +
512     + my $result= "rejected"; #Tag as rejected unti we know otherwise
513     + # we store the more recent recipient domain, for domain statistics
514     + # in fact, we only store the first recipient. Could be sort of headhache
515     + # to obtain precise stats with many recipients on more than one domain !
516     + my $proc = $timestamp_items[1] ; #numeric Id for the email
517     + my $emailnum = $proc; #proc gets modified later...
518     +
519     + if ($emailnum == 23244) {
520     + }
521     +
522     + $totalexamined++;
523     +
524     +
525     + # first spot the fetchmail and local deliveries.
526     +
527     + # Spot from local workstation
528     + $localflag = 0;
529     + $WebMailflag = 0;
530     + if ( $log_items[1] =~ m/$DomainName/ ) { #bjr
531     + $localsendtotal++;
532     + $counts{$abshour}{$CATLOCAL}++;
533     + $localflag = 1;
534     + }
535     +
536     + #Or a remote station
537     + elsif ((!test_for_private_ip($log_items[0])) and (test_for_private_ip($log_items[2])) and ($log_items[5] eq "queued"))
538     + {
539     + #Remote user
540     + $localflag = 1;
541     + $counts{$abshour}{$CATRELAY}++;
542     + }
543     +
544     + elsif (($log_items[2] =~ m/$WebmailIP/) and (!test_for_private_ip($log_items[0]))) {
545     + #Webmail
546     + $localflag = 1;
547     + $WebMailsendtotal++;
548     + $counts{$abshour}{$CATWEBMAIL}++;
549     + $WebMailflag = 1;
550     + }
551     +
552     + # see if from localhost
553     + elsif ( $log_items[1] =~ m/$localhost/ ) {
554     + # but not if it comes from fetchmail
555     + if ( $log_items[3] =~ m/$FETCHMAIL/ ) { }
556     + else {
557     + $localflag = 1;
558     + # might still be from mailman here
559     + if ( $log_items[3] =~ m/$MAILMAN/ ) {
560     + $mailmansendcount++;
561     + $localsendtotal++;
562     + $counts{$abshour}{$CATMAILMAN}++;
563     + $localflag = 1;
564     + }
565     + else {
566     + #Or sent to the DMARC server
567     + #check for email address in $DMARC_Report_emails string
568     + my $logemail = $log_items[4];
569     + if ((index($DMARC_Report_emails,$logemail)>=0) or ($logemail =~ m/$DMARCDomain/)){
570     + $localsendtotal++;
571     + $DMARCSendCount++;
572     + $localflag = 1;
573     + }
574     + else {
575     + if (exists $log_items[8]){
576     + # ignore incoming localhost spoofs
577     + if ( $log_items[8] =~ m/msg denied before queued/ ) { }
578     + else {
579     + #Webmail
580     + $localflag = 1;
581     + $WebMailsendtotal++;
582     + $counts{$abshour}{$CATWEBMAIL}++;
583     + $WebMailflag = 1;
584     + }
585     + }
586     + else {
587     + $localflag = 1;
588     + $WebMailsendtotal++;
589     + $counts{$abshour}{$CATWEBMAIL}++;
590     + $WebMailflag = 1;
591     + }
592     + }
593     + }
594     + }
595     + }
596     +
597     + # try to spot fetchmail emails
598     + if ( $log_items[0] =~ m/$FetchmailIP/ ) {
599     + $localAccepttotal++;
600     + $counts{$abshour}{$CATFETCHMAIL}++;
601     + }
602     + elsif ( $log_items[3] =~ m/$FETCHMAIL/ ) {
603     + $localAccepttotal++;
604     + $counts{$abshour}{$CATFETCHMAIL}++;
605     + }
606     +
607     +# and adjust for recipient field if not set-up by denying plugin - extract from deny msg
608     +
609     + if ( length( $log_items[4] ) == 0 ) {
610     + if ( $log_items[5] eq 'check_goodrcptto' ) {
611     + if ( $log_items[7] gt "invalid recipient" ) {
612     + $log_items[4] =
613     + substr( $log_items[7], 16 ); #Leave only email address
614     +
615     + }
616     + }
617     + }
618     +
619     + # if ( ( $currentrcptdomain{ $proc } || '' ) eq '' ) {
620     + # reduce to lc and process each e,mail if a list, pseperatedy commas
621     + my $recipientmail = lc( $log_items[4] );
622     + if ( $recipientmail =~ m/.*,/ ) {
623     +
624     + #comma - split the line and deal with each domain
625     + # print $recipientmail."\n";
626     + my ($recipients) = split( ',', $recipientmail );
627     + foreach my $recip ($recipients) {
628     + $proc = $proc . $recip;
629     +
630     + # print $proc."\n";
631     + $currentrcptdomain{$proc} = $recip;
632     + add_in_domain($proc);
633     + $recipcount++;
634     + }
635     +
636     + # print "*\n";
637     + #count emails with more than one recipient
638     + # $recipientmail =~ m/(.*),/;
639     + # $currentrcptdomain{ $proc } = $1;
640     + }
641     + else {
642     + $proc = $proc . $recipientmail;
643     + $currentrcptdomain{$proc} = $recipientmail;
644     + add_in_domain($proc);
645     + $recipcount++;
646     + }
647     +
648     + # } else {
649     + # # there more than a recipient for a mail, how many daily ?
650     + # $morethanonercpt++;
651     + # }
652     +
653     +
654     + # then categorise the result
655     +
656     +
657     + if (exists $log_items[5]) {
658     +
659     + if ($log_items[5] eq 'naughty') {
660     + my $rejreason = $log_items[7];
661     + $rejreason = /.*(\(.*\)).*/;
662     + if (!defined($1)){$rejreason = "unknown"}
663     + else {$rejreason = $1}
664     + $found_qpcodes{$log_items[5]."-".$rejreason}++}
665     + else {$found_qpcodes{$log_items[5]}++} ##Count different qpsmtpd result codes
666     +
667     + if ($log_items[5] eq 'check_earlytalker') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
668     +
669     + elsif ($log_items[5] eq 'check_relay') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
670     +
671     + elsif ($log_items[5] eq 'check_norelay') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
672     +
673     + elsif ($log_items[5] eq 'require_resolvable_fromhost') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
674     +
675     + elsif ($log_items[5] eq 'check_basicheaders') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
676     +
677     + elsif ($log_items[5] eq 'rhsbl') { $RBLcount++;$counts{$abshour}{$CATRBLDNS}++;mark_domain_rejected($proc);$blacklistURL{get_domain($log_items[7])}++}
678     +
679     + elsif ($log_items[5] eq 'dnsbl') { $RBLcount++;$counts{$abshour}{$CATRBLDNS}++;mark_domain_rejected($proc);$blacklistURL{get_domain($log_items[7])}++}
680     +
681     + elsif ($log_items[5] eq 'check_badmailfrom') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
682     +
683     + elsif ($log_items[5] eq 'check_badrcptto_patterns') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
684     +
685     + elsif ($log_items[5] eq 'check_badrcptto') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
686     +
687     + elsif ($log_items[5] eq 'check_spamhelo') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
688     +
689     + elsif ($log_items[5] eq 'check_goodrcptto extn') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
690     +
691     + elsif ($log_items[5] eq 'rcpt_ok') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
692     +
693     + elsif ($log_items[5] eq 'pattern_filter') { $PatternFilterCount++;$counts{$abshour}{$CATEXECUT}++;mark_domain_rejected($proc)}
694     +
695     + elsif ($log_items[5] eq 'virus::pattern_filter') { $PatternFilterCount++;$counts{$abshour}{$CATEXECUT}++;mark_domain_rejected($proc)}
696     +
697     + elsif ($log_items[5] eq 'check_goodrcptto') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
698     +
699     + elsif ($log_items[5] eq 'check_smtp_forward') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
700     +
701     + elsif ($log_items[5] eq 'count_unrecognized_commands') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
702     +
703     + elsif ($log_items[5] eq 'check_badcountries') {$MiscDenyCount++;$counts{$abshour}{$CATBADCOUNTRIES}++;mark_domain_rejected($proc)}
704     +
705     + elsif ($log_items[5] eq 'tnef2mime') { } #Not expecting this one.
706     +
707     + elsif ($log_items[5] eq 'spamassassin') { $above15++;$counts{$abshour}{$CATSPAMDEL}++;
708     + # and extract the spam score
709     + # if ($log_items[8] =~ "Yes, hits=(.*) required=([0-9\.]+)")
710     + if ($log_items[8] =~ "Yes, score=(.*) required=([0-9\.]+)")
711     + {$rejectspamavg += $1}
712     + mark_domain_rejected($proc);
713     + }
714     +
715     + elsif (($log_items[5] eq 'virus::clamav') or ($log_items[5] eq 'virus::clamdscan')) { $infectedcount++;$counts{$abshour}{$CATVIRUS}++;
716     + #extract the virus name
717     + if ($log_items[7] =~ "Virus found: (.*)" ) {$found_viruses{$1}++;}
718     + else {$found_viruses{$log_items[7]}++} #Some other message!!
719     + mark_domain_rejected($proc);
720     + }
721     +
722     + elsif ($log_items[5] eq 'queued') { $Accepttotal++;
723     + #extract the spam score
724     + # Remove count for rejectred as it looks as if it might get through!!
725     + $result= "queued";
726     + if ($log_items[8] =~ ".*score=([+-]?\\d+\.?\\d*).* required=([0-9\.]+)") {
727     + $score = trim($1);
728     + if ($score =~ /^[+-]?\d+\.?\d*$/ ) #check its numeric
729     + {
730     + if ($score < $SATagLevel) { $hamcount++;$counts{$abshour}{$CATHAM}++;$hamavg += $score;}
731     + else {$spamcount++;$counts{$abshour}{$CATSPAM}++;$spamavg += $score;$result= "spam";}
732     + } else {
733     + print "Unexpected non numeric found in $proc:".$log_items[8]."($score)\n";
734     + }
735     + } else {
736     + # no SA score - treat it as ham
737     + $hamcount++;$counts{$abshour}{$CATHAM}++;
738     + }
739     + if ( ( $currentrcptdomain{ $proc } || '' ) ne '' ) {
740     + $byrcptdomain{ $currentrcptdomain{ $proc } }{ 'accept' }++ ;
741     + $currentrcptdomain{ $proc } = '' ;
742     + }
743     + }
744     +
745     +
746     + elsif ($log_items[5] eq 'tls') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
747     +
748     + elsif ($log_items[5] eq 'auth::auth_cvm_unix_local') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
749     +
750     + elsif ($log_items[5] eq 'earlytalker') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
751     +
752     + elsif ($log_items[5] eq 'uribl') {$RBLcount++;$counts{$abshour}{$CATRBLDNS}++;mark_domain_rejected($proc);$blacklistURL{get_domain($log_items[7])}++}
753     +
754     + elsif ($log_items[5] eq 'naughty') {
755     + #Naughty plugin seems to span a number of rejection reasons - so we have to use the next but one log_item[7] to identify
756     + if ($log_items[7] =~ m/(karma)/) {
757     + $MiscDenyCount++;$counts{$abshour}{$CATKARMA}++;mark_domain_rejected($proc)}
758     + elsif ($log_items[7] =~ m/(dnsbl)/){
759     + $RBLcount++;$counts{$abshour}{$CATRBLDNS}++;mark_domain_rejected($proc);$blacklistURL{get_domain($log_items[7])}++}
760     + elsif ($log_items[7] =~ m/(helo)/){
761     + $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
762     + else {
763     + #Unidentified Naughty rejection
764     + $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);$unrecog_plugin{$log_items[5]."-".$log_items[7]}++}
765     + }
766     + elsif ($log_items[5] eq 'resolvable_fromhost') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
767     +
768     + elsif ($log_items[5] eq 'loadcheck') {$MiscDenyCount++;$counts{$abshour}{$CATLOAD}++;mark_domain_rejected($proc)}
769     +
770     + elsif ($log_items[5] eq 'karma') {$MiscDenyCount++;$counts{$abshour}{$CATKARMA}++;mark_domain_rejected($proc)}
771     +
772     + elsif ($log_items[5] eq 'dmarc') {$MiscDenyCount++;$counts{$abshour}{$CATDMARC}++;mark_domain_rejected($proc)}
773     +
774     + elsif ($log_items[5] eq 'relay') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
775     +
776     + elsif ($log_items[5] eq 'headers') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
777     +
778     + elsif ($log_items[5] eq 'mailfrom') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
779     +
780     + elsif ($log_items[5] eq 'badrcptto') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
781     +
782     + elsif ($log_items[5] eq 'helo') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
783     +
784     + elsif ($log_items[5] eq 'check_smtp_forward') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
785     +
786     + elsif ($log_items[5] eq 'sender_permitted_from') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
787     +
788     + #Treat it as Unconf if not recognised
789     + else {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);$unrecog_plugin{$log_items[5]}++}
790     + } #Log[5] exists
791     +
792     + #Entry if not local send
793     + if ($localflag == 0) {
794     + if (length($log_items[4]) > 0){
795     + # Need to check here for multiple email addresses
796     + my @emails = split(",",lc($log_items[4]));
797     + if (scalar(@emails) > 1) {
798     + #Just pick the first local address to hang it on.
799     + # TEMP - just go for the first address until I can work out how to spot the 1st "local" one
800     + $usercounts{$emails[0]}{$result}++;
801     + $usercounts{$emails[0]}{"proc"} = $proc;
802     + #Compare with @domains array until we get a local one
803     + my $gotone = $false;
804     + foreach my $email (@emails){
805     + #Extract the domain from the email address
806     + my $fullemail = $email;
807     + $email = s/.*\@(.*)$/$1/;
808     + #and see if it is local
809     + if ($email =~ m/$alldomains/){
810     + $usercounts{lc($fullemail)}{$result}++;
811     + $usercounts{lc($fullemail)}{"proc"} = $proc;
812     + $gotone = $true;
813     + last;
814     + }
815     + }
816     + if (!$gotone) {
817     + $usercounts{'No internal email $proc'}{$result}++;
818     + $usercounts{'No internal email $proc'}{"proc"} = $proc;
819     + }
820     +
821     + } else {
822     + $usercounts{lc($log_items[4])}{$result}++;
823     + $usercounts{lc($log_items[4])}{"proc"} = $proc;
824     + }
825     + }
826     + }
827     + #exit if $emailnum == 15858;
828     +
829     +} #END OF MAIN LOOP
830     +
831     +#total up grand total Columns
832     +$nhour = floor( $start / 3600 );
833     +while ( $nhour < $end / 3600 ) {
834     + $ncateg = 0; #past the where it came from columns
835     + while ( $ncateg < @categs) {
836     + #total columns
837     + $counts{$GRANDTOTAL}{$categs[$ncateg]} += $counts{$nhour}{$categs[$ncateg]};
838     +
839     + # and total rows
840     + if ( $ncateg < $categlen and $ncateg>=$countfromhere) {#skip initial columns of non final reasons
841     + $counts{$nhour}{$categs[@categs-2]} += $counts{$nhour}{$categs[$ncateg]};
842     + }
843     + $ncateg++
844     + }
845     +
846     + $nhour++;
847     +}
848     +
849     +
850     +
851     +#Compute row totals and row percentages
852     +$nhour = floor( $start / 3600 );
853     +while ( $nhour < $end / 3600 ) {
854     + $counts{$nhour}{$categs[@categs-1]} = $counts{$nhour}{$categs[@categs-2]}*100/$totalexamined if $totalexamined;
855     + $nhour++;
856     +
857     +}
858     +
859     +#compute column percentages
860     + $ncateg = 0;
861     + while ( $ncateg < @categs) {
862     + if ($ncateg == @categs-1) {
863     + $counts{$PERCENT}{$categs[$ncateg]} = $counts{$GRANDTOTAL}{$categs[$ncateg-1]}*100/$totalexamined if $totalexamined;
864     + } else {
865     + $counts{$PERCENT}{$categs[$ncateg]} = $counts{$GRANDTOTAL}{$categs[$ncateg]}*100/$totalexamined if $totalexamined;
866     + }
867     + $ncateg++
868     + }
869     +
870     +#compute sum of row percentages
871     +$nhour = floor( $start / 3600 );
872     +while ( $nhour < $end / 3600 ) {
873     + $counts{$GRANDTOTAL}{$categs[@categs-1]} += $counts{$nhour}{$categs[@categs-1]};
874     + $nhour++;
875     +
876     +}
877     +
878     +my $QueryNoLogTerse = ($totalexamined==0); #might indicate logterse not installed in qpsmtpd plugins
879     +
880     +#Calculate some numbers
881     +
882     +$spamavg = $spamavg / $spamcount if $spamcount;
883     +$rejectspamavg = $rejectspamavg / $above15 if $above15;
884     +$hamavg = $hamavg / $hamcount if $hamcount;
885     +
886     +# RBL etc percent of total SMTP sessions
887     +
888     +my $rblpercent = ( ( $RBLcount / $totalexamined ) * 100 ) if $totalexamined;
889     +my $PatternFilterpercent = ( ( $PatternFilterCount / $totalexamined ) * 100 ) if $totalexamined;
890     +my $Miscpercent = ( ( $MiscDenyCount / $totalexamined ) * 100 ) if $totalexamined;
891     +
892     +#Spam and virus percent of total email downloaded
893     +#Expressed as a % of total examined
894     +my $spampercent = ( ( $spamcount / $totalexamined ) * 100 ) if $totalexamined;
895     +my $hampercent = ( ( $hamcount / $totalexamined ) * 100 ) if $totalexamined;
896     +my $hrsinperiod = ( ( $end - $start ) / 3600 );
897     +my $emailperhour = ( $totalexamined / $hrsinperiod ) if $totalexamined;
898     +my $above15percent = ( $above15 / $totalexamined * 100 ) if $totalexamined;
899     +my $infectedpercent = ( ( $infectedcount / ($totalexamined) ) * 100 ) if $totalexamined;
900     +my $AcceptPercent = ( ( $Accepttotal / ($totalexamined) ) * 100 ) if $totalexamined;
901     +
902     +my $oldfh;
903     +
904     +#Open Sendmail if we are mailing it
905     +if ( $opt{'mail'} and !$disabled ) {
906     + open( SENDMAIL, "|$opt{'sendmail'} -oi -t -odq" )
907     + or die "Can't open sendmail: $!\n";
908     + print SENDMAIL "From: $opt{'from'}\n";
909     + print SENDMAIL "To: $opt{'mail'}\n";
910     + print SENDMAIL "Subject: Spam Filter Statistics from $hostname - ",
911     + strftime( "%F", localtime($start) ), "\n\n";
912     + $oldfh = select SENDMAIL;
913     +}
914     +
915     +my $telapsed = time - $tstart;
916     +
917     +if ( !$disabled ) {
918     +
919     + #Output results
920     +
921     + # NEW - save the print to a variable so that it can be processed into html.
922     + #
923     + #Save current output selection and divert into variable
924     + #
925     + my $output;
926     + my $tablestr="";
927     + open(my $outputFH, '>', \$tablestr) or die; # This shouldn't fail
928     + my $oldFH = select $outputFH;
929     +
930     +
931     + print "SMEServer daily Anti-Virus and Spamfilter statistics from $hostname - ".strftime( "%F", localtime($start))."\n";
932     + print "----------------------------------------------------------------------------------", "\n\n";
933     + print "$0 Version : $opt{'version'}", "\n";
934     + print "Period Beginning : ", strftime( "%c", localtime($start) ), "\n";
935     + print "Period Ending : ", strftime( "%c", localtime($end) ), "\n";
936     + print "Clam Version/DB Count/Last DB update: ",`freshclam -V`;
937     + print "SpamAssassin Version : ",`spamassassin -V`;
938     + printf "Tag level: %3d; Reject level: %-3d $warnnoreject\n", $SATagLevel,$SARejectLevel;
939     + if ($HighLogLevel) {
940     + printf "*Loglevel is set to: ".$LogLevel. " - you only need it set to 6\n";
941     + printf "\tYou can set it this way:\n";
942     + printf "\tconfig setprop qpsmtpd LogLevel 6\n";
943     + printf "\tsignal-event email-update\n";
944     + printf "\tsv t /var/service/qpsmtpd\n";
945     + }
946     + printf "Reporting Period : %-.2f hrs\n", $hrsinperiod;
947     + printf "All SMTP connections accepted:%-8d \n", $totalexamined;
948     + printf "Emails per hour : %-8.1f/hr\n", $emailperhour || 0;
949     + printf "Average spam score (accepted): %-11.2f\n", $spamavg || 0;
950     + printf "Average spam score (rejected): %-11.2f\n", $rejectspamavg || 0;
951     + printf "Average ham score : %-11.2f\n", $hamavg || 0;
952     + printf "Number of DMARC reporting emails sent:\t%-11d (not shown on table)\n", $DMARCSendCount || 0;
953     + if ($hamcount != 0){ printf "Number of emails approved through DMARC:\t%-11d (%-3d%% of Ham count)\n", $DMARCOkCount|| 0,$DMARCOkCount*100/$hamcount || 0;}
954     +
955     + my $smeoptimizerprog = "/usr/local/smeoptimizer/SMEOptimizer.pl";
956     + if (-e $smeoptimizerprog) {
957     + #smeoptimizer installed - get result of status
958     + my @smeoptimizerlines = split(/\n/,`/usr/local/smeoptimizer/SMEOptimizer.pl -status`);
959     + print("SMEOptimizer status:\n");
960     + print("\t".$smeoptimizerlines[6]."\n");
961     + print("\t".$smeoptimizerlines[7]."\n");
962     + print("\t".$smeoptimizerlines[8]."\n");
963     + print("\t".$smeoptimizerlines[9]."\n");
964     + print("\t".$smeoptimizerlines[10]."\n");
965     + }
966     +
967     +
968     + print "\nStatistics by Hour:\n";
969     + #
970     + # start by working out which colunns to show - tag the display array
971     + #
972     + $ncateg = 1; ##skip the first column
973     + $finaldisplay[0] = $true;
974     + while ( $ncateg < $categlen) {
975     + if ($display[$ncateg] eq 'yes') { $finaldisplay[$ncateg] = $true }
976     + elsif ($display[$ncateg] eq 'no') { $finaldisplay[$ncateg] = $false }
977     + else {
978     + $finaldisplay[$ncateg] = ($counts{$GRANDTOTAL}{$categs[$ncateg]} != 0);
979     + if ($finaldisplay[$ncateg]) {
980     + #if it has been non zero and auto, then make it yes for the future.
981     + esmith::ConfigDB->open->get('mailstats')->set_prop($categs[$ncateg],'yes')
982     + }
983     +
984     + }
985     + $ncateg++
986     + }
987     + #make sure total and percentages are shown
988     + $finaldisplay[@categs-2] = $true;
989     + $finaldisplay[@categs-1] = $true;
990     +
991     +
992     + # and put together the print lines
993     +
994     + my $Line1; #Full Line across the page
995     + my $Line2; #Broken Line across the page
996     + my $Titles; #Column headers
997     + my $Values; #Values
998     + my $Totals; #Corresponding totals
999     + my $Percent; # and column percentages
1000     +
1001     + my $hour = floor( $start / 3600 );
1002     + $Line1 = '';
1003     + $Line2 = '';
1004     + $Titles = '';
1005     + $Values = '';
1006     + $Totals = '';
1007     + $Percent = '';
1008     + while ( $hour < $end / 3600 ) {
1009     + if ($hour == floor( $start / 3600 )){
1010     + #Do all the once only things
1011     + $ncateg = 0;
1012     + while ( $ncateg < @categs) {
1013     + if ($finaldisplay[$ncateg]){
1014     + $Line1 .= substr('---------------------',0,$colwidth[$ncateg]);
1015     + $Line2 .= substr('---------------------',0,$colwidth[$ncateg]-1);
1016     + $Line2 .= " ";
1017     + $Titles .= sprintf('%'.($colwidth[$ncateg]-1).'s',$categs[$ncateg])."|";
1018     + if ($ncateg == 0) {
1019     + $Totals .= substr('TOTALS ',0,$colwidth[$ncateg]-2);
1020     + $Percent .= substr('PERCENTAGES ',0,$colwidth[$ncateg]-1);
1021     + } else {
1022     + # identify bottom right group and supress unless db->ShowGranPerc set
1023     + if ($ncateg==@categs-1){
1024     + $Totals .= sprintf('%'.$colwidth[$ncateg].'.1f',$counts{$GRANDTOTAL}{$categs[$ncateg]}).'%';
1025     + } else {
1026     + $Totals .= sprintf('%'.$colwidth[$ncateg].'d',$counts{$GRANDTOTAL}{$categs[$ncateg]});
1027     + }
1028     + $Percent .= sprintf('%'.($colwidth[$ncateg]-1).'.1f',$counts{$PERCENT}{$categs[$ncateg]}).'%';
1029     + }
1030     + }
1031     + $ncateg++
1032     + }
1033     + }
1034     +
1035     + $ncateg = 0;
1036     + while ( $ncateg < @categs) {
1037     + if ($finaldisplay[$ncateg]){
1038     + if ($ncateg == 0) {
1039     + $Values .= strftime( "%F, %H", localtime( $hour * 3600 ) )." "
1040     + } elsif ($ncateg == @categs-1) {
1041     + #percentages in last column
1042     + $Values .= sprintf('%'.($colwidth[$ncateg]-2).'.1f',$counts{$hour}{$categs[$ncateg]})."%";
1043     + } else {
1044     + #body numbers
1045     + $Values .= sprintf('%'.($colwidth[$ncateg]-1).'d',$counts{$hour}{$categs[$ncateg]})." ";
1046     + }
1047     + if (($ncateg == @categs-1)){$Values=$Values."\n"} #&& ($hour == floor($end / 3600)-1)
1048     + }
1049     + $ncateg++
1050     + }
1051     +
1052     + $hour++;
1053     + }
1054     +
1055     + #
1056     + # print it.
1057     + #
1058     +
1059     + print $Line1."\n";
1060     + #if ($makeHTMLemail eq "no" && $makeHTMLpage eq "no"){print $Line1."\n";} #These lines mess up the HTML conversion ....
1061     + print $Titles."\n";
1062     + #if ($makeHTMLemail eq "no" && $makeHTMLpage eq "no"){print $Line2."\n";} #ditto
1063     + print $Line2."\n";
1064     + print $Values;
1065     + print $Line2."\n";
1066     + print $Totals."\n";
1067     + print $Percent."\n";
1068     + print $Line1."\n";
1069     +
1070     + if ($localAccepttotal>0) {
1071     + print "*Fetchml* means connections from Fetchmail delivering email\n";
1072     + }
1073     + print "*Local* means connections from workstations on local LAN.\n\n";
1074     + print "*Non\.Conf\.* means sending mailserver did not conform to correct protocol";
1075     + print " or email was to non existant address.\n\n";
1076     +
1077     + if ($finaldisplay[$KarmaCateg]){
1078     + print "*Karma* means email was rejected based on the mailserver's previous activities.\n\n";
1079     + }
1080     +
1081     +
1082     + if ($finaldisplay[$BadCountryCateg]){
1083     + $BadCountries = $cdb->get('qpsmtpd')->prop('BadCountries') || "*none*";
1084     + print "*Geoip\.*:Bad Countries mask is:".$BadCountries."\n\n";
1085     + }
1086     +
1087     +
1088     +
1089     + if (scalar keys %unrecog_plugin > 0){
1090     + #Show unrecog plugins found
1091     + print "*Unrecognised plugins found - categorised as Non-Conf\n";
1092     + foreach my $unrec (keys %unrecog_plugin){
1093     + print "\t$unrec\t($unrecog_plugin{$unrec})\n";
1094     + }
1095     + print "\n";
1096     + }
1097     +
1098     + if ($QueryNoLogTerse) {
1099     + print "* - as no records where found, it looks as though you may not have the *logterse* \nplugin running as part of qpsmtpd \n\n";
1100     +# print " to enable it follow the instructions at .............................\n";
1101     + }
1102     +
1103     +
1104     + if ( !$RHSenabled or !$DNSenabled ) {
1105     +
1106     + # comment about RBL not set
1107     + print
1108     +"* - This means that one or more of the possible spam black listing services\n that are available have not been enabled.\n";
1109     + print " You have not enabled:\n";
1110     +
1111     + if ( !$RHSenabled ) {
1112     + print " RHSBL\n";
1113     + }
1114     +
1115     + if ( !$DNSenabled ) {
1116     + print " DNSBL\n";
1117     + }
1118     +
1119     +
1120     + print " To enable these you can use the following commands:\n";
1121     + if ( !$RHSenabled ) {
1122     + print " config setprop qpsmtpd RHSBL enabled\n";
1123     + }
1124     +
1125     + if ( !$DNSenabled ) {
1126     + print " config setprop qpsmtpd DNSBL enabled\n";
1127     + }
1128     +
1129     + # there so much templates to expand... (PS)
1130     + print " Followed by:\n signal-event email-update and\n sv t /var/service/qpsmtpd\n\n";
1131     + }
1132     +
1133     +# if ($Webmailsendtotal > 0) {print "If you have the mailman contrib installed, then the webmail totals might include some mailman emails\n"}
1134     +
1135     + # time to do a 'by recipient domain' report
1136     + print "Incoming mails by recipient domains usage\n";
1137     + print "-----------------------------------------\n";
1138     + print
1139     + "Domains Type Total Denied XferErr Accept \%accept\n";
1140     + print
1141     + "---------------------------- ---------- ------ ------ ------- ------ -------\n";
1142     + my %total = (
1143     + total => 0,
1144     + deny => 0,
1145     + xfer => 0,
1146     + accept => 0,
1147     + );
1148     + foreach my $domain (
1149     + sort {
1150     + join( "\.", reverse( split /\./, $a ) ) cmp
1151     + join( "\.", reverse( split /\./, $b ) )
1152     + } keys %byrcptdomain
1153     + )
1154     + {
1155     + next if ( ( $byrcptdomain{$domain}{'total'} || 0 ) == 0 );
1156     + my $tp = $byrcptdomain{$domain}{'type'} || 'other';
1157     + my $to = $byrcptdomain{$domain}{'total'} || 0;
1158     + my $de = $byrcptdomain{$domain}{'deny'} || 0;
1159     + my $xr = $byrcptdomain{$domain}{'xfer'} || 0;
1160     + my $ac = $byrcptdomain{$domain}{'accept'} || 0;
1161     + printf "%-28s %-10s %6d %6d %7d %6d %6.2f%%\n", $domain, $tp, $to,
1162     + $de, $xr, $ac, $ac * 100 / $to;
1163     + $total{'total'} += $to;
1164     + $total{'deny'} += $de;
1165     + $total{'xfer'} += $xr;
1166     + $total{'accept'} += $ac;
1167     + }
1168     + print
1169     + "---------------------------- ---------- ------ ------- ------ ------ -------\n";
1170     +
1171     + # $total{ 'total' } can be equal to 0, bad for divisions...
1172     + my $perc1 = 0;
1173     + my $perc2 = 0;
1174     +
1175     +
1176     + if ( $total{'total'} != 0 ) {
1177     + $perc1 = $total{'accept'} * 100 / $total{'total'};
1178     + $perc2 = ( ( $total{'total'} + $morethanonercpt ) / $total{'total'} );
1179     + }
1180     + printf
1181     + "Total %6d %6d %7d %6d %6.2f%%\n\n",
1182     + $total{'total'}, $total{'deny'}, $total{'xfer'}, $total{'accept'},
1183     + $perc1;
1184     + printf
1185     + "%d mails were processed for %d Recipients\nThe average recipients by mail is %4.2f\n\n",
1186     + $total{'total'}, ( $total{'total'} + $morethanonercpt ), $perc2;
1187     +
1188     + if ( $infectedcount > 0 ) {
1189     + show_virus_variants();
1190     + }
1191     +
1192     +
1193     + if ($enableqpsmtpdcodes) {show_qpsmtpd_codes();}
1194     +
1195     + if ($enableSARules) {show_SARules_codes();}
1196     +
1197     + if ($enableGeoiptable and (($total_countries > 0) or $finaldisplay[$BadCountryCateg])){show_Geoip_results();}
1198     +
1199     + if ($enablejunkMailList) {List_Junkmail();}
1200     +
1201     + if ($enableblacklist) {show_blacklist_counts();}
1202     +
1203     + show_user_stats();
1204     +
1205     + print "\nReport generated in $telapsed sec.\n";
1206     +
1207     + if ($savedata) { save_data(); }
1208     + else
1209     + { print "No data saved - if you want to save data to a MySQL database, then please use:\n".
1210     + "config setprop mailstats SaveDataToMySQL yes\n";
1211     + }
1212     +
1213     + select $oldFH;
1214     + close $outputFH;
1215     + if ($makeHTMLemail eq "no" or $makeHTMLemail eq "both") {print $tablestr}
1216     + if ($makeHTMLemail eq "yes" or $makeHTMLemail eq "both" or $makeHTMLpage eq "yes"){
1217     + #Convert text to html and send it
1218     + require CGI;
1219     + require TextToHTML;
1220     + my $cgi = new CGI;
1221     + my $text = $tablestr;
1222     + my %paramhash = (default_link_dict=>'',make_tables=>1,preformat_trigger_lines=>10,tab_width=>20);
1223     + my $conv = new HTML::TextToHTML();
1224     + $conv->args(default_link_dict=>'',make_tables=>1,preformat_trigger_lines=>2,preformat_whitespace_min=>2,
1225     + underline_length_tolerance=>1);
1226     +
1227     + my $html = $cgi->header();
1228     + $html .="<!DOCTYPE html> <html>\n";
1229     + $html .= "<head><title>Mailstats -".strftime( "%F", localtime($start) )."</title>";
1230     + $html .= "<link rel='stylesheet' type='text/css' href='mailstats.css' /></head>\n";
1231     + $html .= "<body>\n";
1232     + $html .= $conv->process_chunk($text);
1233     + $html .= "</body></html>\n";
1234     + if ($makeHTMLemail eq "yes" or $makeHTMLemail eq "both" ) {print $html}
1235     + #And drop it into a file
1236     + if ($makeHTMLpage eq "yes") {
1237     + my $filename = "mailstats.html";
1238     + open(my $fh, '>', $filename) or die "Could not open file '$filename' $!";
1239     + print $fh $html;
1240     + close $fh;
1241     + }
1242     +
1243     + }
1244     +
1245     +
1246     + #Close Sendmail if it was opened
1247     + if ( $opt{'mail'} ) {
1248     + select $oldfh;
1249     + close(SENDMAIL);
1250     + }
1251     +
1252     +} ##report disabled
1253     +
1254     +#All done
1255     +exit 0;
1256     +
1257     +#############################################################################
1258     +# Subroutines ###############################################################
1259     +#############################################################################
1260     +
1261     +
1262     +################################################
1263     +# Determine analysis period (start and end time)
1264     +################################################
1265     +sub analysis_period {
1266     + my $startdate = shift;
1267     + my $enddate = shift;
1268     +
1269     + my $secsininterval = 86400; #daily default
1270     + my $time;
1271     +
1272     + if ($cdb->get('mailstats'))
1273     + {
1274     + my $interval = $cdb->get('mailstats')->prop('Interval') || 'daily'; #"fortnightly"; #"daily";# #; TEMP!!
1275     + if ($interval eq "weekly") {
1276     + $secsininterval = 86400*7;
1277     + } elsif ($interval eq "fortnightly") {
1278     + $secsininterval = 86400*14;
1279     + } elsif ($interval eq "monthly") {
1280     + $secsininterval = 86400*30;
1281     + } elsif ($interval =~m/\d+/) {
1282     + $secsininterval = $interval*3600;
1283     + };
1284     + my $base = $cdb->get('mailstats')->prop('Base') || 'Midnight';
1285     + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
1286     + localtime(time);
1287     + if ($base eq "Midnight"){
1288     + $sec = 0;$min=0;$hour=0;
1289     + } elsif ($base eq "Midday"){
1290     + $sec = 0;$min=0;$hour=12;
1291     + } elsif ($base =~m/\d+/){
1292     + $sec=0;$min=0;$hour=$base;
1293     + };
1294     + #$mday="05"; #$mday="03"; #$mday="16"; #Temp!!
1295     + $time = timelocal($sec,$min,$hour,$mday,$mon,$year);
1296     + }
1297     +
1298     + my $start = str2time( $startdate );
1299     + my $end = $enddate ? str2time( $enddate ) :
1300     + $startdate ? $start + $secsininterval : $time;
1301     + $start = $startdate ? $start : $end - $secsininterval;
1302     + return ( $start > $end ) ? ( $end, $start ) : ( $start, $end );
1303     +}
1304     +
1305     +sub dbg {
1306     + my $msg = shift;
1307     + my $time = scalar localtime;
1308     + $msg = $time.":".$msg."\n";
1309     + if ( $opt{debug} ) {
1310     + print STDERR $msg;
1311     + }
1312     +}
1313     +
1314     +sub List_Junkmail {
1315     +
1316     + #
1317     + # Show how many junkmails in each user's junkmail folder.
1318     + #
1319     + use esmith::AccountsDB;
1320     + my $adb = esmith::AccountsDB->open_ro;
1321     + my $entry;
1322     + foreach my $user ( $adb->users ) {
1323     + my $found = 0;
1324     + my $junkmail_dir =
1325     + "/home/e-smith/files/users/" . $user->key . "/Maildir/.junkmail";
1326     + foreach my $dir (qw(new cur)) {
1327     +
1328     + # Now get the content list for the directory.
1329     + if ( opendir( QDIR, "$junkmail_dir/$dir" ) ) {
1330     + while ( $entry = readdir(QDIR) ) {
1331     + next if $entry =~ /^\./;
1332     + $found++;
1333     + }
1334     + closedir(QDIR);
1335     + }
1336     + }
1337     + if ( $found != 0 ) {
1338     + $junkcount{ $user->key } = $found;
1339     + }
1340     + }
1341     + my $i = keys %junkcount;
1342     + if ( $i > 0 ) {
1343     + print("\nJunk Mails left in folder:\n");
1344     + print("---------------------------\n");
1345     + print("Count\tUser\n");
1346     + print("-------------------------\n");
1347     + foreach my $thisuser (
1348     + sort { $junkcount{$b} <=> $junkcount{$a} }
1349     + keys %junkcount
1350     + )
1351     + {
1352     + printf "%d", $junkcount{$thisuser};
1353     + print "\t" . $thisuser . "\n";
1354     + }
1355     + print("-------------------------\n");
1356     + }
1357     + else {
1358     + print "***No junkmail folders with emails***\n";
1359     + }
1360     +}
1361     +
1362     +sub show_virus_variants
1363     +
1364     +#
1365     +# Show a league table of the different virus types found today
1366     +#
1367     +
1368     +{
1369     + my $line = "------------------------------------------------------------------------\n";
1370     + print("\nVirus Statistics by name:\n");
1371     + print($line);
1372     + foreach my $virus (sort { $found_viruses{$b} <=> $found_viruses{$a} }
1373     + keys %found_viruses)
1374     + {
1375     + if (index($virus,"Sanesecurity") !=-1 or index($virus,"UNOFFICIAL") !=-1){
1376     + print "Rejected $found_viruses{$virus}\thttp://sane.mxuptime.com/s.aspx?id=$virus\n";
1377     + } else {
1378     + print "Rejected $found_viruses{$virus}\t$virus\n";
1379     + }
1380     +
1381     + }
1382     + print($line);
1383     +}
1384     +
1385     +sub show_qpsmtpd_codes
1386     +
1387     +#
1388     +# Show a league table of the qpsmtpd result codes found today
1389     +#
1390     +
1391     +{
1392     + my $line = "---------------------------------------------\n";
1393     + print("\nQpsmtpd codes league table:\n");
1394     + print($line);
1395     + print("Count\tPercent\tReason\n");
1396     + print($line);
1397     + foreach my $qpcode (sort { $found_qpcodes{$b} <=> $found_qpcodes{$a} }
1398     + keys %found_qpcodes)
1399     + {
1400     + print "$found_qpcodes{$qpcode}\t".sprintf('%4.1f',$found_qpcodes{$qpcode}*100/$totalexamined)."%\t\t$qpcode\n" if $totalexamined;
1401     + }
1402     + print($line);
1403     +}
1404     +
1405     +sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
1406     +
1407     +sub get_domain
1408     +{ my $url = shift;
1409     + $url =~ s!^\(dnsbl\)\s!!;
1410     + $url =~ s!^.*https?://(?:www\.)?!!i;
1411     + $url =~ s!/.*!!;
1412     + $url =~ s/[\?\#\:].*//;
1413     + $url =~ s/^([\d]{1,3}.){4}//;
1414     + my $domain = trim($url);
1415     + return $domain;
1416     +}
1417     +
1418     +sub show_blacklist_counts
1419     +
1420     +#
1421     +# Show a sorted league table of the blacklist URL counts
1422     +#
1423     +
1424     +{
1425     + my $line = "------------------\n";
1426     + print("\nBlacklist details:\n");
1427     + print($line);
1428     + if ($cdb->get('qpsmtpd')->prop("RHSBL") eq "enabled") {print "RBLLIST:".$cdb->get('qpsmtpd')->prop("RBLList")."\n";}
1429     + if ($cdb->get('qpsmtpd')->prop("URIBL") eq "enabled") {print "UBLLIST:".$cdb->get('qpsmtpd')->prop("UBLList")."\n";}
1430     + if (!$cdb->get('qpsmtpd')->prop("SBLList") eq "") {print "SBLLIST:".$cdb->get('qpsmtpd')->prop("SBLList")."\n";}
1431     + print($line);
1432     + print("Count\tURL\n");
1433     + print($line);
1434     + foreach my $blcode (sort { $blacklistURL{$b} <=> $blacklistURL{$a} }
1435     + keys %blacklistURL)
1436     + {
1437     + print sprintf('%3u',$blacklistURL{$blcode})."\t$blcode\n";
1438     + }
1439     + print($line);
1440     +}
1441     +
1442     +
1443     +sub show_user_stats
1444     +
1445     +#
1446     +# Show a sorted league table of the user counts
1447     +#
1448     +
1449     +{
1450     + #Compute totals for each entry
1451     + my $grandtotals=0;
1452     + my $totalqueued=0;
1453     + my $totalspam=0;
1454     + my $totalrejected=0;
1455     + foreach my $user (keys %usercounts){
1456     + $usercounts{$user}{"queued"} = 0 if !(exists $usercounts{$user}{"queued"});
1457     + $usercounts{$user}{"rejected"} = 0 if !(exists $usercounts{$user}{"rejected"});
1458     + $usercounts{$user}{"spam"} = 0 if !(exists $usercounts{$user}{"spam"});
1459     + $usercounts{$user}{"totals"} = $usercounts{$user}{"queued"}+$usercounts{$user}{"rejected"}+$usercounts{$user}{"spam"};
1460     + $grandtotals += $usercounts{$user}{"totals"};
1461     + $totalspam += $usercounts{$user}{"spam"};
1462     + $totalqueued += $usercounts{$user}{"queued"};
1463     + $totalrejected += $usercounts{$user}{"rejected"};
1464     + }
1465     + my $line = "--------------------------------------------------\n";
1466     + print("\nStatistics by email address received:\n");
1467     + print($line);
1468     + print("Queued\tRejected\tSpam tagged\tEmail Address\n");
1469     + print($line);
1470     + foreach my $user (sort { $usercounts{$b}{"totals"} <=> $usercounts{$a}{"totals"} }
1471     + keys %usercounts)
1472     + {
1473     + print sprintf('%3u',$usercounts{$user}{"queued"})."\t".sprintf('%3u',$usercounts{$user}{"rejected"})."\t\t".sprintf('%3u',$usercounts{$user}{"spam"})."\t\t$user\n";
1474     + }
1475     + print($line);
1476     + print sprintf('%3u',$totalqueued)."\t".sprintf('%3u',$totalrejected)."\t\t".sprintf('%3u',$totalspam)."\n";
1477     + print($line);
1478     +
1479     +
1480     +}
1481     +
1482     +sub show_Geoip_results
1483     +#
1484     +# Show league table of GEoip results
1485     +#
1486     +{
1487     +
1488     + my ($percentthreshold);
1489     + my ($reject);
1490     + my ($percent);
1491     + my ($totalpercent)=0;
1492     + if ($cdb->get('mailstats')){
1493     + $percentthreshold = $cdb->get('mailstats')->prop("GeoipCutoffPercent") || 0.5;
1494     + } else {
1495     + $percentthreshold = 0.5;
1496     + }
1497     + if ($total_countries > 0) {
1498     + my $line = "---------------------------------------------\n";
1499     + print("\nGeoip results: (cutoff at $percentthreshold%) \n");
1500     + print($line);
1501     + print("Country\tPercent\tCount\tRejected?\n");
1502     + print($line);
1503     + foreach my $country (sort { $found_countries{$b} <=> $found_countries{$a} }
1504     + keys %found_countries)
1505     + {
1506     + $percent = $found_countries{$country} * 100 / $total_countries
1507     + if $total_countries;
1508     + $totalpercent = $totalpercent + $percent;
1509     + if (index($BadCountries, $country) != -1) {$reject = "*";} else { $reject = " ";}
1510     + if ( $percent >= $percentthreshold ) {
1511     + print "$country\t\t"
1512     + . sprintf( '%4.1f', $percent )
1513     + . "%\t\t$found_countries{$country}","\t$reject\n"
1514     + if $total_countries;
1515     + }
1516     +
1517     + }
1518     + print($line);
1519     + my ($showtotals);
1520     + if ($cdb->get('mailstats')){
1521     + $showtotals = ((($cdb->get('mailstats')->prop("ShowLeagueTotals")|| 'yes')) eq "yes");
1522     + } else {
1523     + $showtotals = $true;
1524     + }
1525     +
1526     + if ($showtotals){
1527     + print "TOTALS\t\t".sprintf("%4.1f",$totalpercent)."%\t\t$total_countries\n";
1528     + print($line);
1529     + }
1530     + }
1531     +}
1532     +
1533     +sub show_SARules_codes
1534     +
1535     +#
1536     +# Show a league table of the SARules result codes found today
1537     +# suppress any lower than DB mailstats/SARulePercentThreshold
1538     +#
1539     +
1540     +{
1541     + my ($percentthreshold);
1542     + my ($defaultpercentthreshold);
1543     + my ($totalpercent) = 0;
1544     +
1545     + if ($sum_SARules > 0){
1546     +
1547     + if ($totalexamined >0 and $sum_SARules*100/$totalexamined > $SARulethresholdPercent) {
1548     + $defaultpercentthreshold = $maxcutoff
1549     + } else {
1550     + $defaultpercentthreshold = $mincutoff
1551     + }
1552     + if ($cdb->get('mailstats')){
1553     + $percentthreshold = $cdb->get('mailstats')->prop("SARulePercentThreshold") || $defaultpercentthreshold;
1554     + } else {
1555     + $percentthreshold = $defaultpercentthreshold
1556     + }
1557     + my $line = "---------------------------------------------\n";
1558     + print("\nSpamassassin Rules:(cutoff at ".sprintf('%4.1f',$percentthreshold)."%)\n");
1559     + print($line);
1560     + print("Count\tPercent\tScore\t\t\n");
1561     + print($line);
1562     + foreach my $SARule (sort { $found_SARules{$b}{'count'} <=> $found_SARules{$a}{'count'} }
1563     + keys %found_SARules)
1564     + {
1565     + my $percent = $found_SARules{$SARule}{'count'} * 100 / $totalexamined if $totalexamined;
1566     + my $avehits = $found_SARules{$SARule}{'totalhits'} /
1567     + $found_SARules{$SARule}{'count'}
1568     + if $found_SARules{$SARule}{'count'};
1569     + if ( $percent >= $percentthreshold ) {
1570     + print "$found_SARules{$SARule}{'count'}\t"
1571     + . sprintf( '%4.1f', $percent ) . "%\t"
1572     + . sprintf( '%4.1f', $avehits )
1573     + . "\t$SARule\n"
1574     + if $totalexamined;
1575     + }
1576     + }
1577     + print($line);
1578     + my ($showtotals);
1579     + if ($cdb->get('mailstats')){
1580     + $showtotals = ((($cdb->get('mailstats')->prop("ShowLeagueTotals")|| 'yes')) eq "yes");
1581     + } else {
1582     + $showtotals = $true;
1583     + }
1584     +
1585     + if ($showtotals){
1586     + print "$totalexamined\t(TOTALS)\n";
1587     + print($line);
1588     + }
1589     + print "\n";
1590     + }
1591     +
1592     +
1593     +}
1594     +
1595     +sub mark_domain_rejected
1596     +
1597     +#
1598     +# Tag domain as having a rejected email
1599     +#
1600     +{
1601     +my ($proc) = @_;
1602     +if ( ( $currentrcptdomain{ $proc } || '' ) ne '' ) {
1603     + $byrcptdomain{ $currentrcptdomain{ $proc } }{ 'deny' }++ ;
1604     + $currentrcptdomain{ $proc } = '' ;
1605     + }
1606     +}
1607     +
1608     +sub mark_domain_err
1609     +
1610     + #
1611     + # Tag domain as having an error on email transfer
1612     + #
1613     +{
1614     + my ($proc) = @_;
1615     + if ( ( $currentrcptdomain{$proc} || '' ) ne '' ) {
1616     + $byrcptdomain{ $currentrcptdomain{$proc} }{'xfer'}++;
1617     + $currentrcptdomain{$proc} = '';
1618     + }
1619     +}
1620     +
1621     +sub add_in_domain
1622     +
1623     + #
1624     + # add recipient domain into hash
1625     + #
1626     +{
1627     + my ($proc) = @_;
1628     +
1629     + #split to just domain bit.
1630     + $currentrcptdomain{$proc} =~ s/.*@//;
1631     + $currentrcptdomain{$proc} =~ s/[^\w\-\.]//g;
1632     + $currentrcptdomain{$proc} =~ s/>//g;
1633     + my $NotableDomain = 0;
1634     + if ( defined( $byrcptdomain{ $currentrcptdomain{$proc} }{'type'} ) ) {
1635     + $NotableDomain = 1;
1636     + }
1637     + else {
1638     + foreach (@extdomain) {
1639     + if ( $currentrcptdomain{$proc} =~ m/$_$/ ) {
1640     + $NotableDomain = 1;
1641     + last;
1642     + }
1643     + }
1644     + }
1645     + if ( !$NotableDomain ) {
1646     +
1647     + # check for outgoing email
1648     + if ( $localflag == 1 ) { $currentrcptdomain{$proc} = 'Outgoing' }
1649     + else { $currentrcptdomain{$proc} = 'Others' }
1650     + }
1651     + else {
1652     + if ( $localflag == 1 ) { $currentrcptdomain{$proc} = 'Internal' }
1653     + }
1654     + $byrcptdomain{ $currentrcptdomain{$proc} }{'total'}++;
1655     +}
1656     +
1657     +sub save_data
1658     +
1659     + #
1660     + # Save the data to a MySQL database
1661     + #
1662     +{
1663     + use DBI;
1664     + my $tstart = time;
1665     + my $DBname = "mailstats";
1666     + my $host = esmith::ConfigDB->open_ro->get('mailstats')->prop('DBHost') || "localhost";
1667     + my $port = esmith::ConfigDB->open_ro->get('mailstats')->prop('DBPort') || "3306";
1668     + print "Saving data..";
1669     + my $dbh = DBI->connect( "DBI:mysql:database=$DBname;host=$host;port=$port",
1670     + "mailstats", "mailstats" )
1671     + or die "Cannot open mailstats db - has it beeen created?";
1672     +
1673     + my $hour = floor( $start / 3600 );
1674     + my $reportdate = strftime( "%F", localtime( $hour * 3600 ) );
1675     + my $dateid = get_dateid($dbh,$reportdate);
1676     + my $reccount = 0; #count number of records written
1677     + my $servername = esmith::ConfigDB->open_ro->get('SystemName')->value . "."
1678     + . esmith::ConfigDB->open_ro->get('DomainName')->value;
1679     + # now fill in day related stats - must always check for it already there
1680     + # incase the module is run more than once in a day
1681     + my $SAScoresid = check_date_rec($dbh,"SAscores",$dateid,$servername);
1682     + $dbh->do( "UPDATE SAscores SET ".
1683     + "acceptedcount=".$spamcount.
1684     + ",rejectedcount=".$above15.
1685     + ",hamcount=".$hamcount.
1686     + ",acceptedscore=".$spamhits.
1687     + ",rejectedscore=".$rejectspamhits.
1688     + ",hamscore=".$hamhits.
1689     + ",totalsmtp=".$totalexamined.
1690     + ",totalrecip=".$recipcount.
1691     + ",servername='".$servername.
1692     + "' WHERE SAscoresid =".$SAScoresid);
1693     + # Junkmail stats
1694     + # delete if already there
1695     + $dbh->do("DELETE from JunkMailStats WHERE dateid = ".$dateid." AND servername='".$servername."'");
1696     + # and add records
1697     + foreach my $thisuser (keys %junkcount){
1698     + $dbh->do("INSERT INTO JunkMailStats (dateid,user,count,servername) VALUES ('".
1699     + $dateid."','".$thisuser."','".$junkcount{$thisuser}."','".$servername."')");
1700     + $reccount++;
1701     + }
1702     + #SA rules - delete any first
1703     + $dbh->do("DELETE from SARules WHERE dateid = ".$dateid." AND servername='".$servername."'");
1704     + # and add records
1705     + foreach my $thisrule (keys %found_SARules){
1706     + $dbh->do("INSERT INTO SARules (dateid,rule,count,totalhits,servername) VALUES ('".
1707     + $dateid."','".$thisrule."','".$found_SARules{$thisrule}{'count'}."','".
1708     + $found_SARules{$thisrule}{'totalhits'}."','".$servername."')");
1709     + $reccount++;
1710     + }
1711     + #qpsmtpd result codes
1712     + $dbh->do("DELETE from qpsmtpdcodes WHERE dateid = ".$dateid." AND servername='".$servername."'");
1713     + # and add records
1714     + foreach my $thiscode (keys %found_qpcodes){
1715     + $dbh->do("INSERT INTO qpsmtpdcodes (dateid,reason,count,servername) VALUES ('".
1716     + $dateid."','".$thiscode."','".$found_qpcodes{$thiscode}."','".$servername."')");
1717     + $reccount++;
1718     +}
1719     + # virus stats
1720     + $dbh->do("DELETE from VirusStats WHERE dateid = ".$dateid." AND servername='".$servername."'");
1721     + # and add records
1722     + foreach my $thisvirus (keys %found_viruses){
1723     + $dbh->do("INSERT INTO VirusStats (dateid,descr,count,servername) VALUES ('".
1724     + $dateid."','".$thisvirus."','".$found_viruses{$thisvirus}."','".$servername."')");
1725     + $reccount++;
1726     +
1727     + }
1728     + # domain details
1729     + $dbh->do("DELETE from domains WHERE dateid = ".$dateid." AND servername='".$servername."'");
1730     + # and add records
1731     + foreach my $domain (keys %byrcptdomain){
1732     + next if ( ( $byrcptdomain{$domain}{'total'} || 0 ) == 0 );
1733     + $dbh->do("INSERT INTO domains (dateid,domain,type,total,denied,xfererr,accept,servername) VALUES ('".
1734     + $dateid."','".$domain."','".($byrcptdomain{$domain}{'type'}||'other')."','"
1735     + .$byrcptdomain{$domain}{'total'}."','"
1736     + .($byrcptdomain{$domain}{'deny'}||0)."','"
1737     + .($byrcptdomain{$domain}{'xfer'}||0)."','"
1738     + .($byrcptdomain{$domain}{'accept'}||0)."','"
1739     + .$servername
1740     + ."')");
1741     + $reccount++;
1742     +
1743     + }
1744     + # finally - the hourly breakdown
1745     + # need to remember here that the date might change during the 24 hour span
1746     + my $nhour = floor( $start / 3600 );
1747     + my $ncateg;
1748     + while ( $nhour < $end / 3600 ) {
1749     + #see if the time record has been created
1750     + # print strftime("%H",localtime( $nhour * 3600 ) ).":00:00\n";
1751     + my $sth =
1752     + $dbh->prepare( "SELECT timeid FROM time WHERE time = '" . strftime("%H",localtime( $nhour * 3600 ) ).":00:00'");
1753     + $sth->execute();
1754     + if ( $sth->rows == 0 ) {
1755     + #create entry
1756     + $dbh->do( "INSERT INTO time (time) VALUES ('" .strftime("%H",localtime( $nhour * 3600 ) ).":00:00')" );
1757     + # and pick up timeid
1758     + $sth = $dbh->prepare("SELECT last_insert_id() AS timeid FROM time");
1759     + $sth->execute();
1760     + $reccount++;
1761     + }
1762     + my $timerec = $sth->fetchrow_hashref();
1763     + my $timeid = $timerec->{"timeid"};
1764     + $ncateg = 0;
1765     + # and extract date from first column of $count array
1766     + my $currentdate = strftime( "%F", localtime( $hour * 3600 ) );
1767     + # print "$currentdate.\n";
1768     + if ($currentdate ne $reportdate) {
1769     + #same as before?
1770     + $dateid = get_dateid($dbh,$currentdate);
1771     + $reportdate = $currentdate;
1772     + }
1773     + # delete for this date and time
1774     + $dbh->do("DELETE from ColumnStats WHERE dateid = ".$dateid." AND timeid = ".$timeid." AND servername='".$servername."'");
1775     + while ( $ncateg < @categs-1 ) {
1776     + # then add in each entry
1777     + if (($counts{$nhour}{$categs[$ncateg]} || 0) != 0) {
1778     + $dbh->do("INSERT INTO ColumnStats (dateid,timeid,descr,count,servername) VALUES ("
1779     + .$dateid.",".$timeid.",'".$categs[$ncateg]."',"
1780     + .$counts{$nhour}{$categs[$ncateg]}.",'".$servername."')");
1781     + $reccount++;
1782     + }
1783     +
1784     +# print("INSERT INTO ColumnStats (dateid,timeid,descr,count) VALUES ("
1785     +# .$dateid.",".$timeid.",'".$categs[$ncateg]."',"
1786     +# .$counts{$nhour}{$categs[$ncateg]}.")\n");
1787     +
1788     + $ncateg++;
1789     + }
1790     + $nhour++;
1791     + }
1792     + # and write out the log lines saved - only if html wanted
1793     + if ($makeHTMLemail eq 'yes' or $makeHTMLemail eq 'both' or $makeHTMLpage eq 'yes'){
1794     + foreach my $logid (keys %LogLines){
1795     + $reccount++;
1796     + #Extract from keys
1797     + my $extract = $logid;
1798     + $extract =~/^(.*)-(.*):(.*)$/;
1799     + my $Log64n = $1;
1800     + my $LogMailId = $2;
1801     + my $LogSeq = $3;
1802     + my $LogLine = $dbh->quote($LogLines{$logid});
1803     + my $sql = "INSERT INTO LogData (Log64n,MailID,Sequence,LogStr) VALUES ('";
1804     + $sql .= $Log64n."','".$LogMailId."','".$LogSeq."',".$LogLine.")";
1805     + $dbh->do($sql) or die($sql);
1806     + }
1807     + $dbh->disconnect();
1808     + $telapsed = time - $tstart;
1809     + print "Saved $reccount records in $telapsed sec.";
1810     + }
1811     +}
1812     +
1813     +sub check_date_rec
1814     +
1815     + #
1816     + # check that a specific dated rec is there, create if not
1817     + #
1818     +{
1819     + my ( $dbh, $table, $dateid ) = @_;
1820     + my $sth =
1821     + $dbh->prepare(
1822     + "SELECT " . $table . "id FROM ".$table." WHERE dateid = '$dateid'" );
1823     + $sth->execute();
1824     + if ( $sth->rows == 0 ) {
1825     + #create entry
1826     + $dbh->do( "INSERT INTO ".$table." (dateid) VALUES ('" . $dateid . "')" );
1827     + # and pick up recordid
1828     + $sth = $dbh->prepare("SELECT last_insert_id() AS ".$table."id FROM ".$table);
1829     + $sth->execute();
1830     + }
1831     + my $rec = $sth->fetchrow_hashref();
1832     + $rec->{$table."id"}; #return the id of the reocrd (new or not)
1833     + }
1834     +
1835     + sub check_time_rec
1836     +
1837     + #
1838     + # check that a specific dated amd timed rec is there, create if not
1839     + #
1840     +{
1841     + my ( $dbh, $table, $dateid, $timeid ) = @_;
1842     + my $sth =
1843     + $dbh->prepare(
1844     + "SELECT " . $table . "id FROM ".$table." WHERE dateid = '$dateid' AND timeid = ".$timeid );
1845     + $sth->execute();
1846     + if ( $sth->rows == 0 ) {
1847     + #create entry
1848     + $dbh->do( "INSERT INTO ".$table." (dateid,timeid) VALUES ('" . $dateid . "', '".$timeid."')" );
1849     + # and pick up recordid
1850     + $sth = $dbh->prepare("SELECT last_insert_id() AS ".$table."id FROM ".$table);
1851     + $sth->execute();
1852     + }
1853     + my $rec = $sth->fetchrow_hashref();
1854     + $rec->{$table."id"}; #return the id of the record (new or not)
1855     + }
1856     +
1857     +sub get_dateid
1858     +
1859     +#
1860     +# Check that date is in db, and return corresponding id
1861     +#
1862     +{
1863     + my ($dbh,$reportdate) = @_;
1864     + my $sth =
1865     + $dbh->prepare( "SELECT dateid FROM date WHERE date = '" . $reportdate."'" );
1866     + $sth->execute();
1867     + if ( $sth->rows == 0 ) {
1868     + #create entry
1869     + $dbh->do( "INSERT INTO date (date) VALUES ('" . $reportdate . "')" );
1870     + # and pick up dateid
1871     + $sth = $dbh->prepare("SELECT last_insert_id() AS dateid FROM date");
1872     + $sth->execute();
1873     + }
1874     + my $daterec = $sth->fetchrow_hashref();
1875     + $daterec->{"dateid"};
1876     + }
1877     +
1878     + sub dump_entries
1879     + {
1880     + my $msg = shift;
1881     + #if ($opt{debug} == 1){exit;}
1882     +}
1883     +
1884     +#sub test_for_private_ip {
1885     + #use NetAddr::IP;
1886     + #my $ip = shift;
1887     + #$ip =~ s/^\D*(([0-9]{1,3}\.){3}[0-9]{1,3}).*/$1/e;
1888     + #print "\nIP:$ip";
1889     + #my $nip = NetAddr::IP->new($ip);
1890     + #if ($nip){
1891     + #if ( $nip->is_rfc1918() ){
1892     + #return 1;
1893     + #} else { return 0}
1894     + #} else { return 0}
1895     +#}
1896     +
1897     +
1898     +sub test_for_private_ip {
1899     + use NetAddr::IP;
1900     + $_ = shift;
1901     + return unless /(\d+\.\d+\.\d+\.\d+)/;
1902     + my $ip = NetAddr::IP->new($1);
1903     + return unless $ip;
1904     + return $ip->is_rfc1918();
1905     +}
1906     +
1907     +
1908     diff -urN smeserver-mailstats-1.1.old/root/usr/bin/spamfilter-stats-7.pl smeserver-mailstats-1.1/root/usr/bin/spamfilter-stats-7.pl
1909     --- smeserver-mailstats-1.1.old/root/usr/bin/spamfilter-stats-7.pl 2020-01-03 09:09:24.568846981 +0000
1910     +++ smeserver-mailstats-1.1/root/usr/bin/spamfilter-stats-7.pl 1970-01-01 01:00:00.000000000 +0100
1911     @@ -1,1963 +0,0 @@
1912     -#!/usr/bin/perl -w
1913     -
1914     -#############################################################################
1915     -#
1916     -# This script provides daily SpamFilter statistics.
1917     -#
1918     -# This script was originally developed
1919     -# by Jesper Knudsen at http://sme.swerts-knudsen.dk
1920     -# and re-written by brian read at bjsystems.co.uk (with some help from the community - thanks guys)
1921     -#
1922     -# bjr - 02sept12 - Add in qpsmtpd failure code auth::auth_cvm_unix_local as per Bug 7089
1923     -# bjr - 10Jun15 - Sort out multiple files as input parameters as per bug 5613
1924     -# - Sort out geoip failure status as per Bug 4262
1925     -# - change final message about the DB (it is created automatically these days by the rpm)
1926     -# bjr - 17Jun15 - Add annotation showing Badcountries being eliminated
1927     -# - correct Spamfilter details extract, as per Bug 8656
1928     -# - Add analysis table of Geoip results
1929     -# bjr - 19Jun15 - Add totals for the League tables
1930     -# bjr and Unnilennium - 08Apr16 - Add in else for unrecognised plugin detection
1931     -# bjr - 08Apr16 - Add in link for SaneSecurity "extra" virus detection
1932     -# bjr - 14Jun16 - make compatible with qpsmtpd 0.96
1933     -# bjr - 16Jun16 - Add code to create an html equivalent of the text email (v0.7)
1934     -# bjr - 04Aug16 - Add code to log and count the blacklist RBL urls that have triggered, this (NFR) is Bugzilla 9717
1935     -# bjr - 04Aug16 - Add code to expand the junkmail table to include daily ham and spam and deleted spam for each user - (NFR bugzilla 9716)
1936     -# bjr - 05Aug16 - Add code to log remote relay incoming emails
1937     -# bjr - 10Oct16 - Add code to show stats for the smeoptimizer package
1938     -# bjr - 16dec16 - Fix dnsbl code to deal with psbl.surriel.com - Bug 9717
1939     -# bjr - 16Dec16 - Change geopip table code to show even if no exclusions found (assuming geoip data found) - Bug 9888
1940     -# bjr - 30Apr17 - Change Categ index code - Bug 9888 again
1941     -#
1942     -#############################################################################
1943     -#
1944     -# SMEServer DB usage
1945     -# ------------------
1946     -#
1947     -# mailstats / Status ("enabled"|"disabled")
1948     -# / <column header> ("yes"|"no"|"auto") - enable, supress or only show if nonzero
1949     -# / QpsmtpdCodes ("enabled"|"disabled")
1950     -# / SARules ("enabled"|"disabled")
1951     -# / GeoipTable ("enabled"|"disabled")
1952     -# / GeoipCutoffPercent (0.5%) - threshold to show Geoip country in league table
1953     -# / JunkMailList ("enabled"|"disabled")
1954     -# / SARulePercentThreshold (0.5) - threshold of SArules percentage for report cutoff
1955     -# / Email (admin) - email to send report
1956     -# / SaveDataToMySQL - save data to MySQL database (default is "no")
1957     -# / ShowLeagueTotals - Show totals row after league tables - (default is "yes")
1958     -# / DBHost - MySQL server hostname (default is "localhost").
1959     -# / DBPort - MySQL server post (default is "3306")
1960     -# / Interval - "daily", "weekly", "fortnightly", "monthly", "99999" - last is number of hours (default is daily)
1961     -# / Base - "Midnight", "Midday", "Now", "99" hour (0-23) (default is midnight)
1962     -# / HTMLEmail - "yes", "no", "both" - default is "No" - Send email in HTML
1963     -# / HTMLPage - "yes" / "no" - default is "yes" if HTMLEmail is "yes" or "both" otherwise "no"
1964     -#
1965     -#############################################################################
1966     -#
1967     -#
1968     -# TODO
1969     -#
1970     -# 1. Delete loglines records from any previous run of same table
1971     -# 2. Add tracking LogId for each cont in the table
1972     -# 3. Use link directory file to generate h1 / h2 tags for title and section headings
1973     -# 4. Ditto for links to underlying data
1974     -#
1975     -
1976     -# internal modules (part of core perl distribution)
1977     -use strict;
1978     -use warnings;
1979     -use Getopt::Long;
1980     -use Pod::Usage;
1981     -use POSIX qw/strftime floor/;
1982     -use Time::Local;
1983     -use Date::Parse;
1984     -use Time::TAI64;
1985     -use esmith::ConfigDB;
1986     -use esmith::DomainsDB;
1987     -use Sys::Hostname;
1988     -use Switch;
1989     -use DBIx::Simple;
1990     -use URI::URL;
1991     -
1992     -#use CGI;
1993     -#use HTML::TextToHTML;
1994     -
1995     -my $hostname = hostname();
1996     -my $cdb = esmith::ConfigDB->open_ro or die "Couldn't open ConfigDB : $!\n";
1997     -
1998     -my $true = 1;
1999     -my $false = 0;
2000     -#and see if mailstats are disabled
2001     -my $disabled;
2002     -if ($cdb->get('mailstats')){
2003     - $disabled = !(($cdb->get('mailstats')->prop('Status') || 'enabled') eq 'enabled');
2004     -} else {
2005     - my $db = esmith::ConfigDB->open; my $record = $db->new_record('mailstats', { type => 'report', Status => 'enabled', Email => 'admin' });
2006     - $cdb = esmith::ConfigDB->open_ro or die "Couldn't open ConfigDB : $!\n"; #Open up again to pick up new record
2007     - $disabled = $false;
2008     -}
2009     -
2010     -#Configuration section
2011     -my %opt = (
2012     - version => '0.7.12', # please update at each change.
2013     - debug => 0, # guess what ?
2014     - sendmail => '/usr/sbin/sendmail', # Path to sendmail stub
2015     - from => 'spamfilter-stats', # Who is the mail from
2016     - mail => $cdb->get('mailstats')->prop('Email') || 'admin', # mailstats email recipient
2017     - timezone => `date +%z`,
2018     -);
2019     -
2020     -my $FetchmailIP = '127.0.0.200'; #Apparent Ip address of fetchmail deliveries
2021     -my $WebmailIP = '127.0.0.1'; #Apparent Ip of Webmail sender
2022     -my $localhost = 'localhost'; #Apparent sender for webmail
2023     -my $FETCHMAIL = 'FETCHMAIL'; #Sender from fetchmail when Ip address not 127.0.0.200 - when qpsmtpd denies the email
2024     -my $MAILMAN = "bounces"; #sender when mailman sending when orig is localhost
2025     -my $DMARCDomain="dmarc"; #Pattern to recognised DMARC sent emails (this not very reliable, as the email address could be anything)
2026     -my $DMARCOkPattern="dmarc: pass"; #Pattern to use to detect DMARC approval
2027     -my $localIPregexp = ".*((127\.)|(10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(192\.168\.)).*";
2028     -my $MinCol = 6; #Minimum column width
2029     -my $HourColWidth = 16; #Date and time column width
2030     -
2031     -my $SARulethresholdPercent = 10; #If Sa rules less than this of total emails, then cutoff reduced
2032     -my $maxcutoff = 1; #max percent cutoff applied
2033     -my $mincutoff = 0.2; #min percent cutoff applied
2034     -
2035     -my $tstart = time;
2036     -
2037     -#Local variables
2038     -my $YEAR = ( localtime(time) )[5]; # this is years since 1900
2039     -
2040     -my $total = 0;
2041     -my $spamcount = 0;
2042     -my $spamavg = 0;
2043     -my $spamhits = 0;
2044     -my $hamcount = 0;
2045     -my $hamavg = 0;
2046     -my $hamhits = 0;
2047     -my $rejectspamavg = 0;
2048     -my $rejectspamhits= 0;
2049     -
2050     -my $Accepttotal = 0;
2051     -my $localAccepttotal = 0; #Fetchmail connections
2052     -my $localsendtotal = 0; #Connections from local PCs
2053     -my $totalexamined = 0; #total download + RBL etc
2054     -my $WebMailsendtotal = 0; #total from Webmail
2055     -my $mailmansendcount = 0; #total from mailman
2056     -my $DMARCSendCount = 0; #total DMARC reporting emails sent (approx)
2057     -my $DMARCOkCount = 0; #Total emails approved through DMARC
2058     -
2059     -
2060     -
2061     -my %found_viruses = ();
2062     -my %found_qpcodes = ();
2063     -my %found_SARules = ();
2064     -my %junkcount = ();
2065     -my %unrecog_plugin = ();
2066     -my %blacklistURL = (); #Count of use of each balcklist rhsbl
2067     -my %usercounts = (); #Count per received email of sucessful delivery, queued spam and deleted Spam, and rejected
2068     -
2069     -# replaced by...
2070     -my %counts = (); #Hold all counts in 2-D matrix
2071     -my @display = (); #used to switch on and off columns - yes, no or auto for each category
2072     -my @colwidth = (); #width of each column
2073     - #(auto means only if non zero) - populated from possible db entries
2074     -my @finaldisplay = (); #final decision on display or not - true or false
2075     -
2076     -#count column names, used for headings - also used for DB mailstats property names
2077     -my $CATHOUR='Hour';
2078     -my $CATFETCHMAIL='Fetchmail';
2079     -my $CATWEBMAIL='WebMail';
2080     -my $CATMAILMAN='Mailman';
2081     -my $CATLOCAL='Local';
2082     -my $CATRELAY="Relay";
2083     -# border between where it came from and where it ended..
2084     -my $countfromhere = 6; #Temp - Check this not moved!!
2085     -
2086     -my $CATVIRUS='Virus';
2087     -my $CATRBLDNS='RBL/DNS';
2088     -my $CATEXECUT='Execut.';
2089     -my $CATNONCONF='Non.Conf.';
2090     -my $CATBADCOUNTRIES='Geoip.';
2091     -my $CATKARMA="Karma";
2092     -
2093     -my $CATSPAMDEL='Del.Spam';
2094     -my $CATSPAM='Qued.Spam?';
2095     -my $CATHAM='Ham';
2096     -my $CATTOTALS='TOTALS';
2097     -my $CATPERCENT='PERCENT';
2098     -my $CATDMARC="DMARC Rej.";
2099     -my $CATLOAD="Rej.Load";
2100     -my @categs = ($CATHOUR,$CATFETCHMAIL,$CATWEBMAIL,$CATMAILMAN,$CATLOCAL,$CATRELAY,$CATDMARC,$CATVIRUS,$CATRBLDNS,$CATEXECUT,$CATBADCOUNTRIES,$CATNONCONF,$CATLOAD,$CATKARMA,$CATSPAMDEL,$CATSPAM,$CATHAM,$CATTOTALS,$CATPERCENT);
2101     -my $GRANDTOTAL = '99'; #subs for count arrays, for grand total
2102     -my $PERCENT = '98'; # for column percentages
2103     -
2104     -my $categlen = @categs-2; #-2 to avoid the total and percent column
2105     -
2106     -#
2107     -# Index for certain columns - check these do not move if we add columns
2108     -#
2109     -#my $BadCountryCateg=9;
2110     -#my $DMARCcateg = 5; #Not used.
2111     -#my $KarmaCateg=$BadCountryCateg+3;
2112     -
2113     -my %categindex;
2114     -@categindex{@categs} = (0..$#categs);
2115     -my $BadCountryCateg=$categindex{$CATBADCOUNTRIES};
2116     -my $DMARCcateg = $categindex{$CATDMARC}; #Not used.
2117     -my $KarmaCateg=$categindex{$CATKARMA};
2118     -
2119     -my $above15 = 0;
2120     -my $RBLcount = 0;
2121     -my $MiscDenyCount = 0;
2122     -my $PatternFilterCount = 0;
2123     -my $noninfectedcount = 0;
2124     -my $okemailcount = 0;
2125     -my $infectedcount = 0;
2126     -my $warnnoreject = " ";
2127     -my $rblnotset = ' ';
2128     -
2129     -my %found_countries = ();
2130     -my $total_countries = 0;
2131     -my $BadCountries = ""; #From the DB
2132     -
2133     -my $FS = "\t"; # field separator used by logterse plugin
2134     -my %log_items = ( "", "", "", "", "", "", "", "" );
2135     -my $score;
2136     -my %timestamp_items = ();
2137     -my $localflag = 0; #indicate if current email is local or not
2138     -my $WebMailflag = 0; #indicate if current mail is send from webmail
2139     -
2140     -# some storage for by recipient domains stats (PS)
2141     -# my bad : I have to deal with multiple simoultaneous connections
2142     -# will play with the process number.
2143     -# my $currentrcptdomain = '' ;
2144     -my %currentrcptdomain ; # temporay store the recipient domain until end of mail processing
2145     -my %byrcptdomain ; # Store 'by domains stats'
2146     -my @extdomain ; # only useful in some MX-Backup case, when any subdomains are allowed
2147     -my $morethanonercpt = 0 ; # count every 'second' recipients for a mail.
2148     -my $recipcount = 0; # count every recipient email address received.
2149     -
2150     -#
2151     -#Load up the emails curreently stored for DMARC reporting - so that we cna spot the reports being sent.
2152     -#Held in an slqite db, created by the DMARC perl lib.
2153     -#
2154     -my $dsn = "dbi:SQLite:dbname=/var/lib/qpsmtpd/dmarc/reports.sqlite"; #Taken from /etc/mail-dmarc.ini
2155     -# doesn't seem to need
2156     -my $user = "";
2157     -my $pass = "";
2158     -my $DMARC_Report_emails = ""; #Flat string of all email addresses
2159     -
2160     - if (my $dbix = DBIx::Simple->connect( $dsn, $user, $pass )){
2161     - my $result = $dbix->query("select rua from report_policy_published;");
2162     - $result->bind(my ($emailaddress));
2163     - while ($result->fetch){
2164     - #print STDERR "$emailaddress";
2165     - #remember email from logterse entry has chevrons round it - so we add them here to guarantee the alighment of the match
2166     - #Remove the mailto:
2167     - $emailaddress =~ s/mailto://g;
2168     - # and map any commas to ><
2169     - $emailaddress =~ s/,/></g;
2170     - $DMARC_Report_emails .= "<".$emailaddress.">\n"
2171     - }
2172     - $dbix->disconnect();
2173     - } else { $DMARC_Report_emails = "None found - DB not opened"}
2174     -
2175     -
2176     -#dbg("DMARC-EMAILS:".$DMARC_Report_emails);
2177     -
2178     -# and setup list of local domains for spotting the local one in a list of email addresses (Remote station processing)
2179     -use esmith::DomainsDB;
2180     -my $d = esmith::DomainsDB->open_ro();
2181     -my @domains = $d->keys();
2182     -my $alldomains = "(";
2183     -foreach my $dom (@domains){$alldomains .= $dom."|"}
2184     -$alldomains .= ")";
2185     -#print $alldomains;
2186     -
2187     -# Saving the Log lines processed
2188     -my %LogLines = (); #Save all the log lines processed for writing to the DB
2189     -my %LogId = (); #Save the Log Ids.
2190     -my $CurrentLogId = "";
2191     -my $Sequence = 0;
2192     -
2193     -
2194     -# store the domain of interest. Every other records are stored in a 'Other' zone
2195     -my $ddb = esmith::DomainsDB->open_ro or die "Couldn't open DomainsDB : $!\n";
2196     -
2197     -foreach my $domain( $ddb->get_all_by_prop( type => "domain" ) ) {
2198     - $byrcptdomain{ $domain->key }{ 'type' }='local';
2199     -}
2200     -$byrcptdomain{ $cdb->get('SystemName')->value . "."
2201     - . $cdb->get('DomainName')->value }{ 'type' } = 'local';
2202     -
2203     -# is this system a MX-Backup ?
2204     -if ($cdb->get('mxbackup')){
2205     - if ( ( $cdb->get('mxbackup')->prop('status') || 'disabled' ) eq 'enabled' ) {
2206     - my %MXValues = split( /,/, ( $cdb->get('mxbackup')->prop('name') || '' ) ) ;
2207     - foreach my $data ( keys %MXValues ) {
2208     - $byrcptdomain{ $data }{ 'type' } = "mxbackup-$MXValues{ $data }" ;
2209     - if ( $MXValues{ $data } == 1 ) { # subdomains allowed, must take care of this
2210     - push @extdomain, $data ;
2211     - }
2212     - }
2213     - }
2214     -}
2215     -
2216     -my ( $start, $end ) = analysis_period();
2217     -
2218     -dbg("Time interval:".strftime("%a %b %e %H:%M:%S %Y", localtime($start))."->".strftime("%a %b %e %H:%M:%S %Y", localtime($end))."\n");
2219     -
2220     -#
2221     -# First check current configuration for logging, DNS enable and Max threshold for spamassassin
2222     -#
2223     -
2224     -my $LogLevel = $cdb->get('qpsmtpd')->prop('LogLevel');
2225     -my $HighLogLevel = ( $LogLevel > 6 );
2226     -
2227     -my $RHSenabled =
2228     - ( $cdb->get('qpsmtpd')->prop('RHSBL') eq 'enabled' );
2229     -my $DNSenabled =
2230     - ( $cdb->get('qpsmtpd')->prop('DNSBL') eq 'enabled' );
2231     -my $SARejectLevel =
2232     - $cdb->get('spamassassin')->prop('RejectLevel');
2233     -my $SATagLevel =
2234     - $cdb->get('spamassassin')->prop('TagLevel');
2235     -my $DomainName =
2236     - $cdb->get('DomainName')->value;
2237     -
2238     -# check that logterse is in use
2239     -#my pluginfile = '/var/service/qpsmtpd/config/peers/0';
2240     -
2241     -if ( !$RHSenabled || !$DNSenabled ) {
2242     - $rblnotset = '*';
2243     -}
2244     -
2245     -if ( $SARejectLevel == 0 ) {
2246     -
2247     - $warnnoreject = "(*Warning* 0 = no reject)";
2248     -
2249     -}
2250     -
2251     -# get enable/disable subsections
2252     -my $enableqpsmtpdcodes;
2253     -my $enableSARules;
2254     -my $enableGeoiptable;
2255     -my $enablejunkMailList;
2256     -my $savedata;
2257     -my $enableblacklist; #Enabled according to setting in qpsmtpd
2258     -if ($cdb->get('mailstats')){
2259     - $enableqpsmtpdcodes = ($cdb->get('mailstats')->prop("QpsmtpdCodes") || "enabled") eq "enabled" || $false;
2260     - $enableSARules = ($cdb->get('mailstats')->prop("SARules") || "enabled") eq "enabled" || $false;
2261     - $enablejunkMailList = ($cdb->get('mailstats')->prop("JunkMailList") || "enabled") eq "enabled" || $false;
2262     - $enableGeoiptable = ($cdb->get('mailstats')->prop("Geoiptable") || "enabled") eq "enabled" || $false;
2263     - $savedata = ($cdb->get('mailstats')->prop("SaveDataToMySQL") || "no") eq "yes" || $false;
2264     - } else {
2265     - $enableqpsmtpdcodes = $true;
2266     - $enableSARules = $true;
2267     - $enablejunkMailList = $true;
2268     - $enableGeoiptable = $true;
2269     - $savedata = $false;
2270     - }
2271     - $enableblacklist = ($cdb->get('qpsmtpd')->prop("RHSBL") || "disabled") eq "enabled" || ($cdb->get('qpsmtpd')->prop("URIBL") || "disabled") eq "enabled";
2272     - #$savedata = $false; #TEMP!!
2273     -#if ($savedata){print STDERR "yes"} else {print STDERR "no"}
2274     -
2275     -my $makeHTMLemail = "no";
2276     -#if ($cdb->get('mailstats')){$makeHTMLemail = $cdb->get('mailstats')->prop('HTMLEmail') || "no"} #TEMP!!
2277     -my $makeHTMLpage = "no";
2278     -#if ($makeHTMLemail eq "yes" || $makeHTMLemail eq "both") {$makeHTMLpage = "yes"}
2279     -#if ($cdb->get('mailstats')){$makeHTMLpage = $cdb->get('mailstats')->prop('HTMLPage') || "no"}
2280     -
2281     -
2282     -# Init the hashes
2283     -my $nhour = floor( $start / 3600 );
2284     -my $ncateg;
2285     -while ( $nhour < $end / 3600 ) {
2286     - $counts{$nhour}=();
2287     - $ncateg = 0;
2288     - while ( $ncateg < @categs) {
2289     - $counts{$nhour}{$categs[$ncateg-1]} = 0;
2290     - $ncateg++
2291     - }
2292     - $nhour++;
2293     -}
2294     -# and grand totals, percent and display status from db entries, and column widths
2295     -$ncateg = 0;
2296     -my $colpadding = 0;
2297     -while ( $ncateg < @categs) {
2298     - $counts{$GRANDTOTAL}{$categs[$ncateg]} = 0;
2299     - $counts{$PERCENT}{$categs[$ncateg]} = 0;
2300     -
2301     - if ($cdb->get('mailstats')){
2302     - $display[$ncateg] = lc($cdb->get('mailstats')->prop($categs[$ncateg])) || "auto";
2303     - } else {
2304     - $display[$ncateg] = 'auto'
2305     - }
2306     - if ($ncateg == 0) {
2307     - $colwidth[$ncateg] = $HourColWidth + $colpadding;
2308     - } else {
2309     - $colwidth[$ncateg] = length($categs[$ncateg])+1+$colpadding;
2310     - }
2311     - if ($colwidth[$ncateg] < $MinCol) {$colwidth[$ncateg] = $MinCol + $colpadding}
2312     - $ncateg++
2313     -}
2314     -
2315     -my $starttai = Time::TAI64::unixtai64n($start);
2316     -my $endtai = Time::TAI64::unixtai64n($end);
2317     -my $sum_SARules = 0;
2318     -
2319     -# we remove non valid files
2320     -my @ARGV2;
2321     -foreach ( map { glob } @ARGV){
2322     - push(@ARGV2,($_));
2323     -}
2324     -@ARGV=@ARGV2;
2325     -
2326     -my $count = -1; #for loop reduction in debugging mode
2327     -
2328     -#
2329     -#---------------------------------------
2330     -# Scan the qpsmtpd log file(s)
2331     -#---------------------------------------
2332     -
2333     -
2334     -my $CurrentMailId = "";
2335     -
2336     -LINE: while (<>) {
2337     -
2338     - #print STDERR $starttai,$endtai,$_,"\n";
2339     -
2340     -
2341     - next LINE if !(my($tai,$log) = split(' ',$_,2));
2342     - #dbg("TAI:".$tai);
2343     -
2344     - #dbg("REST1:".$log);
2345     -
2346     - #If date specified, only process lines matching date
2347     - next LINE if ( $tai lt $starttai );
2348     - next LINE if ( $tai gt $endtai );
2349     -
2350     - #Count lines and skip out if debugging
2351     - $count++;
2352     - #last LINE if ($opt{debug} && $count >= 100);
2353     - #dbg("REST:".$log);
2354     -
2355     -
2356     - #Loglines to Saved String for later DB write
2357     - if ($savedata) {
2358     - my $CurrentLine = $_;
2359     - $CurrentLine = /^\@([0-9a-z]*) ([0-9]*) .*$/;
2360     - my $l = length($CurrentLine);
2361     - if ($l != 0){
2362     - if (defined($2)){ #print STDERR "Undefined \$2:".$_.":".$count.":".$l;exit}
2363     - if ($2 ne $CurrentMailId) {
2364     - print "CL:$CurrentLine*\n" if !defined($1);
2365     - $CurrentLogId = $1."-".$2;
2366     - $CurrentMailId = $2;
2367     - $Sequence = 0;
2368     - } else {$Sequence++}
2369     - #$CurrentLogId .=":".$Sequence;
2370     - $LogLines{$CurrentLogId.":".$Sequence} = $_;
2371     - }
2372     - }
2373     - #print STDERR $CurrentLogId.":".$LogLines{$CurrentLogId}."\n";
2374     - #exit
2375     - }
2376     -
2377     -
2378     - # pull out spamasassin rule lists
2379     - if ( $_ =~m/spamassassin: pass, Ham,(.*)</ )
2380     - #if ( $_ =~m/spamassassin plugin.*: check_spam:.*hits=(.*), required.*tests=(.*)/ )
2381     - {
2382     - #dbg("SPAM:".$log);
2383     - #New version does not seem to have spammassasin tests in logs
2384     - #if (exists($2){
2385     - #my (@SAtests) = split(',',$2);
2386     - #foreach my $SAtest (@SAtests) {
2387     - #if (!$SAtest eq "") {
2388     - #$found_SARules{$SAtest}{'count'}++;
2389     - #$found_SARules{$SAtest}{'totalhits'} += $1;
2390     - #$sum_SARules++
2391     - #}
2392     - #}
2393     - #}
2394     -
2395     - }
2396     -
2397     -
2398     - #Pull out Geoip countries for analysis table
2399     - if ( $_ =~m/check_badcountries: GeoIP Country: (.*)/ )
2400     - {
2401     - $found_countries{$1}++;
2402     - $total_countries++;
2403     - }
2404     -
2405     - #Pull out DMARC approvals
2406     - if ( $_ =~m/.*$DMARCOkPattern.*/ )
2407     - {
2408     - $DMARCOkCount++;
2409     - }
2410     -
2411     -
2412     - #only select Logterse output
2413     - next LINE unless m/logging::logterse:/;
2414     -
2415     - my $abstime = Time::TAI64::tai2unix($tai);
2416     - my $abshour = floor( $abstime / 3600 ); # Hours since the epoch
2417     -
2418     -
2419     - my ($timestamp_part, $log_part) = split('`',$_,2); #bjr 0.6.12
2420     - my (@log_items) = split $FS, $log_part;
2421     -
2422     - my (@timestamp_items) = split(' ',$timestamp_part);
2423     -
2424     - my $result= "rejected"; #Tag as rejected unti we know otherwise
2425     - # we store the more recent recipient domain, for domain statistics
2426     - # in fact, we only store the first recipient. Could be sort of headhache
2427     - # to obtain precise stats with many recipients on more than one domain !
2428     - my $proc = $timestamp_items[1] ; #numeric Id for the email
2429     - my $emailnum = $proc; #proc gets modified later...
2430     -
2431     - if ($emailnum == 23244) {
2432     - dbg("TM0:".$timestamp_items[0]);
2433     - dbg("TM1:".$timestamp_items[1]);
2434     - dbg("TM2:".$timestamp_items[2]);
2435     - dbg("TM3:".$timestamp_items[3]);
2436     - dbg("LOG0:".$log_items[0]);
2437     - dbg("LOG1:".$log_items[1]);
2438     - dbg("LOG2:".$log_items[2]);
2439     - dbg("LOG3:".$log_items[3]);
2440     - dbg("LOG4:".$log_items[4]);
2441     - dbg("LOG5:".$log_items[5]);
2442     - dbg("LOG6:".$log_items[6]);
2443     - dbg("LOG7:".$log_items[7]);
2444     - dbg("IPregexp:".$localIPregexp);
2445     - if (!test_for_private_ip($log_items[0])) {dbg("Log0 not found");}
2446     - if (test_for_private_ip($log_items[2])){ dbg("Log2 match")}
2447     - if ($log_items[5] eq "queued") {dbg("LOG5 match")}
2448     - }
2449     -
2450     - $totalexamined++;
2451     -
2452     -# dbg("LOG8:".$log_items[8]);
2453     -
2454     - # first spot the fetchmail and local deliveries.
2455     -
2456     - # Spot from local workstation
2457     - $localflag = 0;
2458     - $WebMailflag = 0;
2459     - if ( $log_items[1] =~ m/$DomainName/ ) { #bjr
2460     - #dbg("LOG1-Found:".$log_items[1]);
2461     - $localsendtotal++;
2462     - $counts{$abshour}{$CATLOCAL}++;
2463     - $localflag = 1;
2464     - }
2465     -
2466     - #Or a remote station
2467     - elsif ((!test_for_private_ip($log_items[0])) and (test_for_private_ip($log_items[2])) and ($log_items[5] eq "queued"))
2468     - {
2469     - #Remote user
2470     - $localflag = 1;
2471     - $counts{$abshour}{$CATRELAY}++;
2472     - }
2473     -
2474     - elsif (($log_items[2] =~ m/$WebmailIP/) and (!test_for_private_ip($log_items[0]))) {
2475     - #Webmail
2476     -# if ($emailnum == 19608){
2477     - dbg("WEBMAIL:");
2478     - dbg("TM0:".$timestamp_items[0]);
2479     - dbg("TM1:".$timestamp_items[1]);
2480     - dbg("TM2:".$timestamp_items[2]);
2481     - dbg("TM3:".$timestamp_items[3]);
2482     - dbg("LOG0:".$log_items[0]);
2483     - dbg("LOG1:".$log_items[1]);
2484     - dbg("LOG2:".$log_items[2]);
2485     - dbg("LOG3:".$log_items[3]);
2486     - dbg("LOG4:".$log_items[4]);
2487     - dbg("LOG5:".$log_items[5]);
2488     - dbg("LOG6:".$log_items[6]);
2489     - dbg("LOG7:".$log_items[7]);
2490     - #exit;
2491     -# }
2492     - $localflag = 1;
2493     - $WebMailsendtotal++;
2494     - $counts{$abshour}{$CATWEBMAIL}++;
2495     - $WebMailflag = 1;
2496     - }
2497     -
2498     - # see if from localhost
2499     - elsif ( $log_items[1] =~ m/$localhost/ ) {
2500     - # but not if it comes from fetchmail
2501     - if ( $log_items[3] =~ m/$FETCHMAIL/ ) { }
2502     - else {
2503     - $localflag = 1;
2504     - # might still be from mailman here
2505     - if ( $log_items[3] =~ m/$MAILMAN/ ) {
2506     - $mailmansendcount++;
2507     - $localsendtotal++;
2508     - $counts{$abshour}{$CATMAILMAN}++;
2509     - $localflag = 1;
2510     - }
2511     - else {
2512     - #Or sent to the DMARC server
2513     - #dbg("LOG4:".$log_items[4]);
2514     - #check for email address in $DMARC_Report_emails string
2515     - my $logemail = $log_items[4];
2516     - if ((index($DMARC_Report_emails,$logemail)>=0) or ($logemail =~ m/$DMARCDomain/)){
2517     - $localsendtotal++;
2518     - $DMARCSendCount++;
2519     - $localflag = 1;
2520     - }
2521     - else {
2522     - #print STDERR "no match:.".$logemail;
2523     - if (exists $log_items[8]){
2524     - #dbg("LOG8:".$log_items[8]);
2525     - # ignore incoming localhost spoofs
2526     - if ( $log_items[8] =~ m/msg denied before queued/ ) { }
2527     - else {
2528     - #Webmail
2529     - $localflag = 1;
2530     - $WebMailsendtotal++;
2531     - $counts{$abshour}{$CATWEBMAIL}++;
2532     - $WebMailflag = 1;
2533     - }
2534     - }
2535     - else {
2536     - $localflag = 1;
2537     - $WebMailsendtotal++;
2538     - $counts{$abshour}{$CATWEBMAIL}++;
2539     - $WebMailflag = 1;
2540     - }
2541     - }
2542     - }
2543     - }
2544     - }
2545     -
2546     - # try to spot fetchmail emails
2547     - if ( $log_items[0] =~ m/$FetchmailIP/ ) {
2548     - #dbg("LOG0:".$log_items[0]);
2549     - $localAccepttotal++;
2550     - $counts{$abshour}{$CATFETCHMAIL}++;
2551     - }
2552     - elsif ( $log_items[3] =~ m/$FETCHMAIL/ ) {
2553     - $localAccepttotal++;
2554     - $counts{$abshour}{$CATFETCHMAIL}++;
2555     - }
2556     -
2557     -# and adjust for recipient field if not set-up by denying plugin - extract from deny msg
2558     -
2559     - if ( length( $log_items[4] ) == 0 ) {
2560     - #dbg("LOG7:".$log_items[0]);
2561     - if ( $log_items[5] eq 'check_goodrcptto' ) {
2562     - if ( $log_items[7] gt "invalid recipient" ) {
2563     - $log_items[4] =
2564     - substr( $log_items[7], 18 ); #Leave only email address
2565     - #dbg("LOG4:".$log_items[0]);
2566     -
2567     - }
2568     - }
2569     - }
2570     -
2571     - # if ( ( $currentrcptdomain{ $proc } || '' ) eq '' ) {
2572     - # reduce to lc and process each e,mail if a list, pseperatedy commas
2573     - my $recipientmail = lc( $log_items[4] );
2574     - #dbg("LOG4:".$log_items[0]);
2575     - if ( $recipientmail =~ m/.*,/ ) {
2576     -
2577     - #comma - split the line and deal with each domain
2578     - # print $recipientmail."\n";
2579     - my ($recipients) = split( ',', $recipientmail );
2580     - foreach my $recip ($recipients) {
2581     - $proc = $proc . $recip;
2582     -
2583     - # print $proc."\n";
2584     - $currentrcptdomain{$proc} = $recip;
2585     - add_in_domain($proc);
2586     - $recipcount++;
2587     - }
2588     -
2589     - # print "*\n";
2590     - #count emails with more than one recipient
2591     - # $recipientmail =~ m/(.*),/;
2592     - # $currentrcptdomain{ $proc } = $1;
2593     - }
2594     - else {
2595     - $proc = $proc . $recipientmail;
2596     - $currentrcptdomain{$proc} = $recipientmail;
2597     - add_in_domain($proc);
2598     - $recipcount++;
2599     - }
2600     -
2601     - # } else {
2602     - # # there more than a recipient for a mail, how many daily ?
2603     - # $morethanonercpt++;
2604     - # }
2605     -
2606     -
2607     - # then categorise the result
2608     -
2609     -
2610     - if (exists $log_items[5]) {
2611     -
2612     - if ($log_items[5] eq 'naughty') {
2613     - my $rejreason = $log_items[7];
2614     - $rejreason = /.*(\(.*\)).*/;
2615     - if (!defined($1)){$rejreason = "unknown"}
2616     - else {$rejreason = $1}
2617     - $found_qpcodes{$log_items[5]."-".$rejreason}++}
2618     - else {$found_qpcodes{$log_items[5]}++} ##Count different qpsmtpd result codes
2619     -
2620     - if ($log_items[5] eq 'check_earlytalker') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2621     -
2622     - elsif ($log_items[5] eq 'check_relay') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2623     -
2624     - elsif ($log_items[5] eq 'check_norelay') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2625     -
2626     - elsif ($log_items[5] eq 'require_resolvable_fromhost') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2627     -
2628     - elsif ($log_items[5] eq 'check_basicheaders') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2629     -
2630     - elsif ($log_items[5] eq 'rhsbl') { $RBLcount++;$counts{$abshour}{$CATRBLDNS}++;mark_domain_rejected($proc);$blacklistURL{get_domain($log_items[7])}++}
2631     -
2632     - elsif ($log_items[5] eq 'dnsbl') { $RBLcount++;$counts{$abshour}{$CATRBLDNS}++;mark_domain_rejected($proc);$blacklistURL{get_domain($log_items[7])}++}
2633     -
2634     - elsif ($log_items[5] eq 'check_badmailfrom') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2635     -
2636     - elsif ($log_items[5] eq 'check_badrcptto_patterns') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2637     -
2638     - elsif ($log_items[5] eq 'check_badrcptto') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2639     -
2640     - elsif ($log_items[5] eq 'check_spamhelo') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2641     -
2642     - elsif ($log_items[5] eq 'check_goodrcptto extn') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2643     -
2644     - elsif ($log_items[5] eq 'rcpt_ok') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2645     -
2646     - elsif ($log_items[5] eq 'pattern_filter') { $PatternFilterCount++;$counts{$abshour}{$CATEXECUT}++;mark_domain_rejected($proc)}
2647     -
2648     - elsif ($log_items[5] eq 'virus::pattern_filter') { $PatternFilterCount++;$counts{$abshour}{$CATEXECUT}++;mark_domain_rejected($proc)}
2649     -
2650     - elsif ($log_items[5] eq 'check_goodrcptto') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2651     -
2652     - elsif ($log_items[5] eq 'check_smtp_forward') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2653     -
2654     - elsif ($log_items[5] eq 'count_unrecognized_commands') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2655     -
2656     - elsif ($log_items[5] eq 'check_badcountries') {$MiscDenyCount++;$counts{$abshour}{$CATBADCOUNTRIES}++;mark_domain_rejected($proc)}
2657     -
2658     - elsif ($log_items[5] eq 'tnef2mime') { } #Not expecting this one.
2659     -
2660     - elsif ($log_items[5] eq 'spamassassin') { $above15++;$counts{$abshour}{$CATSPAMDEL}++;
2661     - # and extract the spam score
2662     - # if ($log_items[8] =~ "Yes, hits=(.*) required=([0-9\.]+)")
2663     - if ($log_items[8] =~ "Yes, score=(.*) required=([0-9\.]+)")
2664     - {$rejectspamavg += $1}
2665     - mark_domain_rejected($proc);
2666     - }
2667     -
2668     - elsif (($log_items[5] eq 'virus::clamav') or ($log_items[5] eq 'virus::clamdscan')) { $infectedcount++;$counts{$abshour}{$CATVIRUS}++;
2669     - #extract the virus name
2670     - if ($log_items[7] =~ "Virus found: (.*)" ) {$found_viruses{$1}++;}
2671     - else {$found_viruses{$log_items[7]}++} #Some other message!!
2672     - #dbg("LOG7:".$log_items[7]);
2673     - mark_domain_rejected($proc);
2674     - }
2675     -
2676     - elsif ($log_items[5] eq 'queued') { $Accepttotal++;
2677     - #extract the spam score
2678     - # Remove count for rejectred as it looks as if it might get through!!
2679     - $result= "queued";
2680     - if ($log_items[8] =~ ".*score=([+-]?\\d+\.?\\d*).* required=([0-9\.]+)") {
2681     - $score = trim($1);
2682     - if ($score =~ /^[+-]?\d+\.?\d*$/ ) #check its numeric
2683     - {
2684     - if ($score < $SATagLevel) { $hamcount++;$counts{$abshour}{$CATHAM}++;$hamavg += $score;}
2685     - else {$spamcount++;$counts{$abshour}{$CATSPAM}++;$spamavg += $score;$result= "spam";}
2686     - } else {
2687     - print "Unexpected non numeric found in $proc:".$log_items[8]."($score)\n";
2688     - }
2689     - } else {
2690     - # no SA score - treat it as ham
2691     - $hamcount++;$counts{$abshour}{$CATHAM}++;
2692     - }
2693     - if ( ( $currentrcptdomain{ $proc } || '' ) ne '' ) {
2694     - $byrcptdomain{ $currentrcptdomain{ $proc } }{ 'accept' }++ ;
2695     - $currentrcptdomain{ $proc } = '' ;
2696     - }
2697     - }
2698     -
2699     -
2700     - elsif ($log_items[5] eq 'tls') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2701     -
2702     - elsif ($log_items[5] eq 'auth::auth_cvm_unix_local') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2703     -
2704     - elsif ($log_items[5] eq 'earlytalker') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2705     -
2706     - elsif ($log_items[5] eq 'uribl') {$RBLcount++;$counts{$abshour}{$CATRBLDNS}++;mark_domain_rejected($proc);$blacklistURL{get_domain($log_items[7])}++}
2707     -
2708     - elsif ($log_items[5] eq 'naughty') {
2709     - #Naughty plugin seems to span a number of rejection reasons - so we have to use the next but one log_item[7] to identify
2710     - if ($log_items[7] =~ m/(karma)/) {
2711     - $MiscDenyCount++;$counts{$abshour}{$CATKARMA}++;mark_domain_rejected($proc)}
2712     - elsif ($log_items[7] =~ m/(dnsbl)/){
2713     - $RBLcount++;$counts{$abshour}{$CATRBLDNS}++;mark_domain_rejected($proc);$blacklistURL{get_domain($log_items[7])}++}
2714     - elsif ($log_items[7] =~ m/(helo)/){
2715     - $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2716     - else {
2717     - #Unidentified Naughty rejection
2718     - $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);$unrecog_plugin{$log_items[5]."-".$log_items[7]}++}
2719     - }
2720     - elsif ($log_items[5] eq 'resolvable_fromhost') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2721     -
2722     - elsif ($log_items[5] eq 'loadcheck') {$MiscDenyCount++;$counts{$abshour}{$CATLOAD}++;mark_domain_rejected($proc)}
2723     -
2724     - elsif ($log_items[5] eq 'karma') {$MiscDenyCount++;$counts{$abshour}{$CATKARMA}++;mark_domain_rejected($proc)}
2725     -
2726     - elsif ($log_items[5] eq 'dmarc') {$MiscDenyCount++;$counts{$abshour}{$CATDMARC}++;mark_domain_rejected($proc)}
2727     -
2728     - elsif ($log_items[5] eq 'relay') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2729     -
2730     - elsif ($log_items[5] eq 'headers') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2731     -
2732     - elsif ($log_items[5] eq 'mailfrom') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2733     -
2734     - elsif ($log_items[5] eq 'badrcptto') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2735     -
2736     - elsif ($log_items[5] eq 'helo') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2737     -
2738     - elsif ($log_items[5] eq 'check_smtp_forward') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2739     -
2740     - elsif ($log_items[5] eq 'sender_permitted_from') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc)}
2741     -
2742     - #Treat it as Unconf if not recognised
2743     - else {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);$unrecog_plugin{$log_items[5]}++}
2744     - } #Log[5] exists
2745     -
2746     - #Entry if not local send
2747     - if ($localflag == 0) {
2748     - if (length($log_items[4]) > 0){
2749     - # Need to check here for multiple email addresses
2750     - my @emails = split(",",lc($log_items[4]));
2751     - if (scalar(@emails) > 1) {
2752     - #Just pick the first local address to hang it on.
2753     - # TEMP - just go for the first address until I can work out how to spot the 1st "local" one
2754     - $usercounts{$emails[0]}{$result}++;
2755     - $usercounts{$emails[0]}{"proc"} = $proc;
2756     - #Compare with @domains array until we get a local one
2757     - my $gotone = $false;
2758     - foreach my $email (@emails){
2759     - #Extract the domain from the email address
2760     - my $fullemail = $email;
2761     - $email = s/.*\@(.*)$/$1/;
2762     - #and see if it is local
2763     - if ($email =~ m/$alldomains/){
2764     - $usercounts{lc($fullemail)}{$result}++;
2765     - $usercounts{lc($fullemail)}{"proc"} = $proc;
2766     - $gotone = $true;
2767     - last;
2768     - }
2769     - }
2770     - if (!$gotone) {
2771     - $usercounts{'No internal email $proc'}{$result}++;
2772     - $usercounts{'No internal email $proc'}{"proc"} = $proc;
2773     - }
2774     -
2775     - } else {
2776     - $usercounts{lc($log_items[4])}{$result}++;
2777     - $usercounts{lc($log_items[4])}{"proc"} = $proc;
2778     - }
2779     - }
2780     - }
2781     - #exit if $emailnum == 15858;
2782     -
2783     -} #END OF MAIN LOOP
2784     -
2785     -#total up grand total Columns
2786     -$nhour = floor( $start / 3600 );
2787     -while ( $nhour < $end / 3600 ) {
2788     - $ncateg = 0; #past the where it came from columns
2789     - while ( $ncateg < @categs) {
2790     - #total columns
2791     - $counts{$GRANDTOTAL}{$categs[$ncateg]} += $counts{$nhour}{$categs[$ncateg]};
2792     -
2793     - # and total rows
2794     - if ( $ncateg < $categlen and $ncateg>=$countfromhere) {#skip initial columns of non final reasons
2795     - $counts{$nhour}{$categs[@categs-2]} += $counts{$nhour}{$categs[$ncateg]};
2796     - }
2797     - $ncateg++
2798     - }
2799     -
2800     - $nhour++;
2801     -}
2802     -
2803     -
2804     -
2805     -#Compute row totals and row percentages
2806     -$nhour = floor( $start / 3600 );
2807     -while ( $nhour < $end / 3600 ) {
2808     - $counts{$nhour}{$categs[@categs-1]} = $counts{$nhour}{$categs[@categs-2]}*100/$totalexamined if $totalexamined;
2809     - $nhour++;
2810     -
2811     -}
2812     -
2813     -#compute column percentages
2814     - $ncateg = 0;
2815     - while ( $ncateg < @categs) {
2816     - if ($ncateg == @categs-1) {
2817     - $counts{$PERCENT}{$categs[$ncateg]} = $counts{$GRANDTOTAL}{$categs[$ncateg-1]}*100/$totalexamined if $totalexamined;
2818     - } else {
2819     - $counts{$PERCENT}{$categs[$ncateg]} = $counts{$GRANDTOTAL}{$categs[$ncateg]}*100/$totalexamined if $totalexamined;
2820     - }
2821     - $ncateg++
2822     - }
2823     -
2824     -#compute sum of row percentages
2825     -$nhour = floor( $start / 3600 );
2826     -while ( $nhour < $end / 3600 ) {
2827     - $counts{$GRANDTOTAL}{$categs[@categs-1]} += $counts{$nhour}{$categs[@categs-1]};
2828     - $nhour++;
2829     -
2830     -}
2831     -
2832     -my $QueryNoLogTerse = ($totalexamined==0); #might indicate logterse not installed in qpsmtpd plugins
2833     -
2834     -#Calculate some numbers
2835     -
2836     -$spamavg = $spamavg / $spamcount if $spamcount;
2837     -$rejectspamavg = $rejectspamavg / $above15 if $above15;
2838     -$hamavg = $hamavg / $hamcount if $hamcount;
2839     -
2840     -# RBL etc percent of total SMTP sessions
2841     -
2842     -my $rblpercent = ( ( $RBLcount / $totalexamined ) * 100 ) if $totalexamined;
2843     -my $PatternFilterpercent = ( ( $PatternFilterCount / $totalexamined ) * 100 ) if $totalexamined;
2844     -my $Miscpercent = ( ( $MiscDenyCount / $totalexamined ) * 100 ) if $totalexamined;
2845     -
2846     -#Spam and virus percent of total email downloaded
2847     -#Expressed as a % of total examined
2848     -my $spampercent = ( ( $spamcount / $totalexamined ) * 100 ) if $totalexamined;
2849     -my $hampercent = ( ( $hamcount / $totalexamined ) * 100 ) if $totalexamined;
2850     -my $hrsinperiod = ( ( $end - $start ) / 3600 );
2851     -my $emailperhour = ( $totalexamined / $hrsinperiod ) if $totalexamined;
2852     -my $above15percent = ( $above15 / $totalexamined * 100 ) if $totalexamined;
2853     -my $infectedpercent = ( ( $infectedcount / ($totalexamined) ) * 100 ) if $totalexamined;
2854     -my $AcceptPercent = ( ( $Accepttotal / ($totalexamined) ) * 100 ) if $totalexamined;
2855     -
2856     -my $oldfh;
2857     -
2858     -#Open Sendmail if we are mailing it
2859     -if ( $opt{'mail'} and !$disabled ) {
2860     - open( SENDMAIL, "|$opt{'sendmail'} -oi -t -odq" )
2861     - or die "Can't open sendmail: $!\n";
2862     - print SENDMAIL "From: $opt{'from'}\n";
2863     - print SENDMAIL "To: $opt{'mail'}\n";
2864     - print SENDMAIL "Subject: Spam Filter Statistics from $hostname - ",
2865     - strftime( "%F", localtime($start) ), "\n\n";
2866     - $oldfh = select SENDMAIL;
2867     -}
2868     -
2869     -my $telapsed = time - $tstart;
2870     -
2871     -if ( !$disabled ) {
2872     -
2873     - #Output results
2874     -
2875     - # NEW - save the print to a variable so that it can be processed into html.
2876     - #
2877     - #Save current output selection and divert into variable
2878     - #
2879     - my $output;
2880     - my $tablestr="";
2881     - open(my $outputFH, '>', \$tablestr) or die; # This shouldn't fail
2882     - my $oldFH = select $outputFH;
2883     -
2884     -
2885     - print "SMEServer daily Anti-Virus and Spamfilter statistics from $hostname - ".strftime( "%F", localtime($start))."\n";
2886     - print "----------------------------------------------------------------------------------", "\n\n";
2887     - print "$0 Version : $opt{'version'}", "\n";
2888     - print "Period Beginning : ", strftime( "%c", localtime($start) ), "\n";
2889     - print "Period Ending : ", strftime( "%c", localtime($end) ), "\n";
2890     - print "Clam Version/DB Count/Last DB update: ",`freshclam -V`;
2891     - print "SpamAssassin Version : ",`spamassassin -V`;
2892     - printf "Tag level: %3d; Reject level: %3d $warnnoreject\n", $SATagLevel,$SARejectLevel;
2893     - if ($HighLogLevel) {
2894     - printf "*Loglevel is set to: ".$LogLevel. " - you only need it set to 6\n";
2895     - printf "\tYou can set it this way:\n";
2896     - printf "\tconfig setprop qpsmtpd LogLevel 6\n";
2897     - printf "\tsignal-event email-update\n";
2898     - printf "\tsv t /var/service/qpsmtpd\n";
2899     - }
2900     - printf "Reporting Period : %.2f hrs\n", $hrsinperiod;
2901     - printf "All SMTP connections accepted:%8d \n", $totalexamined;
2902     - printf "Emails per hour : %8.1f/hr\n", $emailperhour || 0;
2903     - printf "Average spam score (accepted): %11.2f\n", $spamavg || 0;
2904     - printf "Average spam score (rejected): %11.2f\n", $rejectspamavg || 0;
2905     - printf "Average ham score : %11.2f\n", $hamavg || 0;
2906     - printf "Number of DMARC reporting emails sent:\t%11d (not shown on table)\n", $DMARCSendCount || 0;
2907     - if ($hamcount != 0){ printf "Number of emails approved through DMARC:\t%11d (%3d%% of Ham count)\n", $DMARCOkCount|| 0,$DMARCOkCount*100/$hamcount || 0;}
2908     -
2909     - my $smeoptimizerprog = "/usr/local/smeoptimizer/SMEOptimizer.pl";
2910     - if (-e $smeoptimizerprog) {
2911     - #smeoptimizer installed - get result of status
2912     - my @smeoptimizerlines = split(/\n/,`/usr/local/smeoptimizer/SMEOptimizer.pl -status`);
2913     - print("SMEOptimizer status:\n");
2914     - print("\t".$smeoptimizerlines[6]."\n");
2915     - print("\t".$smeoptimizerlines[7]."\n");
2916     - print("\t".$smeoptimizerlines[8]."\n");
2917     - print("\t".$smeoptimizerlines[9]."\n");
2918     - print("\t".$smeoptimizerlines[10]."\n");
2919     - }
2920     -
2921     -
2922     - print "\nStatistics by Hour:\n";
2923     - #
2924     - # start by working out which colunns to show - tag the display array
2925     - #
2926     - $ncateg = 1; ##skip the first column
2927     - $finaldisplay[0] = $true;
2928     - while ( $ncateg < $categlen) {
2929     - if ($display[$ncateg] eq 'yes') { $finaldisplay[$ncateg] = $true }
2930     - elsif ($display[$ncateg] eq 'no') { $finaldisplay[$ncateg] = $false }
2931     - else {
2932     - $finaldisplay[$ncateg] = ($counts{$GRANDTOTAL}{$categs[$ncateg]} != 0);
2933     - if ($finaldisplay[$ncateg]) {
2934     - #if it has been non zero and auto, then make it yes for the future.
2935     - esmith::ConfigDB->open->get('mailstats')->set_prop($categs[$ncateg],'yes')
2936     - }
2937     -
2938     - }
2939     - $ncateg++
2940     - }
2941     - #make sure total and percentages are shown
2942     - $finaldisplay[@categs-2] = $true;
2943     - $finaldisplay[@categs-1] = $true;
2944     -
2945     -
2946     - # and put together the print lines
2947     -
2948     - my $Line1; #Full Line across the page
2949     - my $Line2; #Broken Line across the page
2950     - my $Titles; #Column headers
2951     - my $Values; #Values
2952     - my $Totals; #Corresponding totals
2953     - my $Percent; # and column percentages
2954     -
2955     - my $hour = floor( $start / 3600 );
2956     - $Line1 = '';
2957     - $Line2 = '';
2958     - $Titles = '';
2959     - $Values = '';
2960     - $Totals = '';
2961     - $Percent = '';
2962     - while ( $hour < $end / 3600 ) {
2963     - if ($hour == floor( $start / 3600 )){
2964     - #Do all the once only things
2965     - $ncateg = 0;
2966     - while ( $ncateg < @categs) {
2967     - if ($finaldisplay[$ncateg]){
2968     - $Line1 .= substr('---------------------',0,$colwidth[$ncateg]);
2969     - $Line2 .= substr('---------------------',0,$colwidth[$ncateg]-1);
2970     - $Line2 .= " ";
2971     - $Titles .= sprintf('%'.($colwidth[$ncateg]-1).'s',$categs[$ncateg])."|";
2972     - if ($ncateg == 0) {
2973     - $Totals .= substr('TOTALS ',0,$colwidth[$ncateg]-2);
2974     - $Percent .= substr('PERCENTAGES ',0,$colwidth[$ncateg]-1);
2975     - } else {
2976     - # identify bottom right group and supress unless db->ShowGranPerc set
2977     - if ($ncateg==@categs-1){
2978     - $Totals .= sprintf('%'.$colwidth[$ncateg].'.1f',$counts{$GRANDTOTAL}{$categs[$ncateg]}).'%';
2979     - } else {
2980     - $Totals .= sprintf('%'.$colwidth[$ncateg].'d',$counts{$GRANDTOTAL}{$categs[$ncateg]});
2981     - }
2982     - $Percent .= sprintf('%'.($colwidth[$ncateg]-1).'.1f',$counts{$PERCENT}{$categs[$ncateg]}).'%';
2983     - }
2984     - }
2985     - $ncateg++
2986     - }
2987     - }
2988     -
2989     - $ncateg = 0;
2990     - while ( $ncateg < @categs) {
2991     - if ($finaldisplay[$ncateg]){
2992     - if ($ncateg == 0) {
2993     - $Values .= strftime( "%F, %H", localtime( $hour * 3600 ) )." "
2994     - } elsif ($ncateg == @categs-1) {
2995     - #percentages in last column
2996     - $Values .= sprintf('%'.($colwidth[$ncateg]-2).'.1f',$counts{$hour}{$categs[$ncateg]})."%";
2997     - } else {
2998     - #body numbers
2999     - $Values .= sprintf('%'.($colwidth[$ncateg]-1).'d',$counts{$hour}{$categs[$ncateg]})." ";
3000     - }
3001     - if (($ncateg == @categs-1)){$Values=$Values."\n"} #&& ($hour == floor($end / 3600)-1)
3002     - }
3003     - $ncateg++
3004     - }
3005     -
3006     - $hour++;
3007     - }
3008     -
3009     - #
3010     - # print it.
3011     - #
3012     -
3013     - print $Line1."\n";
3014     - #if ($makeHTMLemail eq "no" && $makeHTMLpage eq "no"){print $Line1."\n";} #These lines mess up the HTML conversion ....
3015     - print $Titles."\n";
3016     - #if ($makeHTMLemail eq "no" && $makeHTMLpage eq "no"){print $Line2."\n";} #ditto
3017     - print $Line2."\n";
3018     - print $Values;
3019     - print $Line2."\n";
3020     - print $Totals."\n";
3021     - print $Percent."\n";
3022     - print $Line1."\n";
3023     -
3024     - if ($localAccepttotal>0) {
3025     - print "*Fetchml* means connections from Fetchmail delivering email\n";
3026     - }
3027     - print "*Local* means connections from workstations on local LAN.\n\n";
3028     - print "*Non\.Conf\.* means sending mailserver did not conform to correct protocol";
3029     - print " or email was to non existant address.\n\n";
3030     -
3031     - if ($finaldisplay[$KarmaCateg]){
3032     - print "*Karma* means email was rejected based on the mailserver's previous activities.\n\n";
3033     - }
3034     -
3035     -
3036     - if ($finaldisplay[$BadCountryCateg]){
3037     - $BadCountries = $cdb->get('qpsmtpd')->prop('BadCountries') || "*none*";
3038     - print "*Geoip\.*:Bad Countries mask is:".$BadCountries."\n\n";
3039     - }
3040     -
3041     -
3042     -
3043     - if (scalar keys %unrecog_plugin > 0){
3044     - #Show unrecog plugins found
3045     - print "*Unrecognised plugins found - categorised as Non-Conf\n";
3046     - foreach my $unrec (keys %unrecog_plugin){
3047     - print "\t$unrec\t($unrecog_plugin{$unrec})\n";
3048     - }
3049     - print "\n";
3050     - }
3051     -
3052     - if ($QueryNoLogTerse) {
3053     - print "* - as no records where found, it looks as though you may not have the *logterse* \nplugin running as part of qpsmtpd \n\n";
3054     -# print " to enable it follow the instructions at .............................\n";
3055     - }
3056     -
3057     -
3058     - if ( !$RHSenabled or !$DNSenabled ) {
3059     -
3060     - # comment about RBL not set
3061     - print
3062     -"* - This means that one or more of the possible spam black listing services\n that are available have not been enabled.\n";
3063     - print " You have not enabled:\n";
3064     -
3065     - if ( !$RHSenabled ) {
3066     - print " RHSBL\n";
3067     - }
3068     -
3069     - if ( !$DNSenabled ) {
3070     - print " DNSBL\n";
3071     - }
3072     -
3073     -
3074     - print " To enable these you can use the following commands:\n";
3075     - if ( !$RHSenabled ) {
3076     - print " config setprop qpsmtpd RHSBL enabled\n";
3077     - }
3078     -
3079     - if ( !$DNSenabled ) {
3080     - print " config setprop qpsmtpd DNSBL enabled\n";
3081     - }
3082     -
3083     - # there so much templates to expand... (PS)
3084     - print " Followed by:\n signal-event email-update and\n sv t /var/service/qpsmtpd\n\n";
3085     - }
3086     -
3087     -# if ($Webmailsendtotal > 0) {print "If you have the mailman contrib installed, then the webmail totals might include some mailman emails\n"}
3088     -
3089     - # time to do a 'by recipient domain' report
3090     - print "Incoming mails by recipient domains usage\n";
3091     - print "-----------------------------------------\n";
3092     - print
3093     - "Domains Type Total Denied XferErr Accept \%accept\n";
3094     - print
3095     - "---------------------------- ---------- ------ ------ ------- ------ -------\n";
3096     - my %total = (
3097     - total => 0,
3098     - deny => 0,
3099     - xfer => 0,
3100     - accept => 0,
3101     - );
3102     - foreach my $domain (
3103     - sort {
3104     - join( "\.", reverse( split /\./, $a ) ) cmp
3105     - join( "\.", reverse( split /\./, $b ) )
3106     - } keys %byrcptdomain
3107     - )
3108     - {
3109     - next if ( ( $byrcptdomain{$domain}{'total'} || 0 ) == 0 );
3110     - my $tp = $byrcptdomain{$domain}{'type'} || 'other';
3111     - my $to = $byrcptdomain{$domain}{'total'} || 0;
3112     - my $de = $byrcptdomain{$domain}{'deny'} || 0;
3113     - my $xr = $byrcptdomain{$domain}{'xfer'} || 0;
3114     - my $ac = $byrcptdomain{$domain}{'accept'} || 0;
3115     - printf "%-28s %-10s %6d %6d %7d %6d %6.2f%%\n", $domain, $tp, $to,
3116     - $de, $xr, $ac, $ac * 100 / $to;
3117     - $total{'total'} += $to;
3118     - $total{'deny'} += $de;
3119     - $total{'xfer'} += $xr;
3120     - $total{'accept'} += $ac;
3121     - }
3122     - print
3123     - "---------------------------- ---------- ------ ------- ------ ------ -------\n";
3124     -
3125     - # $total{ 'total' } can be equal to 0, bad for divisions...
3126     - my $perc1 = 0;
3127     - my $perc2 = 0;
3128     -
3129     -
3130     - if ( $total{'total'} != 0 ) {
3131     - $perc1 = $total{'accept'} * 100 / $total{'total'};
3132     - $perc2 = ( ( $total{'total'} + $morethanonercpt ) / $total{'total'} );
3133     - }
3134     - printf
3135     - "Total %6d %6d %7d %6d %6.2f%%\n\n",
3136     - $total{'total'}, $total{'deny'}, $total{'xfer'}, $total{'accept'},
3137     - $perc1;
3138     - printf
3139     - "%d mails were processed for %d Recipients\nThe average recipients by mail is %4.2f\n\n",
3140     - $total{'total'}, ( $total{'total'} + $morethanonercpt ), $perc2;
3141     -
3142     - if ( $infectedcount > 0 ) {
3143     - show_virus_variants();
3144     - }
3145     -
3146     -
3147     - if ($enableqpsmtpdcodes) {show_qpsmtpd_codes();}
3148     -
3149     - if ($enableSARules) {show_SARules_codes();}
3150     -
3151     - if ($enableGeoiptable and (($total_countries > 0) or $finaldisplay[$BadCountryCateg])){show_Geoip_results();}
3152     -
3153     - if ($enablejunkMailList) {List_Junkmail();}
3154     -
3155     - if ($enableblacklist) {show_blacklist_counts();}
3156     -
3157     - show_user_stats();
3158     -
3159     - print "\nReport generated in $telapsed sec.\n";
3160     -
3161     - if ($savedata) { save_data(); }
3162     - else
3163     - { print "No data saved - if you want to save data to a MySQL database, then please use:\n".
3164     - "config setprop mailstats SaveDataToMySQL yes\n";
3165     - }
3166     -
3167     - select $oldFH;
3168     - close $outputFH;
3169     - if ($makeHTMLemail eq "no" or $makeHTMLemail eq "both") {print $tablestr}
3170     - if ($makeHTMLemail eq "yes" or $makeHTMLemail eq "both" or $makeHTMLpage eq "yes"){
3171     - #Convert text to html and send it
3172     - require CGI;
3173     - require TextToHTML;
3174     - my $cgi = new CGI;
3175     - my $text = $tablestr;
3176     - my %paramhash = (default_link_dict=>'',make_tables=>1,preformat_trigger_lines=>10,tab_width=>20);
3177     - my $conv = new HTML::TextToHTML();
3178     - $conv->args(default_link_dict=>'',make_tables=>1,preformat_trigger_lines=>2,preformat_whitespace_min=>2,
3179     - underline_length_tolerance=>1);
3180     -
3181     - my $html = $cgi->header();
3182     - $html .="<!DOCTYPE html> <html>\n";
3183     - $html .= "<head><title>Mailstats -".strftime( "%F", localtime($start) )."</title>";
3184     - $html .= "<link rel='stylesheet' type='text/css' href='mailstats.css' /></head>\n";
3185     - $html .= "<body>\n";
3186     - $html .= $conv->process_chunk($text);
3187     - $html .= "</body></html>\n";
3188     - if ($makeHTMLemail eq "yes" or $makeHTMLemail eq "both" ) {print $html}
3189     - #And drop it into a file
3190     - if ($makeHTMLpage eq "yes") {
3191     - my $filename = "mailstats.html";
3192     - open(my $fh, '>', $filename) or die "Could not open file '$filename' $!";
3193     - print $fh $html;
3194     - close $fh;
3195     - }
3196     -
3197     - }
3198     -
3199     -
3200     - #Close Sendmail if it was opened
3201     - if ( $opt{'mail'} ) {
3202     - select $oldfh;
3203     - close(SENDMAIL);
3204     - }
3205     -
3206     -} ##report disabled
3207     -
3208     -#All done
3209     -exit 0;
3210     -
3211     -#############################################################################
3212     -# Subroutines ###############################################################
3213     -#############################################################################
3214     -
3215     -
3216     -################################################
3217     -# Determine analysis period (start and end time)
3218     -################################################
3219     -sub analysis_period {
3220     - my $startdate = shift;
3221     - my $enddate = shift;
3222     -
3223     - my $secsininterval = 86400; #daily default
3224     - my $time;
3225     -
3226     - if ($cdb->get('mailstats'))
3227     - {
3228     - my $interval = $cdb->get('mailstats')->prop('Interval') || 'daily'; #"fortnightly"; #"daily";# #; TEMP!!
3229     - if ($interval eq "weekly") {
3230     - $secsininterval = 86400*7;
3231     - } elsif ($interval eq "fortnightly") {
3232     - $secsininterval = 86400*14;
3233     - } elsif ($interval eq "monthly") {
3234     - $secsininterval = 86400*30;
3235     - } elsif ($interval =~m/\d+/) {
3236     - $secsininterval = $interval*3600;
3237     - };
3238     - my $base = $cdb->get('mailstats')->prop('Base') || 'Midnight';
3239     - my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
3240     - localtime(time);
3241     - if ($base eq "Midnight"){
3242     - $sec = 0;$min=0;$hour=0;
3243     - } elsif ($base eq "Midday"){
3244     - $sec = 0;$min=0;$hour=12;
3245     - } elsif ($base =~m/\d+/){
3246     - $sec=0;$min=0;$hour=$base;
3247     - };
3248     - #$mday="05"; #$mday="03"; #$mday="16"; #Temp!!
3249     - $time = timelocal($sec,$min,$hour,$mday,$mon,$year);
3250     - }
3251     -
3252     - my $start = str2time( $startdate );
3253     - my $end = $enddate ? str2time( $enddate ) :
3254     - $startdate ? $start + $secsininterval : $time;
3255     - $start = $startdate ? $start : $end - $secsininterval;
3256     - return ( $start > $end ) ? ( $end, $start ) : ( $start, $end );
3257     -}
3258     -
3259     -sub dbg {
3260     - my $msg = shift;
3261     - my $time = scalar localtime;
3262     - $msg = $time.":".$msg."\n";
3263     - if ( $opt{debug} ) {
3264     - print STDERR $msg;
3265     - }
3266     -}
3267     -
3268     -sub List_Junkmail {
3269     -
3270     - #
3271     - # Show how many junkmails in each user's junkmail folder.
3272     - #
3273     - use esmith::AccountsDB;
3274     - my $adb = esmith::AccountsDB->open_ro;
3275     - my $entry;
3276     - foreach my $user ( $adb->users ) {
3277     - my $found = 0;
3278     - my $junkmail_dir =
3279     - "/home/e-smith/files/users/" . $user->key . "/Maildir/.junkmail";
3280     - foreach my $dir (qw(new cur)) {
3281     -
3282     - # Now get the content list for the directory.
3283     - if ( opendir( QDIR, "$junkmail_dir/$dir" ) ) {
3284     - while ( $entry = readdir(QDIR) ) {
3285     - next if $entry =~ /^\./;
3286     - $found++;
3287     - }
3288     - closedir(QDIR);
3289     - }
3290     - }
3291     - if ( $found != 0 ) {
3292     - $junkcount{ $user->key } = $found;
3293     - }
3294     - }
3295     - my $i = keys %junkcount;
3296     - if ( $i > 0 ) {
3297     - print("\nJunk Mails left in folder:\n");
3298     - print("---------------------------\n");
3299     - print("Count\tUser\n");
3300     - print("-------------------------\n");
3301     - foreach my $thisuser (
3302     - sort { $junkcount{$b} <=> $junkcount{$a} }
3303     - keys %junkcount
3304     - )
3305     - {
3306     - printf "%d", $junkcount{$thisuser};
3307     - print "\t" . $thisuser . "\n";
3308     - }
3309     - print("-------------------------\n");
3310     - }
3311     - else {
3312     - print "***No junkmail folders with emails***\n";
3313     - }
3314     -}
3315     -
3316     -sub show_virus_variants
3317     -
3318     -#
3319     -# Show a league table of the different virus types found today
3320     -#
3321     -
3322     -{
3323     - my $line = "------------------------------------------------------------------------\n";
3324     - print("\nVirus Statistics by name:\n");
3325     - print($line);
3326     - foreach my $virus (sort { $found_viruses{$b} <=> $found_viruses{$a} }
3327     - keys %found_viruses)
3328     - {
3329     - if (index($virus,"Sanesecurity") !=-1 or index($virus,"UNOFFICIAL") !=-1){
3330     - print "Rejected $found_viruses{$virus}\thttp://sane.mxuptime.com/s.aspx?id=$virus\n";
3331     - } else {
3332     - print "Rejected $found_viruses{$virus}\t$virus\n";
3333     - }
3334     -
3335     - }
3336     - print($line);
3337     -}
3338     -
3339     -sub show_qpsmtpd_codes
3340     -
3341     -#
3342     -# Show a league table of the qpsmtpd result codes found today
3343     -#
3344     -
3345     -{
3346     - my $line = "---------------------------------------------\n";
3347     - print("\nQpsmtpd codes league table:\n");
3348     - print($line);
3349     - print("Count\tPercent\tReason\n");
3350     - print($line);
3351     - foreach my $qpcode (sort { $found_qpcodes{$b} <=> $found_qpcodes{$a} }
3352     - keys %found_qpcodes)
3353     - {
3354     - print "$found_qpcodes{$qpcode}\t".sprintf('%4.1f',$found_qpcodes{$qpcode}*100/$totalexamined)."%\t\t$qpcode\n" if $totalexamined;
3355     - }
3356     - print($line);
3357     -}
3358     -
3359     -sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
3360     -
3361     -sub get_domain
3362     -{ my $url = shift;
3363     - $url =~ s!^\(dnsbl\)\s!!;
3364     - $url =~ s!^.*https?://(?:www\.)?!!i;
3365     - $url =~ s!/.*!!;
3366     - $url =~ s/[\?\#\:].*//;
3367     - $url =~ s/^([\d]{1,3}.){4}//;
3368     - my $domain = trim($url);
3369     - return $domain;
3370     -}
3371     -
3372     -sub show_blacklist_counts
3373     -
3374     -#
3375     -# Show a sorted league table of the blacklist URL counts
3376     -#
3377     -
3378     -{
3379     - my $line = "------------------\n";
3380     - print("\nBlacklist details:\n");
3381     - print($line);
3382     - if ($cdb->get('qpsmtpd')->prop("RHSBL") eq "enabled") {print "RBLLIST:".$cdb->get('qpsmtpd')->prop("RBLList")."\n";}
3383     - if ($cdb->get('qpsmtpd')->prop("URIBL") eq "enabled") {print "UBLLIST:".$cdb->get('qpsmtpd')->prop("UBLList")."\n";}
3384     - if (!$cdb->get('qpsmtpd')->prop("SBLList") eq "") {print "SBLLIST:".$cdb->get('qpsmtpd')->prop("SBLList")."\n";}
3385     - print($line);
3386     - print("Count\tURL\n");
3387     - print($line);
3388     - foreach my $blcode (sort { $blacklistURL{$b} <=> $blacklistURL{$a} }
3389     - keys %blacklistURL)
3390     - {
3391     - print sprintf('%3u',$blacklistURL{$blcode})."\t$blcode\n";
3392     - }
3393     - print($line);
3394     -}
3395     -
3396     -
3397     -sub show_user_stats
3398     -
3399     -#
3400     -# Show a sorted league table of the user counts
3401     -#
3402     -
3403     -{
3404     - #Compute totals for each entry
3405     - my $grandtotals=0;
3406     - my $totalqueued=0;
3407     - my $totalspam=0;
3408     - my $totalrejected=0;
3409     - foreach my $user (keys %usercounts){
3410     - $usercounts{$user}{"queued"} = 0 if !(exists $usercounts{$user}{"queued"});
3411     - $usercounts{$user}{"rejected"} = 0 if !(exists $usercounts{$user}{"rejected"});
3412     - $usercounts{$user}{"spam"} = 0 if !(exists $usercounts{$user}{"spam"});
3413     - $usercounts{$user}{"totals"} = $usercounts{$user}{"queued"}+$usercounts{$user}{"rejected"}+$usercounts{$user}{"spam"};
3414     - $grandtotals += $usercounts{$user}{"totals"};
3415     - $totalspam += $usercounts{$user}{"spam"};
3416     - $totalqueued += $usercounts{$user}{"queued"};
3417     - $totalrejected += $usercounts{$user}{"rejected"};
3418     - }
3419     - my $line = "--------------------------------------------------\n";
3420     - print("\nStatistics by email address received:\n");
3421     - print($line);
3422     - print("Queued\tRejected\tSpam tagged\tEmail Address\n");
3423     - print($line);
3424     - foreach my $user (sort { $usercounts{$b}{"totals"} <=> $usercounts{$a}{"totals"} }
3425     - keys %usercounts)
3426     - {
3427     - print sprintf('%3u',$usercounts{$user}{"queued"})."\t".sprintf('%3u',$usercounts{$user}{"rejected"})."\t\t".sprintf('%3u',$usercounts{$user}{"spam"})."\t\t$user\n";
3428     - }
3429     - print($line);
3430     - print sprintf('%3u',$totalqueued)."\t".sprintf('%3u',$totalrejected)."\t\t".sprintf('%3u',$totalspam)."\n";
3431     - print($line);
3432     -
3433     -
3434     -}
3435     -
3436     -sub show_Geoip_results
3437     -#
3438     -# Show league table of GEoip results
3439     -#
3440     -{
3441     -
3442     - my ($percentthreshold);
3443     - my ($reject);
3444     - my ($percent);
3445     - my ($totalpercent)=0;
3446     - if ($cdb->get('mailstats')){
3447     - $percentthreshold = $cdb->get('mailstats')->prop("GeoipCutoffPercent") || 0.5;
3448     - } else {
3449     - $percentthreshold = 0.5;
3450     - }
3451     - if ($total_countries > 0) {
3452     - my $line = "---------------------------------------------\n";
3453     - print("\nGeoip results: (cutoff at $percentthreshold%) \n");
3454     - print($line);
3455     - print("Country\tPercent\tCount\tRejected?\n");
3456     - print($line);
3457     - foreach my $country (sort { $found_countries{$b} <=> $found_countries{$a} }
3458     - keys %found_countries)
3459     - {
3460     - $percent = $found_countries{$country} * 100 / $total_countries
3461     - if $total_countries;
3462     - $totalpercent = $totalpercent + $percent;
3463     - if (index($BadCountries, $country) != -1) {$reject = "*";} else { $reject = " ";}
3464     - if ( $percent >= $percentthreshold ) {
3465     - print "$country\t\t"
3466     - . sprintf( '%4.1f', $percent )
3467     - . "%\t\t$found_countries{$country}","\t$reject\n"
3468     - if $total_countries;
3469     - }
3470     -
3471     - }
3472     - print($line);
3473     - my ($showtotals);
3474     - if ($cdb->get('mailstats')){
3475     - $showtotals = ((($cdb->get('mailstats')->prop("ShowLeagueTotals")|| 'yes')) eq "yes");
3476     - } else {
3477     - $showtotals = $true;
3478     - }
3479     -
3480     - if ($showtotals){
3481     - print "TOTALS\t\t".sprintf("%4.1f",$totalpercent)."%\t\t$total_countries\n";
3482     - print($line);
3483     - }
3484     - }
3485     -}
3486     -
3487     -sub show_SARules_codes
3488     -
3489     -#
3490     -# Show a league table of the SARules result codes found today
3491     -# suppress any lower than DB mailstats/SARulePercentThreshold
3492     -#
3493     -
3494     -{
3495     - my ($percentthreshold);
3496     - my ($defaultpercentthreshold);
3497     - my ($totalpercent) = 0;
3498     -
3499     - if ($sum_SARules > 0){
3500     -
3501     - if ($totalexamined >0 and $sum_SARules*100/$totalexamined > $SARulethresholdPercent) {
3502     - $defaultpercentthreshold = $maxcutoff
3503     - } else {
3504     - $defaultpercentthreshold = $mincutoff
3505     - }
3506     - if ($cdb->get('mailstats')){
3507     - $percentthreshold = $cdb->get('mailstats')->prop("SARulePercentThreshold") || $defaultpercentthreshold;
3508     - } else {
3509     - $percentthreshold = $defaultpercentthreshold
3510     - }
3511     - my $line = "---------------------------------------------\n";
3512     - print("\nSpamassassin Rules:(cutoff at ".sprintf('%4.1f',$percentthreshold)."%)\n");
3513     - print($line);
3514     - print("Count\tPercent\tScore\t\t\n");
3515     - print($line);
3516     - foreach my $SARule (sort { $found_SARules{$b}{'count'} <=> $found_SARules{$a}{'count'} }
3517     - keys %found_SARules)
3518     - {
3519     - my $percent = $found_SARules{$SARule}{'count'} * 100 / $totalexamined if $totalexamined;
3520     - my $avehits = $found_SARules{$SARule}{'totalhits'} /
3521     - $found_SARules{$SARule}{'count'}
3522     - if $found_SARules{$SARule}{'count'};
3523     - if ( $percent >= $percentthreshold ) {
3524     - print "$found_SARules{$SARule}{'count'}\t"
3525     - . sprintf( '%4.1f', $percent ) . "%\t"
3526     - . sprintf( '%4.1f', $avehits )
3527     - . "\t$SARule\n"
3528     - if $totalexamined;
3529     - }
3530     - }
3531     - print($line);
3532     - my ($showtotals);
3533     - if ($cdb->get('mailstats')){
3534     - $showtotals = ((($cdb->get('mailstats')->prop("ShowLeagueTotals")|| 'yes')) eq "yes");
3535     - } else {
3536     - $showtotals = $true;
3537     - }
3538     -
3539     - if ($showtotals){
3540     - print "$totalexamined\t(TOTALS)\n";
3541     - print($line);
3542     - }
3543     - print "\n";
3544     - }
3545     -
3546     -
3547     -}
3548     -
3549     -sub mark_domain_rejected
3550     -
3551     -#
3552     -# Tag domain as having a rejected email
3553     -#
3554     -{
3555     -my ($proc) = @_;
3556     -if ( ( $currentrcptdomain{ $proc } || '' ) ne '' ) {
3557     - $byrcptdomain{ $currentrcptdomain{ $proc } }{ 'deny' }++ ;
3558     - $currentrcptdomain{ $proc } = '' ;
3559     - }
3560     -}
3561     -
3562     -sub mark_domain_err
3563     -
3564     - #
3565     - # Tag domain as having an error on email transfer
3566     - #
3567     -{
3568     - my ($proc) = @_;
3569     - if ( ( $currentrcptdomain{$proc} || '' ) ne '' ) {
3570     - $byrcptdomain{ $currentrcptdomain{$proc} }{'xfer'}++;
3571     - $currentrcptdomain{$proc} = '';
3572     - }
3573     -}
3574     -
3575     -sub add_in_domain
3576     -
3577     - #
3578     - # add recipient domain into hash
3579     - #
3580     -{
3581     - my ($proc) = @_;
3582     -
3583     - #split to just domain bit.
3584     - $currentrcptdomain{$proc} =~ s/.*@//;
3585     - $currentrcptdomain{$proc} =~ s/[^\w\-\.]//g;
3586     - $currentrcptdomain{$proc} =~ s/>//g;
3587     - my $NotableDomain = 0;
3588     - if ( defined( $byrcptdomain{ $currentrcptdomain{$proc} }{'type'} ) ) {
3589     - $NotableDomain = 1;
3590     - }
3591     - else {
3592     - foreach (@extdomain) {
3593     - if ( $currentrcptdomain{$proc} =~ m/$_$/ ) {
3594     - $NotableDomain = 1;
3595     - last;
3596     - }
3597     - }
3598     - }
3599     - if ( !$NotableDomain ) {
3600     -
3601     - # check for outgoing email
3602     - if ( $localflag == 1 ) { $currentrcptdomain{$proc} = 'Outgoing' }
3603     - else { $currentrcptdomain{$proc} = 'Others' }
3604     - }
3605     - else {
3606     - if ( $localflag == 1 ) { $currentrcptdomain{$proc} = 'Internal' }
3607     - }
3608     - $byrcptdomain{ $currentrcptdomain{$proc} }{'total'}++;
3609     -}
3610     -
3611     -sub save_data
3612     -
3613     - #
3614     - # Save the data to a MySQL database
3615     - #
3616     -{
3617     - use DBI;
3618     - my $tstart = time;
3619     - my $DBname = "mailstats";
3620     - my $host = esmith::ConfigDB->open_ro->get('mailstats')->prop('DBHost') || "localhost";
3621     - my $port = esmith::ConfigDB->open_ro->get('mailstats')->prop('DBPort') || "3306";
3622     - print "Saving data..";
3623     - my $dbh = DBI->connect( "DBI:mysql:database=$DBname;host=$host;port=$port",
3624     - "mailstats", "mailstats" )
3625     - or die "Cannot open mailstats db - has it beeen created?";
3626     -
3627     - my $hour = floor( $start / 3600 );
3628     - my $reportdate = strftime( "%F", localtime( $hour * 3600 ) );
3629     - my $dateid = get_dateid($dbh,$reportdate);
3630     - my $reccount = 0; #count number of records written
3631     - my $servername = esmith::ConfigDB->open_ro->get('SystemName')->value . "."
3632     - . esmith::ConfigDB->open_ro->get('DomainName')->value;
3633     - # now fill in day related stats - must always check for it already there
3634     - # incase the module is run more than once in a day
3635     - my $SAScoresid = check_date_rec($dbh,"SAscores",$dateid,$servername);
3636     - $dbh->do( "UPDATE SAscores SET ".
3637     - "acceptedcount=".$spamcount.
3638     - ",rejectedcount=".$above15.
3639     - ",hamcount=".$hamcount.
3640     - ",acceptedscore=".$spamhits.
3641     - ",rejectedscore=".$rejectspamhits.
3642     - ",hamscore=".$hamhits.
3643     - ",totalsmtp=".$totalexamined.
3644     - ",totalrecip=".$recipcount.
3645     - ",servername='".$servername.
3646     - "' WHERE SAscoresid =".$SAScoresid);
3647     - # Junkmail stats
3648     - # delete if already there
3649     - $dbh->do("DELETE from JunkMailStats WHERE dateid = ".$dateid." AND servername='".$servername."'");
3650     - # and add records
3651     - foreach my $thisuser (keys %junkcount){
3652     - $dbh->do("INSERT INTO JunkMailStats (dateid,user,count,servername) VALUES ('".
3653     - $dateid."','".$thisuser."','".$junkcount{$thisuser}."','".$servername."')");
3654     - $reccount++;
3655     - }
3656     - #SA rules - delete any first
3657     - $dbh->do("DELETE from SARules WHERE dateid = ".$dateid." AND servername='".$servername."'");
3658     - # and add records
3659     - foreach my $thisrule (keys %found_SARules){
3660     - $dbh->do("INSERT INTO SARules (dateid,rule,count,totalhits,servername) VALUES ('".
3661     - $dateid."','".$thisrule."','".$found_SARules{$thisrule}{'count'}."','".
3662     - $found_SARules{$thisrule}{'totalhits'}."','".$servername."')");
3663     - $reccount++;
3664     - }
3665     - #qpsmtpd result codes
3666     - $dbh->do("DELETE from qpsmtpdcodes WHERE dateid = ".$dateid." AND servername='".$servername."'");
3667     - # and add records
3668     - foreach my $thiscode (keys %found_qpcodes){
3669     - $dbh->do("INSERT INTO qpsmtpdcodes (dateid,reason,count,servername) VALUES ('".
3670     - $dateid."','".$thiscode."','".$found_qpcodes{$thiscode}."','".$servername."')");
3671     - $reccount++;
3672     -}
3673     - # virus stats
3674     - $dbh->do("DELETE from VirusStats WHERE dateid = ".$dateid." AND servername='".$servername."'");
3675     - # and add records
3676     - foreach my $thisvirus (keys %found_viruses){
3677     - $dbh->do("INSERT INTO VirusStats (dateid,descr,count,servername) VALUES ('".
3678     - $dateid."','".$thisvirus."','".$found_viruses{$thisvirus}."','".$servername."')");
3679     - $reccount++;
3680     -
3681     - }
3682     - # domain details
3683     - $dbh->do("DELETE from domains WHERE dateid = ".$dateid." AND servername='".$servername."'");
3684     - # and add records
3685     - foreach my $domain (keys %byrcptdomain){
3686     - next if ( ( $byrcptdomain{$domain}{'total'} || 0 ) == 0 );
3687     - $dbh->do("INSERT INTO domains (dateid,domain,type,total,denied,xfererr,accept,servername) VALUES ('".
3688     - $dateid."','".$domain."','".($byrcptdomain{$domain}{'type'}||'other')."','"
3689     - .$byrcptdomain{$domain}{'total'}."','"
3690     - .($byrcptdomain{$domain}{'deny'}||0)."','"
3691     - .($byrcptdomain{$domain}{'xfer'}||0)."','"
3692     - .($byrcptdomain{$domain}{'accept'}||0)."','"
3693     - .$servername
3694     - ."')");
3695     - $reccount++;
3696     -
3697     - }
3698     - # finally - the hourly breakdown
3699     - # need to remember here that the date might change during the 24 hour span
3700     - my $nhour = floor( $start / 3600 );
3701     - my $ncateg;
3702     - while ( $nhour < $end / 3600 ) {
3703     - #see if the time record has been created
3704     - # print strftime("%H",localtime( $nhour * 3600 ) ).":00:00\n";
3705     - my $sth =
3706     - $dbh->prepare( "SELECT timeid FROM time WHERE time = '" . strftime("%H",localtime( $nhour * 3600 ) ).":00:00'");
3707     - $sth->execute();
3708     - if ( $sth->rows == 0 ) {
3709     - #create entry
3710     - $dbh->do( "INSERT INTO time (time) VALUES ('" .strftime("%H",localtime( $nhour * 3600 ) ).":00:00')" );
3711     - # and pick up timeid
3712     - $sth = $dbh->prepare("SELECT last_insert_id() AS timeid FROM time");
3713     - $sth->execute();
3714     - $reccount++;
3715     - }
3716     - my $timerec = $sth->fetchrow_hashref();
3717     - my $timeid = $timerec->{"timeid"};
3718     - $ncateg = 0;
3719     - # and extract date from first column of $count array
3720     - my $currentdate = strftime( "%F", localtime( $hour * 3600 ) );
3721     - # print "$currentdate.\n";
3722     - if ($currentdate ne $reportdate) {
3723     - #same as before?
3724     - $dateid = get_dateid($dbh,$currentdate);
3725     - $reportdate = $currentdate;
3726     - }
3727     - # delete for this date and time
3728     - $dbh->do("DELETE from ColumnStats WHERE dateid = ".$dateid." AND timeid = ".$timeid." AND servername='".$servername."'");
3729     - while ( $ncateg < @categs-1 ) {
3730     - # then add in each entry
3731     - if (($counts{$nhour}{$categs[$ncateg]} || 0) != 0) {
3732     - $dbh->do("INSERT INTO ColumnStats (dateid,timeid,descr,count,servername) VALUES ("
3733     - .$dateid.",".$timeid.",'".$categs[$ncateg]."',"
3734     - .$counts{$nhour}{$categs[$ncateg]}.",'".$servername."')");
3735     - $reccount++;
3736     - }
3737     -
3738     -# print("INSERT INTO ColumnStats (dateid,timeid,descr,count) VALUES ("
3739     -# .$dateid.",".$timeid.",'".$categs[$ncateg]."',"
3740     -# .$counts{$nhour}{$categs[$ncateg]}.")\n");
3741     -
3742     - $ncateg++;
3743     - }
3744     - $nhour++;
3745     - }
3746     - # and write out the log lines saved - only if html wanted
3747     - if ($makeHTMLemail eq 'yes' or $makeHTMLemail eq 'both' or $makeHTMLpage eq 'yes'){
3748     - foreach my $logid (keys %LogLines){
3749     - $reccount++;
3750     - #Extract from keys
3751     - my $extract = $logid;
3752     - $extract =~/^(.*)-(.*):(.*)$/;
3753     - my $Log64n = $1;
3754     - my $LogMailId = $2;
3755     - my $LogSeq = $3;
3756     - my $LogLine = $dbh->quote($LogLines{$logid});
3757     - my $sql = "INSERT INTO LogData (Log64n,MailID,Sequence,LogStr) VALUES ('";
3758     - $sql .= $Log64n."','".$LogMailId."','".$LogSeq."',".$LogLine.")";
3759     - $dbh->do($sql) or die($sql);
3760     - }
3761     - $dbh->disconnect();
3762     - $telapsed = time - $tstart;
3763     - print "Saved $reccount records in $telapsed sec.";
3764     - }
3765     -}
3766     -
3767     -sub check_date_rec
3768     -
3769     - #
3770     - # check that a specific dated rec is there, create if not
3771     - #
3772     -{
3773     - my ( $dbh, $table, $dateid ) = @_;
3774     - my $sth =
3775     - $dbh->prepare(
3776     - "SELECT " . $table . "id FROM ".$table." WHERE dateid = '$dateid'" );
3777     - $sth->execute();
3778     - if ( $sth->rows == 0 ) {
3779     - #create entry
3780     - $dbh->do( "INSERT INTO ".$table." (dateid) VALUES ('" . $dateid . "')" );
3781     - # and pick up recordid
3782     - $sth = $dbh->prepare("SELECT last_insert_id() AS ".$table."id FROM ".$table);
3783     - $sth->execute();
3784     - }
3785     - my $rec = $sth->fetchrow_hashref();
3786     - $rec->{$table."id"}; #return the id of the reocrd (new or not)
3787     - }
3788     -
3789     - sub check_time_rec
3790     -
3791     - #
3792     - # check that a specific dated amd timed rec is there, create if not
3793     - #
3794     -{
3795     - my ( $dbh, $table, $dateid, $timeid ) = @_;
3796     - my $sth =
3797     - $dbh->prepare(
3798     - "SELECT " . $table . "id FROM ".$table." WHERE dateid = '$dateid' AND timeid = ".$timeid );
3799     - $sth->execute();
3800     - if ( $sth->rows == 0 ) {
3801     - #create entry
3802     - $dbh->do( "INSERT INTO ".$table." (dateid,timeid) VALUES ('" . $dateid . "', '".$timeid."')" );
3803     - # and pick up recordid
3804     - $sth = $dbh->prepare("SELECT last_insert_id() AS ".$table."id FROM ".$table);
3805     - $sth->execute();
3806     - }
3807     - my $rec = $sth->fetchrow_hashref();
3808     - $rec->{$table."id"}; #return the id of the record (new or not)
3809     - }
3810     -
3811     -sub get_dateid
3812     -
3813     -#
3814     -# Check that date is in db, and return corresponding id
3815     -#
3816     -{
3817     - my ($dbh,$reportdate) = @_;
3818     - my $sth =
3819     - $dbh->prepare( "SELECT dateid FROM date WHERE date = '" . $reportdate."'" );
3820     - $sth->execute();
3821     - if ( $sth->rows == 0 ) {
3822     - #create entry
3823     - $dbh->do( "INSERT INTO date (date) VALUES ('" . $reportdate . "')" );
3824     - # and pick up dateid
3825     - $sth = $dbh->prepare("SELECT last_insert_id() AS dateid FROM date");
3826     - $sth->execute();
3827     - }
3828     - my $daterec = $sth->fetchrow_hashref();
3829     - $daterec->{"dateid"};
3830     - }
3831     -
3832     - sub dump_entries
3833     - {
3834     - my $msg = shift;
3835     - #dbg($msg);
3836     - #dbg("TM0:".$timestamp_items[0]);
3837     - #dbg("TM1:".$timestamp_items[1]);
3838     - #dbg("TM2:".$timestamp_items[2]);
3839     - #dbg("TM3:".$timestamp_items[3]);
3840     - #dbg("LOG0:".$log_items[0]);
3841     - #dbg("LOG1:".$log_items[1]);
3842     - #dbg("LOG2:".$log_items[2]);
3843     - #dbg("LOG3:".$log_items[3]);
3844     - #dbg("LOG4:".$log_items[4]);
3845     - #dbg("LOG5:".$log_items[5]);
3846     - #dbg("LOG6:".$log_items[6]);
3847     - #dbg("LOG7:".$log_items[7]);
3848     - #if ($opt{debug} == 1){exit;}
3849     -}
3850     -
3851     -#sub test_for_private_ip {
3852     - #use NetAddr::IP;
3853     - #my $ip = shift;
3854     - #$ip =~ s/^\D*(([0-9]{1,3}\.){3}[0-9]{1,3}).*/$1/e;
3855     - #print "\nIP:$ip";
3856     - #my $nip = NetAddr::IP->new($ip);
3857     - #if ($nip){
3858     - #if ( $nip->is_rfc1918() ){
3859     - #return 1;
3860     - #} else { return 0}
3861     - #} else { return 0}
3862     -#}
3863     -
3864     -
3865     -sub test_for_private_ip {
3866     - use NetAddr::IP;
3867     - $_ = shift;
3868     - return unless /(\d+\.\d+\.\d+\.\d+)/;
3869     - my $ip = NetAddr::IP->new($1);
3870     - return unless $ip;
3871     - return $ip->is_rfc1918();
3872     -}
3873     -
3874     -

admin@koozali.org
ViewVC Help
Powered by ViewVC 1.2.1 RSS 2.0 feed