/[smecontribs]/rpms/smeserver-mailstats/contribs7/smeserver-mailstats-0.0.3-update04.patch
ViewVC logotype

Annotation of /rpms/smeserver-mailstats/contribs7/smeserver-mailstats-0.0.3-update04.patch

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


Revision 1.1 - (hide annotations) (download)
Sun Apr 27 13:50:22 2008 UTC (16 years ago) by brianread
Branch: MAIN
CVS Tags: smeserver-mailstats-0_0_3-11_el4_sme, smeserver-mailstats-0_0_3-10_el4_sme, smeserver-mailstats-0_0_3-13_el4_sme, smeserver-mailstats-0_0_3-12_el4_sme, smeserver-mailstats-0_0_3-7_el4_sme, smeserver-mailstats-0_0_3-6_el4_sme, smeserver-mailstats-0_0_3-9_el4_sme, smeserver-mailstats-0_0_3-8_el4_sme
Fix for bug [SME:3734]

1 brianread 1.1 --- smeserver-mailstats-0.0.3/root/usr/bin/spamfilter-stats-7.pl.update04 2008-04-27 12:55:29.000000000 +0100
2     +++ smeserver-mailstats-0.0.3/root/usr/bin/spamfilter-stats-7.pl 2008-04-27 13:43:22.000000000 +0100
3     @@ -1,1541 +1,1542 @@
4     -#!/usr/bin/perl -w
5     -
6     -#############################################################################
7     -#
8     -# This script provides daily SpamFilter statistics and deletes all users
9     -# junkmails. Configuration of the script is done by the Spam Filter
10     -# Server-Manager module
11     -#
12     -# April 2006 - no longer controlled by server manager, and does not delete files
13     -#
14     -# This script has been developed
15     -# by Jesper Knudsen at http://sme.swerts-knudsen.dk
16     -#
17     -# Revision History:
18     -#
19     -# August 13, 2003: Initial version
20     -# August 25, 2004: fixed problem when hostname had no-ASCII chars
21     -# March 23, 2006 Revised for sme7 RM
22     -# March 27, 2006 ditto BJR (http://www.abandonmicrosoft.co.uk)
23     -# - Merged Clamav and SA stats
24     -# - Moved all analysis to qsmtpd log
25     -# - Removed parameterised interval (for simplicity - not sure of format anyway)
26     -# - add in archived log files for people who have high turnover
27     -# - Alter labels to be more accurate
28     -# - Detect deleted spam (over threshold) without using spam score
29     -# - Detect RBL rejections
30     -# - Detect pattern (executible) rejections
31     -# - Look for the DENY labels - add in Miscellaneous category
32     -# April 6, 2006 - check qpsmtp log level and also DNS enable properties
33     -# - Average spam scores for under and over threshold seperatly
34     -# - Log tag and Reject levels
35     -# - TBD - check that RBL DENY are being detected (I have no date to check this)
36     -# April 7, 2007 - re-written by Charlie Brady totally in Perl
37     -# April 16, 2006 - move warnings to report
38     -# - Spot fetchmail deliveries
39     -# - Spot Internal connections from client PCs
40     -# - TBD check that RBL DENY are being detected (I have no data to check this)
41     -# April 30, 2006 - Pascal Schirrmann Start Time and End Time to noon - should be a param
42     -# so the script can be run at any time in the day.
43     -# - adds 'by recipients domains' stats Useful for MX-Backup or multi domains hosts
44     -# - Add a 'recipients per mail' stat. Useful : until now the sums are correct :-)
45     -# - Correct some messages about rbl who can led to wrong entry in the config database
46     -# ( and without expected results, of course !)
47     -# - improve a regexp in the SPAM detection
48     -# May 1, 2006 - BJR - Fix situation where mxbackup prop is not defined
49     -# - fix a spelling and minor format of domain report
50     -# May 9, 2006 - bjr - Make RBL percentage a percentage of total connections (else it >100%)
51     -# May 9, 2006 - ps - some 'sanity check' in the 'per domains part of the stats (to avoid / 0)
52     -# May 12, 2006 - ps - some cleanup in the 'per domains' stats
53     -# - Add a version number, logged in the mail
54     -# June 20, 2006 - bjr - Minor change to RBL instructions, and adjust domain table format
55     -# Feb 19, 2007 - bjr - Adjust table lines oin a couple of places
56     -# - bjr - and add documentation details about percentages etc
57     -# - bjr - Alter misc to "non conforming" anmd accumulated these hourly
58     -# - bjr - Express change over tag count to exclude spam rejected over threshold
59     -# - bjr - Change "processsed" to "fully downloaded"
60     -# - bjr - Change percentages so that they are all a percetnage of the total emails received
61     -# 0.6.1 - bjr - Change to use output from the logterse qpsmtpd plugin
62     -# 0.6.2 - bjr - Fix fetchmail tests
63     -# 0.6.3 - bjr - adjust for log-items change in order
64     -# 0.6.4&5 - bjr - Adjust table formatting
65     -# 0.6.6 - bjr - Take outgoing emails out of "others", add "Outgoing" and "Internal"
66     -# 0.6.7 - bjr - Fix missing plugins/wrong names. pull invalid recipient out of deny msg for goodrcptto
67     -# 0.6.8 - bjr - catch a few more plugin name failures
68     -# 0.6.9 - bjr - Catch webmail and mailman
69     -# 0.6.10 - bjr - Refine Webmail identification
70     -# 0.6.11 - bjr - Fix Webmail identification
71     -# 0.6.12 - bjr - split logterse line a bit more carefully (multiple sent to addresss with space and comma confuse it)
72     -# 0.6.13 - bjr - add totals and percentages to bottom of the table
73     -# - Generalise counts so that columns can be brought in and out
74     -# - control columns with Db entries
75     -# 0.6.14 - bjr - Add in league tables of qpsmtpd codes and SA rules
76     -# - Add in loglevel check
77     -# - parameterise email address for report
78     -# 0.6.15 - bjr - fix columns included in totals
79     -# - sort out domains when more that one email address in recipient field
80     -# 0.6.16 - cb - fix date range bug (http://bugs.contribs.org/show_bug.cgi?id=3366)
81     -# 0.6.17 - cb - avoid numerous re-openings of config db
82     -# 0.6.18 - cb - tidy up options configuration section
83     -# 0.6.19 - cb - rename parse_args => analysis_period, and simplify
84     -# 0.6.20 - bjr - Retofit bjr fixes since file edited by charlie - Details
85     -# - Add Average SA Scores to SA league table,
86     -# - sort junkmail counts, sorted out xfererr for domains
87     -# - Fixed multiple recipients for single emails
88     -# - Fix Report suppression code for qpsmtpd codes etc
89     -# - Added code to save stats to MySQL DB (defaulted to off)
90     -# - Fixed interval so that it analyzes Midnight to midnight
91     -# - Allow varied interval for report
92     -# 0.6.21 - bjr - Move initial test (and create) for mailstats prop before
93     -# first reference to mailstats
94     -#
95     -# TODO
96     -# ----
97     -#
98     -# sort out multiple emails recipients, count each one, and log multiple counts
99     -#
100     -#
101     -#
102     -#############################################################################
103     -#
104     -# SMEServer DB usage
105     -# ------------------
106     -#
107     -# mailstats / Status ("enabled"|"disabled")
108     -# / <column header> ("yes"|"no"|"auto") - enable, supress or only show if nonzero
109     -# / QpsmtpdCodes ("enabled"|"disabled")
110     -# / SARules ("enabled"|"disabled")
111     -# / JunkMailList ("enabled"|"disabled")
112     -# / SARulePercentThreshold (0.5) - threshold of SArules percentage for report cutoff
113     -# / Email (admin) - email to send report
114     -# / SaveDataToMySQL - save data to MySQL database (default is "no")
115     -# / DBHost - MySQL server hostname (default is "localhost").
116     -# / DBPort - MySQL server post (default is "3306")
117     -# / Interval - "day", "week", "fortnight", "month", "99999" - last is number of seconds (default is day)
118     -# / Base - "Midnight", "Midday", "Now", "99" hour (0-23) (default is midnight)
119     -#
120     -#############################################################################
121     -#
122     -# Table structure for MySQL table for saving data
123     -#
124     -# Database : `mailstats`
125     -#
126     -# use mailstats;
127     -# --------------------------------------------------------
128     -
129     -#
130     -# Table structure for table `ColumnStats`
131     -#
132     -#
133     -#CREATE TABLE `ColumnStats` (
134     -# `ColumnStatsid` int(11) NOT NULL auto_increment,
135     -# `dateid` int(11) NOT NULL default '0',
136     -# `timeid` int(11) NOT NULL default '0',
137     -# `descr` varchar(20) NOT NULL default '',
138     -# `count` bigint(20) NOT NULL default '0',
139     -# `servername` varchar(30) NOT NULL default '',
140     -# PRIMARY KEY (`ColumnStatsid`)
141     -#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
142     -
143     -# --------------------------------------------------------
144     -
145     -#
146     -# Table structure for table `JunkMailStats`
147     -#
148     -
149     -#CREATE TABLE `JunkMailStats` (
150     -# `JunkMailstatsid` int(11) NOT NULL auto_increment,
151     -# `dateid` int(11) NOT NULL default '0',
152     -# `user` varchar(12) NOT NULL default '',
153     -# `count` bigint(20) NOT NULL default '0',
154     -# `servername` varchar(30) default NULL,
155     -# PRIMARY KEY (`JunkMailstatsid`)
156     -#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
157     -#
158     -# --------------------------------------------------------
159     -
160     -#
161     -# Table structure for table `SARules`
162     -#
163     -
164     -#CREATE TABLE `SARules` (
165     -# `SARulesid` int(11) NOT NULL auto_increment,
166     -# `dateid` int(11) NOT NULL default '0',
167     -# `rule` varchar(50) NOT NULL default '',
168     -# `count` bigint(20) NOT NULL default '0',
169     -# `totalhits` bigint(20) NOT NULL default '0',
170     -# `servername` varchar(30) NOT NULL default '',
171     -# PRIMARY KEY (`SARulesid`)
172     -#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
173     -
174     -# --------------------------------------------------------
175     -
176     -#
177     -# Table structure for table `SAscores`
178     -#
179     -
180     -#CREATE TABLE `SAscores` (
181     -# `SAscoresid` int(11) NOT NULL auto_increment,
182     -# `dateid` int(11) NOT NULL default '0',
183     -# `acceptedcount` bigint(20) NOT NULL default '0',
184     -# `rejectedcount` bigint(20) NOT NULL default '0',
185     -# `hamcount` bigint(20) NOT NULL default '0',
186     -# `acceptedscore` decimal(20,2) NOT NULL default '0.00',
187     -# `rejectedscore` decimal(20,2) NOT NULL default '0.00',
188     -# `hamscore` decimal(20,2) NOT NULL default '0.00',
189     -# `totalsmtp` bigint(20) NOT NULL default '0',
190     -# `totalrecip` bigint(20) NOT NULL default '0',
191     -# `servername` varchar(30) NOT NULL default '',
192     -# PRIMARY KEY (`SAscoresid`)
193     -#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
194     -
195     -# --------------------------------------------------------
196     -
197     -#
198     -# Table structure for table `VirusStats`
199     -#
200     -
201     -#CREATE TABLE `VirusStats` (
202     -# `VirusStatsid` int(11) NOT NULL auto_increment,
203     -# `dateid` int(11) NOT NULL default '0',
204     -# `descr` varchar(40) NOT NULL default '',
205     -# `count` bigint(20) NOT NULL default '0',
206     -# `servername` varchar(30) NOT NULL default '',
207     -# PRIMARY KEY (`VirusStatsid`)
208     -#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
209     -#
210     -# --------------------------------------------------------
211     -
212     -#
213     -# Table structure for table `date`
214     -#
215     -
216     -#CREATE TABLE `date` (
217     -# `dateid` int(11) NOT NULL auto_increment,
218     -# `date` date NOT NULL default '0000-00-00',
219     -# PRIMARY KEY (`dateid`)
220     -#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
221     -#
222     -# --------------------------------------------------------
223     -
224     -#
225     -# Table structure for table `domains`
226     -#
227     -
228     -#CREATE TABLE `domains` (
229     -# `domainsid` int(11) NOT NULL auto_increment,
230     -# `dateid` int(11) NOT NULL default '0',
231     -# `domain` varchar(40) NOT NULL default '',
232     -# `type` varchar(10) NOT NULL default '',
233     -# `total` bigint(20) NOT NULL default '0',
234     -# `denied` bigint(20) NOT NULL default '0',
235     -# `xfererr` bigint(20) NOT NULL default '0',
236     -# `accept` bigint(20) NOT NULL default '0',
237     -# `servername` varchar(30) NOT NULL default '',
238     -# PRIMARY KEY (`domainsid`)
239     -#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
240     -
241     -# --------------------------------------------------------
242     -
243     -#
244     -# Table structure for table `qpsmtpdcodes`
245     -#
246     -
247     -#CREATE TABLE `qpsmtpdcodes` (
248     -# `qpsmtpdcodesid` int(11) NOT NULL auto_increment,
249     -# `dateid` int(11) NOT NULL default '0',
250     -# `reason` varchar(40) NOT NULL default '',
251     -# `count` bigint(20) NOT NULL default '0',
252     -# `servername` varchar(30) NOT NULL default '',
253     -# PRIMARY KEY (`qpsmtpdcodesid`)
254     -#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
255     -
256     -# --------------------------------------------------------
257     -
258     -#
259     -# Table structure for table `time`
260     -#
261     -
262     -#CREATE TABLE `time` (
263     -# `timeid` int(11) NOT NULL auto_increment,
264     -# `time` time NOT NULL default '00:00:00',
265     -# PRIMARY KEY (`timeid`)
266     -#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
267     -#
268     -#############################################################################
269     -
270     -# internal modules (part of core perl distribution)
271     -use strict;
272     -use warnings;
273     -use Getopt::Long;
274     -use Pod::Usage;
275     -use POSIX qw/strftime floor/;
276     -use Time::Local;
277     -use Date::Manip;
278     -use Time::TAI64;
279     -use esmith::ConfigDB;
280     -use esmith::DomainsDB;
281     -use Sys::Hostname;
282     -use Switch;
283     -
284     -my $hostname = hostname();
285     -my $cdb = esmith::ConfigDB->open_ro or die "Couldn't open ConfigDB : $!\n";
286     -
287     -my $true = 1;
288     -my $false = 0;
289     -#and see if mailstats are disabled
290     -my $disabled;
291     -if ($cdb->get('mailstats')){
292     - $disabled = !(($cdb->get('mailstats')->prop('Status') || 'enabled') eq 'enabled');
293     -} else {
294     - my $db = esmith::ConfigDB->open; my $record = $db->new_record('mailstats', { type => 'report', Status => 'enabled', Email => 'admin' });
295     - $cdb = esmith::ConfigDB->open_ro or die "Couldn't open ConfigDB : $!\n"; #Open up again to pick up new record
296     - $disabled = $false;
297     -}
298     -
299     -#Configuration section
300     -my %opt = (
301     - version => '0.6.21', # please update at each change.
302     - debug => 0, # guess what ?
303     - sendmail => '/usr/sbin/sendmail', # Path to sendmail stub
304     - from => 'spamfilter-stats', # Who is the mail from
305     - mail => # mailstats email recipient
306     - $cdb->get('mailstats')->prop('Email') || 'admin',
307     - timezone => `date +%z`,
308     -);
309     -
310     -Date_Init("TZ=$opt{'timezone'}");
311     -
312     -my $FetchmailIP = '127.0.0.200'; #Apparent Ip address of fetchmail deliveries
313     -my $WebmailIP = '127.0.0.1'; #Apparent Ip of Webmail sender
314     -my $localhost = 'localhost'; #Apparent sender for webmail
315     -my $FETCHMAIL = 'FETCHMAIL'; #Sender from fetchmail when Ip address not 127.0.0.200 - when qpsmtpd denies the email
316     -my $MAILMAN = "bounces"; #sender when mailman sending when orig is localhost
317     -
318     -my $MinCol = 8; #Minimum column width
319     -my $HourColWidth = 16; #Date and time column width
320     -
321     -my $SARulethresholdPercent = 10; #If Sa rules less than this of total emails, then cutoff reduced
322     -my $maxcutoff = 1; #max percent cutoff applied
323     -my $mincutoff = 0.2; #min percent cutoff applied
324     -
325     -my $tstart = time;
326     -
327     -#Local variables
328     -my $YEAR = ( localtime(time) )[5]; # this is years since 1900
329     -
330     -my $total = 0;
331     -my $spamcount = 0;
332     -my $spamavg = 0;
333     -my $spamhits = 0;
334     -my $hamcount = 0;
335     -my $hamavg = 0;
336     -my $hamhits = 0;
337     -my $rejectspamavg = 0;
338     -my $rejectspamhits= 0;
339     -
340     -my $Accepttotal = 0;
341     -my $localAccepttotal = 0; #Fetchmail connections
342     -my $localsendtotal = 0; #Connections from local PCs
343     -my $totalexamined = 0; #total download + RBL etc
344     -my $WebMailsendtotal = 0; #total from Webmail
345     -my $mailmansendcount = 0; #total from mailman
346     -
347     -my %found_viruses = ();
348     -my %found_qpcodes = ();
349     -my %found_SARules = ();
350     -my %junkcount = ();
351     -
352     -# replaced by...
353     -my %counts = (); #Hold all counts in 2-D matrix
354     -my @display = (); #used to switch on and off columns - yes, no or auto for each category
355     -my @colwidth = (); #width of each column
356     - #(auto means only if non zero) - populated from possible db entries
357     -my @finaldisplay = (); #final decision on display or not - true or false
358     -
359     -#count column names, used for headings - also used for DB mailstats property names
360     -my $CATHOUR='Hour';
361     -my $CATFETCHMAIL='Fetchmail';
362     -my $CATWEBMAIL='WebMail';
363     -my $CATMAILMAN='Mailman';
364     -my $CATLOCAL='Local';
365     -# border between where it came from and where it ended..
366     -my $countfromhere = 5;
367     -
368     -my $CATVIRUS='Virus';
369     -my $CATRBLDNS='RBL/DNS';
370     -my $CATEXECUT='Execut.';
371     -my $CATNONCONF='Non.Conf.';
372     -my $CATSPAMDEL='Del.Spam';
373     -my $CATSPAM='Qued.Spam?';
374     -my $CATHAM='Ham';
375     -my $CATTOTALS='TOTALS';
376     -my $CATPERCENT='PERCENT';
377     -my @categs = ($CATHOUR,$CATFETCHMAIL,$CATWEBMAIL,$CATMAILMAN,$CATLOCAL,$CATVIRUS,$CATRBLDNS,$CATEXECUT,$CATNONCONF,$CATSPAMDEL,$CATSPAM,$CATHAM,$CATTOTALS,$CATPERCENT);
378     -my $GRANDTOTAL = '99'; #subs for count arrays, for grand total
379     -my $PERCENT = '98'; # for column percentages
380     -
381     -my $categlen = @categs-2; #-2 to avoid the total and percent column
382     -
383     -my $above15 = 0;
384     -my $RBLcount = 0;
385     -my $MiscDenyCount = 0;
386     -my $PatternFilterCount = 0;
387     -my $noninfectedcount = 0;
388     -my $okemailcount = 0;
389     -my $infectedcount = 0;
390     -my $warnnoreject = " ";
391     -my $rblnotset = ' ';
392     -
393     -my $FS = "\t"; # field separator used by logterse plugin
394     -my %log_items = ( "", "", "", "", "", "", "", "" );
395     -my $score;
396     -my %timestamp_items = ();
397     -my $localflag = 0; #indicate if current email is local or not
398     -my $WebMailflag = 0; #indicate if current mail is send from webmail
399     -
400     -# some storage for by recipient domains stats (PS)
401     -# my bad : I have to deal with multiple simoultaneous connections
402     -# will play with the process number.
403     -# my $currentrcptdomain = '' ;
404     -my %currentrcptdomain ; # temporay store the recipient domain until end of mail processing
405     -my %byrcptdomain ; # Store 'by domains stats'
406     -my @extdomain ; # only useful in some MX-Backup case, when any subdomains are allowed
407     -my $morethanonercpt = 0 ; # count every 'second' recipients for a mail.
408     -my $recipcount = 0; # count every recipient email address received.
409     -
410     -
411     -# store the domain of interest. Every other records are stored in a 'Other' zone
412     -my $ddb = esmith::DomainsDB->open_ro or die "Couldn't open DomainsDB : $!\n";
413     -
414     -foreach my $domain( $ddb->get_all_by_prop( type => "domain" ) ) {
415     - $byrcptdomain{ $domain->key }{ 'type' }='local';
416     -}
417     -$byrcptdomain{ $cdb->get('SystemName')->value . "."
418     - . $cdb->get('DomainName')->value }{ 'type' } = 'local';
419     -
420     -# is this system a MX-Backup ?
421     -if ($cdb->get('mxbackup')){
422     - if ( ( $cdb->get('mxbackup')->prop('status') || 'disabled' ) eq 'enabled' ) {
423     - my %MXValues = split( /,/, ( $cdb->get('mxbackup')->prop('name') || '' ) ) ;
424     - foreach my $data ( keys %MXValues ) {
425     - $byrcptdomain{ $data }{ 'type' } = "mxbackup-$MXValues{ $data }" ;
426     - if ( $MXValues{ $data } == 1 ) { # subdomains allowed, must take care of this
427     - push @extdomain, $data ;
428     - }
429     - }
430     - }
431     -}
432     -
433     -my ( $start, $end ) = analysis_period();
434     -
435     -#
436     -# First check current configuration for logging, DNS enable and Max threshold for spamassassin
437     -#
438     -
439     -my $LogLevel = $cdb->get('qpsmtpd')->prop('LogLevel');
440     -my $HighLogLevel = ( $LogLevel > 6 );
441     -
442     -my $RHSenabled =
443     - ( $cdb->get('qpsmtpd')->prop('RHSBL') eq 'enabled' );
444     -my $DNSenabled =
445     - ( $cdb->get('qpsmtpd')->prop('DNSBL') eq 'enabled' );
446     -my $SARejectLevel =
447     - $cdb->get('spamassassin')->prop('RejectLevel');
448     -my $SATagLevel =
449     - $cdb->get('spamassassin')->prop('TagLevel');
450     -my $DomainName =
451     - $cdb->get('DomainName')->value;
452     -
453     -# check that logterse is in use
454     -#my pluginfile = '/var/service/qpsmtpd/config/peers/0';
455     -
456     -if ( !$RHSenabled || !$DNSenabled ) {
457     - $rblnotset = '*';
458     -}
459     -
460     -if ( $SARejectLevel == 0 ) {
461     -
462     - $warnnoreject = "(*Warning* 0 = no reject)";
463     -
464     -}
465     -
466     -#
467     -#---------------------------------------
468     -# Scan the qpsmtpd log file
469     -#---------------------------------------
470     -
471     -
472     -# Init the hashes
473     -my $nhour = floor( $start / 3600 );
474     -my $ncateg;
475     -while ( $nhour < $end / 3600 ) {
476     - $counts{$nhour}=();
477     - $ncateg = 0;
478     - while ( $ncateg < @categs) {
479     - $counts{$nhour}{$categs[$ncateg-1]} = 0;
480     - $ncateg++
481     - }
482     - $nhour++;
483     -}
484     -# and grand totals and display status from db entries, and column widths
485     -$ncateg = 0;
486     -while ( $ncateg < @categs) {
487     - $counts{$GRANDTOTAL}{$categs[$ncateg]} = 0;
488     - if ($cdb->get('mailstats')){
489     - $display[$ncateg] = lc($cdb->get('mailstats')->prop($categs[$ncateg])) || "auto";
490     - } else {
491     - $display[$ncateg] = 'auto'
492     - }
493     - if ($ncateg == 0) {
494     - $colwidth[$ncateg] = $HourColWidth
495     - } else {
496     - $colwidth[$ncateg] = length($categs[$ncateg])+1
497     - }
498     - if ($colwidth[$ncateg] < $MinCol) {$colwidth[$ncateg] = $MinCol}
499     - $ncateg++
500     -}
501     -
502     -my $starttai = Time::TAI64::unixtai64n($start);
503     -my $endtai = Time::TAI64::unixtai64n($end);
504     -my $sum_SARules = 0;
505     -
506     -LINE: while (<>) {
507     - my($tai,$log) = split(' ',$_,2);
508     -
509     -
510     - #If date specified, only process lines matching date
511     - next LINE if ( $tai lt $starttai );
512     - last if ( $tai gt $endtai );
513     -
514     - # pull out spamasassin rule lists
515     - if ( $_ =~m/spamassassin plugin: check_spam:.*hits=(.*), required.*tests=(.*)/ )
516     - {
517     - my ($SAtests) = split(',',$2);
518     - foreach my $SAtest ($SAtests) {
519     - if (!$SAtest eq "") {
520     - $found_SARules{$SAtest}{'count'}++;
521     - $found_SARules{$SAtest}{'totalhits'} += $1;
522     - $sum_SARules++
523     - }
524     - }
525     -
526     - }
527     - #only select Logterse output
528     - next LINE unless m/terse plugin/;
529     -
530     -
531     - my $abstime = Time::TAI64::tai2unix($tai);
532     - my $abshour = floor( $abstime / 3600 ); # Hours since the epoch
533     -
534     -
535     - my ($timestamp_part, $log_part) = split('`',$_,2); #bjr 0.6.12
536     - my (@log_items) = split $FS, $log_part;
537     -
538     - my (@timestamp_items) = split(' ',$timestamp_part);
539     -
540     - # we store the more recent recipient domain, for domain statistics
541     - # in fact, we only store the first recipient. Could be sort of headhache
542     - # to obtain precise stats with many recipients on more than one domain !
543     - my $proc = $timestamp_items[1] ; #numeric Id for the email
544     -
545     - $totalexamined++;
546     -
547     - # first spot the fetchmail and local deliveries.
548     -
549     - # Spot from local workstation
550     - $localflag = 0;
551     - $WebMailflag = 0;
552     - if ( $log_items[1] =~ m/.*$DomainName.*/ ) {
553     - $localsendtotal++;
554     - $counts{$abshour}{$CATLOCAL}++;
555     - $localflag = 1;
556     - }
557     -
558     - # see if from localhost
559     - elsif ( $log_items[1] =~ m/.*$localhost.*/ ) {
560     -
561     - # but not if it comes from fetchmail
562     - if ( $log_items[3] =~ m/.*$FETCHMAIL.*/ ) { }
563     - else {
564     -
565     - # might still be from mailman here
566     - if ( $log_items[3] =~ m/.*$MAILMAN.*/ ) {
567     - $mailmansendcount++;
568     - $localsendtotal++;
569     - $counts{$abshour}{$CATMAILMAN}++;
570     - $localflag = 1;
571     - }
572     - else {
573     -
574     - # eliminate incoming localhost spoofs
575     - if ( $log_items[8] =~ m/.*msg denied before queued.*/ ) { }
576     - else {
577     - $localflag = 1;
578     - $WebMailsendtotal++;
579     - $counts{$abshour}{$CATWEBMAIL}++;
580     - $WebMailflag = 1;
581     - }
582     - }
583     - }
584     - }
585     -
586     - # try to spot fetchmail emails
587     - if ( $log_items[0] =~ m/.*$FetchmailIP.*/ ) {
588     - $localAccepttotal++;
589     - $counts{$abshour}{$CATFETCHMAIL}++;
590     - }
591     - elsif ( $log_items[3] =~ m/.*$FETCHMAIL.*/ ) {
592     - $localAccepttotal++;
593     - $counts{$abshour}{$CATFETCHMAIL}++;
594     - }
595     -
596     -# and adjust for recipient field if not set-up by denying plugin - extract from deny msg
597     -
598     - if ( length( $log_items[4] ) == 0 ) {
599     - if ( $log_items[5] eq 'check_goodrcptto' ) {
600     - if ( $log_items[7] gt "invalid recipient" ) {
601     - $log_items[4] =
602     - substr( $log_items[7], 18 ) #Leave only email address
603     - }
604     - }
605     - }
606     -
607     - # if ( ( $currentrcptdomain{ $proc } || '' ) eq '' ) {
608     - # reduce to lc and process each e,mail if a list, pseperatedy commas
609     - my $recipientmail = lc( $log_items[4] );
610     - if ( $recipientmail =~ m/.*,/ ) {
611     -
612     - #comma - split the line and deal with each domain
613     - # print $recipientmail."\n";
614     - my ($recipients) = split( ',', $recipientmail );
615     - foreach my $recip ($recipients) {
616     - $proc = $proc . $recip;
617     -
618     - # print $proc."\n";
619     - $currentrcptdomain{$proc} = $recip;
620     - add_in_domain($proc);
621     - $recipcount++;
622     - }
623     -
624     - # print "*\n";
625     - #count emails with more than one recipient
626     - # $recipientmail =~ m/(.*),/;
627     - # $currentrcptdomain{ $proc } = $1;
628     - }
629     - else {
630     - $proc = $proc . $recipientmail;
631     - $currentrcptdomain{$proc} = $recipientmail;
632     - add_in_domain($proc);
633     - $recipcount++;
634     - }
635     -
636     - # } else {
637     - # # there more than a recipient for a mail, how many daily ?
638     - # $morethanonercpt++;
639     - # }
640     -
641     -
642     - # then categorise the result
643     -
644     -
645     - if (exists $log_items[5]) {
646     -
647     - $found_qpcodes{$log_items[5]}++; ##Count different qpsmtpd result codes
648     -
649     - #Check for badly formed lines (from earlier testing)
650     -
651     - if ($log_items[5] eq 'check_earlytalker') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
652     -
653     - if ($log_items[5] eq 'check_relay') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
654     -
655     - if ($log_items[5] eq 'check_norelay') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
656     -
657     - if ($log_items[5] eq 'require_resolvable_fromhost') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
658     -
659     - if ($log_items[5] eq 'check_basicheaders') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
660     -
661     - if ($log_items[5] eq 'rhsbl') { $RBLcount++;$counts{$abshour}{$CATRBLDNS}++;mark_domain_rejected($proc);next LINE}
662     -
663     - if ($log_items[5] eq 'dnsbl') { $RBLcount++;$counts{$abshour}{$CATRBLDNS}++;mark_domain_rejected($proc);next LINE}
664     -
665     - if ($log_items[5] eq 'check_badmailfrom') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
666     -
667     - if ($log_items[5] eq 'check_badrcptto_patterns') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
668     -
669     - if ($log_items[5] eq 'check_badrcptto') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
670     -
671     - if ($log_items[5] eq 'check_spamhelo') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
672     -
673     - if ($log_items[5] eq 'check_goodrcptto extn') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
674     -
675     - if ($log_items[5] eq 'rcpt_ok') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
676     -
677     - if ($log_items[5] eq 'pattern_filter') { $PatternFilterCount++;$counts{$abshour}{$CATEXECUT}++;mark_domain_rejected($proc);next LINE}
678     -
679     - if ($log_items[5] eq 'virus::pattern_filter') { $PatternFilterCount++;$counts{$abshour}{$CATEXECUT}++;mark_domain_rejected($proc);next LINE}
680     -
681     - if ($log_items[5] eq 'check_goodrcptto') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
682     -
683     - if ($log_items[5] eq 'check_smtp_forward') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
684     -
685     - if ($log_items[5] eq 'count_unrecognized_commands') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
686     -
687     - if ($log_items[5] eq 'tnef2mime') { next LINE} #Not expecting this one.
688     -
689     - if ($log_items[5] eq 'spamassassin') { $above15++;$counts{$abshour}{$CATSPAMDEL}++;
690     - # and extract the spam score
691     - if ($log_items[8] =~ "Yes, hits=(.*) required=([0-9\.]+)") {$rejectspamavg += $1}
692     - mark_domain_rejected($proc);
693     - next LINE
694     - }
695     -
696     - if ($log_items[5] eq 'virus::clamav') { $infectedcount++;$counts{$abshour}{$CATVIRUS}++;
697     - #extract the virus name
698     - if ($log_items[7] =~ "Virus Found: (.*)" ) {$found_viruses{$1}++;}
699     - mark_domain_rejected($proc);
700     - next LINE
701     - }
702     -
703     - if ($log_items[5] eq 'queued') { $Accepttotal++;
704     - #extract the spam score
705     - if ($log_items[8] =~ ".*hits=(.*) required=([0-9\.]+)") {
706     - $score = $1;
707     -# print $log_items[8]."<".$score.">\n";
708     - if ($score < $SATagLevel) { $hamcount++;$counts{$abshour}{$CATHAM}++;$hamavg += $score}
709     - else {$spamcount++;$counts{$abshour}{$CATSPAM}++;$spamavg += $score}
710     - } else {
711     - # no SA score - so it must be ham
712     - $hamcount++;$counts{$abshour}{$CATHAM}++;
713     - }
714     - if ( ( $currentrcptdomain{ $proc } || '' ) ne '' ) {
715     - $byrcptdomain{ $currentrcptdomain{ $proc } }{ 'accept' }++ ;
716     - $currentrcptdomain{ $proc } = '' ;
717     - }
718     - next LINE
719     - }
720     -
721     - print $log_items[5]."\n"; #Not detected
722     -
723     - }
724     -
725     -} #END OF MAIN LOOP
726     -
727     -#total up grand total Columns
728     -$nhour = floor( $start / 3600 );
729     -while ( $nhour < $end / 3600 ) {
730     - $ncateg = 0; #past the where it came from columns
731     - while ( $ncateg < @categs) {
732     - #total columns
733     - $counts{$GRANDTOTAL}{$categs[$ncateg]} += $counts{$nhour}{$categs[$ncateg]};
734     -
735     - # and total rows
736     - if ( $ncateg < $categlen && $ncateg>=$countfromhere) {#skip initial columns of non final reasons
737     - $counts{$nhour}{$categs[@categs-2]} += $counts{$nhour}{$categs[$ncateg]};
738     - }
739     - $ncateg++
740     - }
741     -
742     - $nhour++;
743     -}
744     -
745     -
746     -
747     -#Compute row totals and row percentages
748     -$nhour = floor( $start / 3600 );
749     -while ( $nhour < $end / 3600 ) {
750     - $counts{$nhour}{$categs[@categs-1]} = $counts{$nhour}{$categs[@categs-2]}*100/$totalexamined if $totalexamined;
751     - $nhour++;
752     -
753     -}
754     -
755     -#compute column percentages
756     - $ncateg = 0;
757     - while ( $ncateg < @categs) {
758     - if ($ncateg == @categs-1) {
759     - $counts{$PERCENT}{$categs[$ncateg]} = $counts{$GRANDTOTAL}{$categs[$ncateg-1]}*100/$totalexamined if $totalexamined;
760     - } else {
761     - $counts{$PERCENT}{$categs[$ncateg]} = $counts{$GRANDTOTAL}{$categs[$ncateg]}*100/$totalexamined if $totalexamined;
762     - }
763     - $ncateg++
764     - }
765     -
766     -#compute sum of row percentages
767     -$nhour = floor( $start / 3600 );
768     -while ( $nhour < $end / 3600 ) {
769     - $counts{$GRANDTOTAL}{$categs[@categs-1]} += $counts{$nhour}{$categs[@categs-1]};
770     - $nhour++;
771     -
772     -}
773     -
774     -my $QueryNoLogTerse = ($totalexamined==0); #might indicate logterse not installed in qpsmtpd plugins
775     -
776     -#Calculate some numbers
777     -
778     -$spamavg = $spamavg / $spamcount if $spamcount;
779     -$rejectspamavg = $rejectspamavg / $above15 if $above15;
780     -$hamavg = $hamavg / $hamcount if $hamcount;
781     -
782     -# RBL etc percent of total SMTP sessions
783     -
784     -my $rblpercent = ( ( $RBLcount / $totalexamined ) * 100 ) if $totalexamined;
785     -my $PatternFilterpercent = ( ( $PatternFilterCount / $totalexamined ) * 100 ) if $totalexamined;
786     -my $Miscpercent = ( ( $MiscDenyCount / $totalexamined ) * 100 ) if $totalexamined;
787     -
788     -#Spam and virus percent of total email downloaded
789     -#Expressed as a % of total examined
790     -my $spampercent = ( ( $spamcount / $totalexamined ) * 100 ) if $totalexamined;
791     -my $hampercent = ( ( $hamcount / $totalexamined ) * 100 ) if $totalexamined;
792     -my $hrsinperiod = ( ( $end - $start ) / 3600 );
793     -my $emailperhour = ( $totalexamined / $hrsinperiod ) if $totalexamined;
794     -my $above15percent = ( $above15 / $totalexamined * 100 ) if $totalexamined;
795     -my $infectedpercent = ( ( $infectedcount / ($totalexamined) ) * 100 ) if $totalexamined;
796     -my $AcceptPercent = ( ( $Accepttotal / ($totalexamined) ) * 100 ) if $totalexamined;
797     -
798     -my $oldfh;
799     -
800     -#Open Sendmail if we are mailing it
801     -if ( $opt{'mail'} && !$disabled ) {
802     - open( SENDMAIL, "|$opt{'sendmail'} -oi -t -odq" )
803     - or die "Can't open sendmail: $!\n";
804     - print SENDMAIL "From: $opt{'from'}\n";
805     - print SENDMAIL "To: $opt{'mail'}\n";
806     - print SENDMAIL "Subject: Spam Filter Statistics from $hostname - ",
807     - strftime( "%F", localtime($start) ), "\n\n";
808     - $oldfh = select SENDMAIL;
809     -}
810     -
811     -my $telapsed = time - $tstart;
812     -
813     -if ( !$disabled ) {
814     -
815     - #Output results
816     - print "SMEServer daily Anti-Virus and Spamfilter statistics", "\n";
817     - print "----------------------------------------------------", "\n\n";
818     -
819     - print "$0 Version : $opt{'version'}", "\n\n";
820     - print "Period Beginning : ", strftime( "%c", localtime($start) ), "\n";
821     - print "Period Ending : ", strftime( "%c", localtime($end) ), "\n";
822     - print "\n";
823     -
824     - print "Clam Version : ", `freshclam -V`;
825     - print "SpamAssassin Version : ", `spamassassin -V`;
826     - printf "Tag level: %3d; Reject level: %3d $warnnoreject\n", $SATagLevel,
827     - $SARejectLevel;
828     - if ($HighLogLevel) {
829     - printf "*Loglevel is set to: ".$LogLevel. " - you only need it set to 6\n";
830     - printf "\tYou can set it this way:\n";
831     - printf "\tconfig setprop qpsmtpd LogLevel 6\n";
832     - printf "\tsignal-event email-update\n";
833     - printf "\tsv t /var/service/qpsmtpd\n\n";
834     - }
835     - print "\n";
836     - printf "Reporting Period : %.2f hrs\n", $hrsinperiod;
837     - print "----------------------------\n";
838     - print "\n";
839     -
840     - printf "All SMTP connections accepted:%8d \n", $totalexamined;
841     -
842     - printf "Emails per hour : %8.1f/hr\n", $emailperhour || 0;
843     - print "\n";
844     - printf "Average spam score (accepted): %11.2f\n", $spamavg || 0;
845     - printf "Average spam score (rejected): %11.2f\n", $rejectspamavg || 0;
846     - printf "Average ham score : %11.2f\n", $hamavg || 0;
847     - print "\n";
848     - print "Statistics by Hour\n";
849     -
850     - #
851     - # start by working out which colunns to show - tag the display array
852     - #
853     - $ncateg = 1; ##skip the first column
854     - $finaldisplay[0] = $true;
855     - while ( $ncateg < $categlen) {
856     - if ($display[$ncateg] eq 'yes') { $finaldisplay[$ncateg] = $true }
857     - elsif ($display[$ncateg] eq 'no') { $finaldisplay[$ncateg] = $false }
858     - else {
859     - $finaldisplay[$ncateg] = ($counts{$GRANDTOTAL}{$categs[$ncateg]} != 0);
860     - if ($finaldisplay[$ncateg]) {
861     - #if it has been non zero and auto, then make it yes for the future.
862     - esmith::ConfigDB->open->get('mailstats')->set_prop($categs[$ncateg],'yes')
863     - }
864     -
865     - }
866     - $ncateg++
867     - }
868     - #make sure total and percentages are shown
869     - $finaldisplay[@categs-2] = $true;
870     - $finaldisplay[@categs-1] = $true;
871     -
872     -
873     - # and put together the print lines
874     - #
875     - my $Line1; #Full Line across the page
876     - my $Line2; #Broken Line across the page
877     - my $Titles; #Column headers
878     - my $Values; #Values
879     - my $Totals; #Corresponding totals
880     - my $Percent; # and column percentages
881     -
882     - my $hour = floor( $start / 3600 );
883     - $Line1 = '';
884     - $Line2 = '';
885     - $Titles = '';
886     - $Values = '';
887     - $Totals = '';
888     - $Percent = '';
889     - while ( $hour < $end / 3600 ) {
890     - if ($hour == floor( $start / 3600 )){
891     - #Do all the once only things
892     - $ncateg = 0;
893     - while ( $ncateg < @categs) {
894     - if ($finaldisplay[$ncateg]){
895     - $Line1 .= substr('---------------------',0,$colwidth[$ncateg]);
896     - $Line2 .= substr('---------------------',0,$colwidth[$ncateg]-1);
897     - $Line2 .= " ";
898     - $Titles .= sprintf('%'.($colwidth[$ncateg]-1).'s',$categs[$ncateg])." ";
899     - if ($ncateg == 0) {
900     - $Totals .= substr('TOTALS ',0,$colwidth[$ncateg]-2);
901     - $Percent .= substr('PERCENTAGES ',0,$colwidth[$ncateg]-1);
902     - } else {
903     - # identify bottom right group and supress unless db->ShowGranPerc set
904     - if ($ncateg==@categs-1){
905     - $Totals .= sprintf('%'.$colwidth[$ncateg].'.1f',$counts{$GRANDTOTAL}{$categs[$ncateg]}).'%';
906     - } else {
907     - $Totals .= sprintf('%'.$colwidth[$ncateg].'d',$counts{$GRANDTOTAL}{$categs[$ncateg]});
908     - }
909     - $Percent .= sprintf('%'.($colwidth[$ncateg]-1).'.1f',$counts{$PERCENT}{$categs[$ncateg]}).'%';
910     - }
911     - }
912     - $ncateg++
913     - }
914     - }
915     -
916     - $ncateg = 0;
917     - while ( $ncateg < @categs) {
918     - if ($finaldisplay[$ncateg]){
919     - if ($ncateg == 0) {
920     - $Values .= strftime( "%F, %H", localtime( $hour * 3600 ) )." "
921     - } elsif ($ncateg == @categs-1) {
922     - #percentages in last column
923     - $Values .= sprintf('%'.($colwidth[$ncateg]-2).'.1f',$counts{$hour}{$categs[$ncateg]})."%";
924     - } else {
925     - #body numbers
926     - $Values .= sprintf('%'.($colwidth[$ncateg]-1).'d',$counts{$hour}{$categs[$ncateg]})." ";
927     - }
928     - if (($ncateg == @categs-1)){$Values=$Values."\n"} #&& ($hour == floor($end / 3600)-1)
929     - }
930     - $ncateg++
931     - }
932     -
933     - $hour++;
934     - }
935     -
936     - # print it.
937     - print $Line1."\n";
938     - print $Titles."\n";
939     - print $Line2."\n";
940     - print $Values."\n";
941     - print $Line2."\n";
942     - print $Totals."\n";
943     - print $Percent."\n";
944     - print $Line1."\n";
945     -
946     -
947     - if ($localAccepttotal>0) {
948     - print "*Fetchml* means connections from Fetchmail delivering email\n";
949     - }
950     - print "*Local* means connections from workstations on local LAN.\n";
951     - print "*Non\.Conf\.* means sending mailserver did not conform to correct protocol.\n";
952     - print " or email was to non existant address.\n";
953     - print "\n";
954     -
955     - if ($QueryNoLogTerse) {
956     - print "* - as no records where found, it looks as though you may not have the *logterse* \nplugin running as part of qpsmtpd \n";
957     -# print " to enable it follow the instructions at .............................\n";
958     - }
959     -
960     -
961     - if ( !$RHSenabled || !$DNSenabled ) {
962     -
963     - # comment about RBL not set
964     - print
965     -"* - This means that one or more of the possible spam black listing services\n that are available have not been enabled.\n";
966     - print " You have not enabled:\n";
967     -
968     - if ( !$RHSenabled ) {
969     - print " RHSBL\n";
970     - }
971     -
972     - if ( !$DNSenabled ) {
973     - print " DNSBL\n";
974     - }
975     -
976     -
977     - print " To enable these you can use the following commands:\n";
978     - if ( !$RHSenabled ) {
979     - print " config setprop qpsmtpd RHSBL enabled\n";
980     - }
981     -
982     - if ( !$DNSenabled ) {
983     - print " config setprop qpsmtpd DNSBL enabled\n";
984     - }
985     -
986     - # there so much templates to expand... (PS)
987     - print " Followed by:\n signal-event email-update and\n sv t /var/service/qpsmtpd\n\n";
988     - }
989     -
990     -# if ($Webmailsendtotal > 0) {print "If you have the mailman contrib installed, then the webmail totals might include some mailman emails\n"}
991     -
992     - # time to do a 'by recipient domain' report
993     - print "\nIncoming mails by recipient domains usage\n";
994     - print "-----------------------------------------\n";
995     - print
996     - "Domains Type Total Denied XferErr Accept \%accept\n";
997     - print
998     - "---------------------------- ---------- ------ ------ ------- ------ -------\n";
999     - my %total = (
1000     - total => 0,
1001     - deny => 0,
1002     - xfer => 0,
1003     - accept => 0,
1004     - );
1005     - foreach my $domain (
1006     - sort {
1007     - join( "\.", reverse( split /\./, $a ) ) cmp
1008     - join( "\.", reverse( split /\./, $b ) )
1009     - } keys %byrcptdomain
1010     - )
1011     - {
1012     - next if ( ( $byrcptdomain{$domain}{'total'} || 0 ) == 0 );
1013     - my $tp = $byrcptdomain{$domain}{'type'} || 'other';
1014     - my $to = $byrcptdomain{$domain}{'total'} || 0;
1015     - my $de = $byrcptdomain{$domain}{'deny'} || 0;
1016     - my $xr = $byrcptdomain{$domain}{'xfer'} || 0;
1017     - my $ac = $byrcptdomain{$domain}{'accept'} || 0;
1018     - printf "%-28s %-10s %6d %6d %7d %6d %6.2f%%\n", $domain, $tp, $to,
1019     - $de, $xr, $ac, $ac * 100 / $to;
1020     - $total{'total'} += $to;
1021     - $total{'deny'} += $de;
1022     - $total{'xfer'} += $xr;
1023     - $total{'accept'} += $ac;
1024     - }
1025     - print
1026     - "---------------------------- ---------- ------ ------- ------ ------ -------\n";
1027     -
1028     - # $total{ 'total' } can be equal to 0, bad for divisions...
1029     - my $perc1 = 0;
1030     - my $perc2 = 0;
1031     -
1032     -
1033     - if ( $total{'total'} != 0 ) {
1034     - $perc1 = $total{'accept'} * 100 / $total{'total'};
1035     - $perc2 = ( ( $total{'total'} + $morethanonercpt ) / $total{'total'} );
1036     - }
1037     - printf
1038     - "Total %6d %6d %7d %6d %6.2f%%\n\n",
1039     - $total{'total'}, $total{'deny'}, $total{'xfer'}, $total{'accept'},
1040     - $perc1;
1041     - printf
1042     - "%d mails were processed for %d Recipients\nThe average recipients by mail is %4.2f\n\n",
1043     - $total{'total'}, ( $total{'total'} + $morethanonercpt ), $perc2;
1044     -
1045     - if ( $infectedcount > 0 ) {
1046     - show_virus_variants();
1047     - }
1048     -
1049     - # get enable/disable subsections
1050     - my $enableqpsmtpdcodes;
1051     - my $enableSARules;
1052     - my $enablejunkMailList;
1053     - my $savedata;
1054     - if ($cdb->get('mailstats')){
1055     - $enableqpsmtpdcodes = ($cdb->get('mailstats')->prop("QpsmtpdCodes") || "enabled") eq "enabled" || $true;
1056     - $enableSARules = ($cdb->get('mailstats')->prop("SARules") || "enabled" eq "enabled") || $true;
1057     - $enablejunkMailList = ($cdb->get('mailstats')->prop("JunkMailList") || "enabled") eq "enabled" || $true;
1058     - $savedata = ($cdb->get('mailstats')->prop("SaveDataToMySQL") || "no") eq "yes" || $false;
1059     - } else {
1060     - $enableqpsmtpdcodes = $true;
1061     - $enableSARules = $true;
1062     - $enablejunkMailList = $true;
1063     - $savedata = $false;
1064     - }
1065     -
1066     - if ($enableqpsmtpdcodes) {show_qpsmtpd_codes();}
1067     -
1068     - if ($enableSARules) {show_SARules_codes();}
1069     -
1070     - if ($enablejunkMailList) {List_Junkmail();}
1071     -
1072     - print "\nDone. Report generated in $telapsed sec.\n\n";
1073     -
1074     - if ($savedata) { save_data(); }
1075     - else
1076     - { print "No data saved - if you want to save data to a MySQL database, then please use:\n".
1077     - "config setprop mailstats SaveDataToMySQL yes\nYou must have created the database first.";
1078     - }
1079     -
1080     -
1081     - #Close Senmdmail if it was opened
1082     - if ( $opt{'mail'} ) {
1083     - select $oldfh;
1084     - close(SENDMAIL);
1085     - }
1086     -
1087     -} ##report disabled
1088     -
1089     -#All done
1090     -exit 0;
1091     -
1092     -#############################################################################
1093     -# Subroutines ###############################################################
1094     -#############################################################################
1095     -
1096     -
1097     -################################################
1098     -# Determine analysis period (start and end time)
1099     -################################################
1100     -sub analysis_period {
1101     - my $startdate = shift;
1102     - my $enddate = shift;
1103     -
1104     - my $secsininterval = 86400; #daily default
1105     - my $time;
1106     -
1107     - if ($cdb->get('mailstats'))
1108     - {
1109     - my $interval = $cdb->get('mailstats')->prop('Interval') || 'daily';
1110     - if ($interval eq "weekly") {
1111     - $secsininterval = 86400*7;
1112     - } elsif ($interval eq "fortnightly") {
1113     - $secsininterval = 86400*14;
1114     - } elsif ($interval eq "monthly") {
1115     - $secsininterval = 86400;
1116     - } elsif ($interval =~m/\d+/) {
1117     - $secsininterval = $interval*3600;
1118     - };
1119     - my $base = $cdb->get('mailstats')->prop('Base') || 'Midnight';
1120     - my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
1121     - localtime(time);
1122     - if ($base eq "Midnight"){
1123     - $sec = 0;$min=0;$hour=0;
1124     - } elsif ($base eq "Midday"){
1125     - $sec = 0;$min=0;$hour=12;
1126     - } elsif ($base =~m/\d+/){
1127     - $sec=0;$min=0;$hour=$base;
1128     - };
1129     - $time = timelocal($sec,$min,$hour,$mday,$mon,$year)
1130     - }
1131     - my $start = UnixDate( $startdate, "%s" );
1132     - my $end = $enddate ? UnixDate( $enddate, "%s" ) :
1133     - $startdate ? $start + $secsininterval : $time;
1134     - $start = $startdate ? $start : $end - $secsininterval;
1135     - return ( $start > $end ) ? ( $end, $start ) : ( $start, $end );
1136     -}
1137     -
1138     -sub dbg {
1139     - my $msg = shift;
1140     -
1141     - if ( $opt{debug} ) {
1142     - print STDERR $msg;
1143     - }
1144     -}
1145     -
1146     -sub List_Junkmail {
1147     -
1148     - #
1149     - # Show how many junkmails in each user's junkmail folder.
1150     - #
1151     - use esmith::AccountsDB;
1152     - my $adb = esmith::AccountsDB->open_ro;
1153     - my $entry;
1154     - foreach my $user ( $adb->users ) {
1155     - my $found = 0;
1156     - my $junkmail_dir =
1157     - "/home/e-smith/files/users/" . $user->key . "/Maildir/.junkmail";
1158     - foreach my $dir (qw(new cur)) {
1159     -
1160     - # Now get the content list for the directory.
1161     - if ( opendir( QDIR, "$junkmail_dir/$dir" ) ) {
1162     - while ( $entry = readdir(QDIR) ) {
1163     - next if $entry =~ /^\./;
1164     - $found++;
1165     - }
1166     - closedir(QDIR);
1167     - }
1168     - }
1169     - if ( $found != 0 ) {
1170     - $junkcount{ $user->key } = $found;
1171     - }
1172     - }
1173     - my $i = keys %junkcount;
1174     - if ( $i > 0 ) {
1175     - print("Junk Mails left in folder:\n");
1176     - print("-------------------------\n");
1177     - print("Count\tUser\n");
1178     - print("-------------------------\n");
1179     - foreach my $thisuser (
1180     - sort { $junkcount{$b} <=> $junkcount{$a} }
1181     - keys %junkcount
1182     - )
1183     - {
1184     - printf "%d", $junkcount{$thisuser};
1185     - print "\t" . $thisuser . "\n";
1186     - }
1187     - print("-------------------------\n");
1188     - }
1189     - else {
1190     - print "***No junkmail folders with emails***\n";
1191     - }
1192     -}
1193     -
1194     -sub show_virus_variants
1195     -
1196     -#
1197     -# Show a league table of the different virus types found today
1198     -#
1199     -
1200     -{
1201     -
1202     - print("Virus Statistics by name:\n");
1203     - print("---------------------------------------------\n");
1204     - foreach my $virus (sort { $found_viruses{$b} <=> $found_viruses{$a} }
1205     - keys %found_viruses)
1206     - {
1207     - print "Rejected $found_viruses{$virus}\t$virus\n";
1208     - }
1209     - print("---------------------------------------------\n\n");
1210     -}
1211     -
1212     -sub show_qpsmtpd_codes
1213     -
1214     -#
1215     -# Show a league table of the qpsmtpd result codes found today
1216     -#
1217     -
1218     -{
1219     -
1220     - print("Qpsmtpd codes league table:\n");
1221     - print("---------------------------------------------\n");
1222     - print("Count\tPercent\tReason\t\n");
1223     - print("---------------------------------------------\n");
1224     - foreach my $qpcode (sort { $found_qpcodes{$b} <=> $found_qpcodes{$a} }
1225     - keys %found_qpcodes)
1226     - {
1227     - print "$found_qpcodes{$qpcode}\t".sprintf('%4.1f',$found_qpcodes{$qpcode}*100/$totalexamined)."%\t$qpcode\n" if $totalexamined;
1228     - }
1229     - print("---------------------------------------------\n\n");
1230     -}
1231     -
1232     -sub show_SARules_codes
1233     -
1234     -#
1235     -# Show a league table of the SARules result codes found today
1236     -# suppress any lower than DB mailstats/SARulePercentThreshold
1237     -#
1238     -
1239     -{
1240     -
1241     - my ($percentthreshold);
1242     - my ($defaultpercentthreshold);
1243     -
1244     - if ($totalexamined >0 && $sum_SARules*100/$totalexamined > $SARulethresholdPercent) {
1245     - $defaultpercentthreshold = $maxcutoff
1246     - } else {
1247     - $defaultpercentthreshold = $mincutoff
1248     - }
1249     - if ($cdb->get('mailstats')){
1250     - $percentthreshold = $cdb->get('mailstats')->prop("SARulePercentThreshold") || $defaultpercentthreshold;
1251     - } else {
1252     - $percentthreshold = $defaultpercentthreshold
1253     - }
1254     - print("Spamassassin Rules:\n");
1255     - print("---------------------------------------------\n");
1256     - print("Count\tPercent\tRule\t\n");
1257     - print("---------------------------------------------\n");
1258     - foreach my $SARule (sort { $found_SARules{$b}{'count'} <=> $found_SARules{$a}{'count'} }
1259     - keys %found_SARules)
1260     - {
1261     - my $percent = $found_SARules{$SARule}{'count'} * 100 / $totalexamined
1262     - if $totalexamined;
1263     - my $avehits = $found_SARules{$SARule}{'totalhits'} /
1264     - $found_SARules{$SARule}{'count'}
1265     - if $found_SARules{$SARule}{'count'};
1266     - if ( $percent > $percentthreshold ) {
1267     - print "$found_SARules{$SARule}{'count'}\t"
1268     - . sprintf( '%4.1f', $percent ) . "%\t"
1269     - . sprintf( '%4.1f', $avehits )
1270     - . "\t$SARule\n"
1271     - if $totalexamined;
1272     - }
1273     - }
1274     - print("---------------------------------------------\n\n");
1275     -
1276     -
1277     -}
1278     -
1279     -sub mark_domain_rejected
1280     -
1281     -#
1282     -# Tag domain as having a rejected email
1283     -#
1284     -{
1285     -my ($proc) = @_;
1286     -if ( ( $currentrcptdomain{ $proc } || '' ) ne '' ) {
1287     - $byrcptdomain{ $currentrcptdomain{ $proc } }{ 'deny' }++ ;
1288     - $currentrcptdomain{ $proc } = '' ;
1289     - }
1290     -}
1291     -
1292     -sub mark_domain_err
1293     -
1294     - #
1295     - # Tag domain as having an error on email transfer
1296     - #
1297     -{
1298     - my ($proc) = @_;
1299     - if ( ( $currentrcptdomain{$proc} || '' ) ne '' ) {
1300     - $byrcptdomain{ $currentrcptdomain{$proc} }{'xfer'}++;
1301     - $currentrcptdomain{$proc} = '';
1302     - }
1303     -}
1304     -
1305     -sub add_in_domain
1306     -
1307     - #
1308     - # add recipient domain into hash
1309     - #
1310     -{
1311     - my ($proc) = @_;
1312     -
1313     - #split to just domain bit.
1314     - $currentrcptdomain{$proc} =~ s/.*@//;
1315     - $currentrcptdomain{$proc} =~ s/[^\w\-\.]//g;
1316     - $currentrcptdomain{$proc} =~ s/>//g;
1317     - my $NotableDomain = 0;
1318     - if ( defined( $byrcptdomain{ $currentrcptdomain{$proc} }{'type'} ) ) {
1319     - $NotableDomain = 1;
1320     - }
1321     - else {
1322     - foreach (@extdomain) {
1323     - if ( $currentrcptdomain{$proc} =~ m/$_$/ ) {
1324     - $NotableDomain = 1;
1325     - last;
1326     - }
1327     - }
1328     - }
1329     - if ( !$NotableDomain ) {
1330     -
1331     - # check for outgoing email
1332     - if ( $localflag == 1 ) { $currentrcptdomain{$proc} = 'Outgoing' }
1333     - else { $currentrcptdomain{$proc} = 'Others' }
1334     - }
1335     - else {
1336     - if ( $localflag == 1 ) { $currentrcptdomain{$proc} = 'Internal' }
1337     - }
1338     - $byrcptdomain{ $currentrcptdomain{$proc} }{'total'}++;
1339     -}
1340     -
1341     -sub save_data
1342     -
1343     - #
1344     - # Save the data to a MySQL database
1345     - #
1346     -{
1347     - use DBI;
1348     - my $tstart = time;
1349     - my $DBname = "mailstats";
1350     - my $host = esmith::ConfigDB->open_ro->get('mailstats')->prop('DBHost') || "localhost";
1351     - my $port = esmith::ConfigDB->open_ro->get('mailstats')->prop('DBPort') || "3306";
1352     - print "Saving data..";
1353     - my $dbh = DBI->connect( "DBI:mysql:database=$DBname;host=$host;port=$port",
1354     - "mailstats", "mailstats" )
1355     - or die "Cannot open mailstats db - has it beeen created?";
1356     -
1357     - my $hour = floor( $start / 3600 );
1358     - my $reportdate = strftime( "%F", localtime( $hour * 3600 ) );
1359     - my $dateid = get_dateid($dbh,$reportdate);
1360     - my $reccount = 0; #count number of records written
1361     - my $servername = esmith::ConfigDB->open_ro->get('SystemName')->value . "."
1362     - . esmith::ConfigDB->open_ro->get('DomainName')->value;
1363     - # now fill in day related stats - must always check for it already there
1364     - # incase the module is run more than once in a day
1365     - my $SAScoresid = check_date_rec($dbh,"SAscores",$dateid,$servername);
1366     - $dbh->do( "UPDATE SAscores SET ".
1367     - "acceptedcount=".$spamcount.
1368     - ",rejectedcount=".$above15.
1369     - ",hamcount=".$hamcount.
1370     - ",acceptedscore=".$spamhits.
1371     - ",rejectedscore=".$rejectspamhits.
1372     - ",hamscore=".$hamhits.
1373     - ",totalsmtp=".$totalexamined.
1374     - ",totalrecip=".$recipcount.
1375     - ",servername='".$servername.
1376     - "' WHERE SAscoresid =".$SAScoresid);
1377     - # Junkmail stats
1378     - # delete if already there
1379     - $dbh->do("DELETE from JunkMailStats WHERE dateid = ".$dateid." AND servername='".$servername."'");
1380     - # and add records
1381     - foreach my $thisuser (keys %junkcount){
1382     - $dbh->do("INSERT INTO JunkMailStats (dateid,user,count,servername) VALUES ('".
1383     - $dateid."','".$thisuser."','".$junkcount{$thisuser}."','".$servername."')");
1384     - $reccount++;
1385     - }
1386     - #SA rules - delete any first
1387     - $dbh->do("DELETE from SARules WHERE dateid = ".$dateid." AND servername='".$servername."'");
1388     - # and add records
1389     - foreach my $thisrule (keys %found_SARules){
1390     - $dbh->do("INSERT INTO SARules (dateid,rule,count,totalhits,servername) VALUES ('".
1391     - $dateid."','".$thisrule."','".$found_SARules{$thisrule}{'count'}."','".
1392     - $found_SARules{$thisrule}{'totalhits'}."','".$servername."')");
1393     - $reccount++;
1394     - }
1395     - #qpsmtpd result codes
1396     - $dbh->do("DELETE from qpsmtpdcodes WHERE dateid = ".$dateid." AND servername='".$servername."'");
1397     - # and add records
1398     - foreach my $thiscode (keys %found_qpcodes){
1399     - $dbh->do("INSERT INTO qpsmtpdcodes (dateid,reason,count,servername) VALUES ('".
1400     - $dateid."','".$thiscode."','".$found_qpcodes{$thiscode}."','".$servername."')");
1401     - $reccount++;
1402     -}
1403     - # virus stats
1404     - $dbh->do("DELETE from VirusStats WHERE dateid = ".$dateid." AND servername='".$servername."'");
1405     - # and add records
1406     - foreach my $thisvirus (keys %found_viruses){
1407     - $dbh->do("INSERT INTO VirusStats (dateid,descr,count,servername) VALUES ('".
1408     - $dateid."','".$thisvirus."','".$found_viruses{$thisvirus}."','".$servername."')");
1409     - $reccount++;
1410     -
1411     - }
1412     - # domain details
1413     - $dbh->do("DELETE from domains WHERE dateid = ".$dateid." AND servername='".$servername."'");
1414     - # and add records
1415     - foreach my $domain (keys %byrcptdomain){
1416     - next if ( ( $byrcptdomain{$domain}{'total'} || 0 ) == 0 );
1417     - $dbh->do("INSERT INTO domains (dateid,domain,type,total,denied,xfererr,accept,servername) VALUES ('".
1418     - $dateid."','".$domain."','".($byrcptdomain{$domain}{'type'}||'other')."','"
1419     - .$byrcptdomain{$domain}{'total'}."','"
1420     - .($byrcptdomain{$domain}{'deny'}||0)."','"
1421     - .($byrcptdomain{$domain}{'xfer'}||0)."','"
1422     - .($byrcptdomain{$domain}{'accept'}||0)."','"
1423     - .$servername
1424     - ."')");
1425     - $reccount++;
1426     -
1427     - }
1428     - # finally - the hourly breakdown
1429     - # need to remember here that the date might change during the 24 hour span
1430     - my $nhour = floor( $start / 3600 );
1431     - my $ncateg;
1432     - while ( $nhour < $end / 3600 ) {
1433     - #see if the time record has been created
1434     - # print strftime("%H",localtime( $nhour * 3600 ) ).":00:00\n";
1435     - my $sth =
1436     - $dbh->prepare( "SELECT timeid FROM time WHERE time = '" . strftime("%H",localtime( $nhour * 3600 ) ).":00:00'");
1437     - $sth->execute();
1438     - if ( $sth->rows == 0 ) {
1439     - #create entry
1440     - $dbh->do( "INSERT INTO time (time) VALUES ('" .strftime("%H",localtime( $nhour * 3600 ) ).":00:00')" );
1441     - # and pick up timeid
1442     - $sth = $dbh->prepare("SELECT last_insert_id() AS timeid FROM time");
1443     - $sth->execute();
1444     - $reccount++;
1445     - }
1446     - my $timerec = $sth->fetchrow_hashref();
1447     - my $timeid = $timerec->{"timeid"};
1448     - $ncateg = 0;
1449     - # and extract date from first column of $count array
1450     - my $currentdate = strftime( "%F", localtime( $hour * 3600 ) );
1451     - # print "$currentdate.\n";
1452     - if ($currentdate ne $reportdate) {
1453     - #same as before?
1454     - $dateid = get_dateid($dbh,$currentdate);
1455     - $reportdate = $currentdate;
1456     - }
1457     - # delete for this date and time
1458     - $dbh->do("DELETE from ColumnStats WHERE dateid = ".$dateid." AND timeid = ".$timeid." AND servername='".$servername."'");
1459     - while ( $ncateg < @categs-1 ) {
1460     - # then add in each entry
1461     - if (($counts{$nhour}{$categs[$ncateg]} || 0) != 0) {
1462     - $dbh->do("INSERT INTO ColumnStats (dateid,timeid,descr,count,servername) VALUES ("
1463     - .$dateid.",".$timeid.",'".$categs[$ncateg]."',"
1464     - .$counts{$nhour}{$categs[$ncateg]}.",'".$servername."')");
1465     - $reccount++;
1466     - }
1467     -
1468     -# print("INSERT INTO ColumnStats (dateid,timeid,descr,count) VALUES ("
1469     -# .$dateid.",".$timeid.",'".$categs[$ncateg]."',"
1470     -# .$counts{$nhour}{$categs[$ncateg]}.")\n");
1471     -
1472     - $ncateg++;
1473     - }
1474     - $nhour++;
1475     - }
1476     - $dbh->disconnect();
1477     - my $telapsed = time - $tstart;
1478     - print "Saved $reccount records in $telapsed sec.";
1479     -}
1480     -
1481     -sub check_date_rec
1482     -
1483     - #
1484     - # check that a specific dated rec is there, create if not
1485     - #
1486     -{
1487     - my ( $dbh, $table, $dateid ) = @_;
1488     - my $sth =
1489     - $dbh->prepare(
1490     - "SELECT " . $table . "id FROM ".$table." WHERE dateid = '$dateid'" );
1491     - $sth->execute();
1492     - if ( $sth->rows == 0 ) {
1493     - #create entry
1494     - $dbh->do( "INSERT INTO ".$table." (dateid) VALUES ('" . $dateid . "')" );
1495     - # and pick up recordid
1496     - $sth = $dbh->prepare("SELECT last_insert_id() AS ".$table."id FROM ".$table);
1497     - $sth->execute();
1498     - }
1499     - my $rec = $sth->fetchrow_hashref();
1500     - $rec->{$table."id"}; #return the id of the reocrd (new or not)
1501     - }
1502     -
1503     - sub check_time_rec
1504     -
1505     - #
1506     - # check that a specific dated amd timed rec is there, create if not
1507     - #
1508     -{
1509     - my ( $dbh, $table, $dateid, $timeid ) = @_;
1510     - my $sth =
1511     - $dbh->prepare(
1512     - "SELECT " . $table . "id FROM ".$table." WHERE dateid = '$dateid' AND timeid = ".$timeid );
1513     - $sth->execute();
1514     - if ( $sth->rows == 0 ) {
1515     - #create entry
1516     - $dbh->do( "INSERT INTO ".$table." (dateid,timeid) VALUES ('" . $dateid . "', '".$timeid."')" );
1517     - # and pick up recordid
1518     - $sth = $dbh->prepare("SELECT last_insert_id() AS ".$table."id FROM ".$table);
1519     - $sth->execute();
1520     - }
1521     - my $rec = $sth->fetchrow_hashref();
1522     - $rec->{$table."id"}; #return the id of the record (new or not)
1523     - }
1524     -
1525     -sub get_dateid
1526     -
1527     -#
1528     -# Check that date is in db, and return corresponding id
1529     -#
1530     -{
1531     - my ($dbh,$reportdate) = @_;
1532     - my $sth =
1533     - $dbh->prepare( "SELECT dateid FROM date WHERE date = '" . $reportdate."'" );
1534     - $sth->execute();
1535     - if ( $sth->rows == 0 ) {
1536     - #create entry
1537     - $dbh->do( "INSERT INTO date (date) VALUES ('" . $reportdate . "')" );
1538     - # and pick up dateid
1539     - $sth = $dbh->prepare("SELECT last_insert_id() AS dateid FROM date");
1540     - $sth->execute();
1541     - }
1542     - my $daterec = $sth->fetchrow_hashref();
1543     - $daterec->{"dateid"};
1544     - }
1545     +#!/usr/bin/perl -w
1546     +
1547     +#############################################################################
1548     +#
1549     +# This script provides daily SpamFilter statistics and deletes all users
1550     +# junkmails. Configuration of the script is done by the Spam Filter
1551     +# Server-Manager module
1552     +#
1553     +# April 2006 - no longer controlled by server manager, and does not delete files
1554     +#
1555     +# This script has been developed
1556     +# by Jesper Knudsen at http://sme.swerts-knudsen.dk
1557     +#
1558     +# Revision History:
1559     +#
1560     +# August 13, 2003: Initial version
1561     +# August 25, 2004: fixed problem when hostname had no-ASCII chars
1562     +# March 23, 2006 Revised for sme7 RM
1563     +# March 27, 2006 ditto BJR (http://www.abandonmicrosoft.co.uk)
1564     +# - Merged Clamav and SA stats
1565     +# - Moved all analysis to qsmtpd log
1566     +# - Removed parameterised interval (for simplicity - not sure of format anyway)
1567     +# - add in archived log files for people who have high turnover
1568     +# - Alter labels to be more accurate
1569     +# - Detect deleted spam (over threshold) without using spam score
1570     +# - Detect RBL rejections
1571     +# - Detect pattern (executible) rejections
1572     +# - Look for the DENY labels - add in Miscellaneous category
1573     +# April 6, 2006 - check qpsmtp log level and also DNS enable properties
1574     +# - Average spam scores for under and over threshold seperatly
1575     +# - Log tag and Reject levels
1576     +# - TBD - check that RBL DENY are being detected (I have no date to check this)
1577     +# April 7, 2007 - re-written by Charlie Brady totally in Perl
1578     +# April 16, 2006 - move warnings to report
1579     +# - Spot fetchmail deliveries
1580     +# - Spot Internal connections from client PCs
1581     +# - TBD check that RBL DENY are being detected (I have no data to check this)
1582     +# April 30, 2006 - Pascal Schirrmann Start Time and End Time to noon - should be a param
1583     +# so the script can be run at any time in the day.
1584     +# - adds 'by recipients domains' stats Useful for MX-Backup or multi domains hosts
1585     +# - Add a 'recipients per mail' stat. Useful : until now the sums are correct :-)
1586     +# - Correct some messages about rbl who can led to wrong entry in the config database
1587     +# ( and without expected results, of course !)
1588     +# - improve a regexp in the SPAM detection
1589     +# May 1, 2006 - BJR - Fix situation where mxbackup prop is not defined
1590     +# - fix a spelling and minor format of domain report
1591     +# May 9, 2006 - bjr - Make RBL percentage a percentage of total connections (else it >100%)
1592     +# May 9, 2006 - ps - some 'sanity check' in the 'per domains part of the stats (to avoid / 0)
1593     +# May 12, 2006 - ps - some cleanup in the 'per domains' stats
1594     +# - Add a version number, logged in the mail
1595     +# June 20, 2006 - bjr - Minor change to RBL instructions, and adjust domain table format
1596     +# Feb 19, 2007 - bjr - Adjust table lines oin a couple of places
1597     +# - bjr - and add documentation details about percentages etc
1598     +# - bjr - Alter misc to "non conforming" anmd accumulated these hourly
1599     +# - bjr - Express change over tag count to exclude spam rejected over threshold
1600     +# - bjr - Change "processsed" to "fully downloaded"
1601     +# - bjr - Change percentages so that they are all a percetnage of the total emails received
1602     +# 0.6.1 - bjr - Change to use output from the logterse qpsmtpd plugin
1603     +# 0.6.2 - bjr - Fix fetchmail tests
1604     +# 0.6.3 - bjr - adjust for log-items change in order
1605     +# 0.6.4&5 - bjr - Adjust table formatting
1606     +# 0.6.6 - bjr - Take outgoing emails out of "others", add "Outgoing" and "Internal"
1607     +# 0.6.7 - bjr - Fix missing plugins/wrong names. pull invalid recipient out of deny msg for goodrcptto
1608     +# 0.6.8 - bjr - catch a few more plugin name failures
1609     +# 0.6.9 - bjr - Catch webmail and mailman
1610     +# 0.6.10 - bjr - Refine Webmail identification
1611     +# 0.6.11 - bjr - Fix Webmail identification
1612     +# 0.6.12 - bjr - split logterse line a bit more carefully (multiple sent to addresss with space and comma confuse it)
1613     +# 0.6.13 - bjr - add totals and percentages to bottom of the table
1614     +# - Generalise counts so that columns can be brought in and out
1615     +# - control columns with Db entries
1616     +# 0.6.14 - bjr - Add in league tables of qpsmtpd codes and SA rules
1617     +# - Add in loglevel check
1618     +# - parameterise email address for report
1619     +# 0.6.15 - bjr - fix columns included in totals
1620     +# - sort out domains when more that one email address in recipient field
1621     +# 0.6.16 - cb - fix date range bug (http://bugs.contribs.org/show_bug.cgi?id=3366)
1622     +# 0.6.17 - cb - avoid numerous re-openings of config db
1623     +# 0.6.18 - cb - tidy up options configuration section
1624     +# 0.6.19 - cb - rename parse_args => analysis_period, and simplify
1625     +# 0.6.20 - bjr - Retofit bjr fixes since file edited by charlie - Details
1626     +# - Add Average SA Scores to SA league table,
1627     +# - sort junkmail counts, sorted out xfererr for domains
1628     +# - Fixed multiple recipients for single emails
1629     +# - Fix Report suppression code for qpsmtpd codes etc
1630     +# - Added code to save stats to MySQL DB (defaulted to off)
1631     +# - Fixed interval so that it analyzes Midnight to midnight
1632     +# - Allow varied interval for report
1633     +# 0.6.21 - bjr - Move initial test (and create) for mailstats prop before
1634     +# first reference to mailstats
1635     +# 0.6.22 - bjr - bug fix [SME:3734]
1636     +#
1637     +# TODO
1638     +# ----
1639     +#
1640     +# sort out multiple emails recipients, count each one, and log multiple counts
1641     +#
1642     +#
1643     +#
1644     +#############################################################################
1645     +#
1646     +# SMEServer DB usage
1647     +# ------------------
1648     +#
1649     +# mailstats / Status ("enabled"|"disabled")
1650     +# / <column header> ("yes"|"no"|"auto") - enable, supress or only show if nonzero
1651     +# / QpsmtpdCodes ("enabled"|"disabled")
1652     +# / SARules ("enabled"|"disabled")
1653     +# / JunkMailList ("enabled"|"disabled")
1654     +# / SARulePercentThreshold (0.5) - threshold of SArules percentage for report cutoff
1655     +# / Email (admin) - email to send report
1656     +# / SaveDataToMySQL - save data to MySQL database (default is "no")
1657     +# / DBHost - MySQL server hostname (default is "localhost").
1658     +# / DBPort - MySQL server post (default is "3306")
1659     +# / Interval - "day", "week", "fortnight", "month", "99999" - last is number of seconds (default is day)
1660     +# / Base - "Midnight", "Midday", "Now", "99" hour (0-23) (default is midnight)
1661     +#
1662     +#############################################################################
1663     +#
1664     +# Table structure for MySQL table for saving data
1665     +#
1666     +# Database : `mailstats`
1667     +#
1668     +# use mailstats;
1669     +# --------------------------------------------------------
1670     +
1671     +#
1672     +# Table structure for table `ColumnStats`
1673     +#
1674     +#
1675     +#CREATE TABLE `ColumnStats` (
1676     +# `ColumnStatsid` int(11) NOT NULL auto_increment,
1677     +# `dateid` int(11) NOT NULL default '0',
1678     +# `timeid` int(11) NOT NULL default '0',
1679     +# `descr` varchar(20) NOT NULL default '',
1680     +# `count` bigint(20) NOT NULL default '0',
1681     +# `servername` varchar(30) NOT NULL default '',
1682     +# PRIMARY KEY (`ColumnStatsid`)
1683     +#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
1684     +
1685     +# --------------------------------------------------------
1686     +
1687     +#
1688     +# Table structure for table `JunkMailStats`
1689     +#
1690     +
1691     +#CREATE TABLE `JunkMailStats` (
1692     +# `JunkMailstatsid` int(11) NOT NULL auto_increment,
1693     +# `dateid` int(11) NOT NULL default '0',
1694     +# `user` varchar(12) NOT NULL default '',
1695     +# `count` bigint(20) NOT NULL default '0',
1696     +# `servername` varchar(30) default NULL,
1697     +# PRIMARY KEY (`JunkMailstatsid`)
1698     +#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
1699     +#
1700     +# --------------------------------------------------------
1701     +
1702     +#
1703     +# Table structure for table `SARules`
1704     +#
1705     +
1706     +#CREATE TABLE `SARules` (
1707     +# `SARulesid` int(11) NOT NULL auto_increment,
1708     +# `dateid` int(11) NOT NULL default '0',
1709     +# `rule` varchar(50) NOT NULL default '',
1710     +# `count` bigint(20) NOT NULL default '0',
1711     +# `totalhits` bigint(20) NOT NULL default '0',
1712     +# `servername` varchar(30) NOT NULL default '',
1713     +# PRIMARY KEY (`SARulesid`)
1714     +#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
1715     +
1716     +# --------------------------------------------------------
1717     +
1718     +#
1719     +# Table structure for table `SAscores`
1720     +#
1721     +
1722     +#CREATE TABLE `SAscores` (
1723     +# `SAscoresid` int(11) NOT NULL auto_increment,
1724     +# `dateid` int(11) NOT NULL default '0',
1725     +# `acceptedcount` bigint(20) NOT NULL default '0',
1726     +# `rejectedcount` bigint(20) NOT NULL default '0',
1727     +# `hamcount` bigint(20) NOT NULL default '0',
1728     +# `acceptedscore` decimal(20,2) NOT NULL default '0.00',
1729     +# `rejectedscore` decimal(20,2) NOT NULL default '0.00',
1730     +# `hamscore` decimal(20,2) NOT NULL default '0.00',
1731     +# `totalsmtp` bigint(20) NOT NULL default '0',
1732     +# `totalrecip` bigint(20) NOT NULL default '0',
1733     +# `servername` varchar(30) NOT NULL default '',
1734     +# PRIMARY KEY (`SAscoresid`)
1735     +#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
1736     +
1737     +# --------------------------------------------------------
1738     +
1739     +#
1740     +# Table structure for table `VirusStats`
1741     +#
1742     +
1743     +#CREATE TABLE `VirusStats` (
1744     +# `VirusStatsid` int(11) NOT NULL auto_increment,
1745     +# `dateid` int(11) NOT NULL default '0',
1746     +# `descr` varchar(40) NOT NULL default '',
1747     +# `count` bigint(20) NOT NULL default '0',
1748     +# `servername` varchar(30) NOT NULL default '',
1749     +# PRIMARY KEY (`VirusStatsid`)
1750     +#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
1751     +#
1752     +# --------------------------------------------------------
1753     +
1754     +#
1755     +# Table structure for table `date`
1756     +#
1757     +
1758     +#CREATE TABLE `date` (
1759     +# `dateid` int(11) NOT NULL auto_increment,
1760     +# `date` date NOT NULL default '0000-00-00',
1761     +# PRIMARY KEY (`dateid`)
1762     +#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
1763     +#
1764     +# --------------------------------------------------------
1765     +
1766     +#
1767     +# Table structure for table `domains`
1768     +#
1769     +
1770     +#CREATE TABLE `domains` (
1771     +# `domainsid` int(11) NOT NULL auto_increment,
1772     +# `dateid` int(11) NOT NULL default '0',
1773     +# `domain` varchar(40) NOT NULL default '',
1774     +# `type` varchar(10) NOT NULL default '',
1775     +# `total` bigint(20) NOT NULL default '0',
1776     +# `denied` bigint(20) NOT NULL default '0',
1777     +# `xfererr` bigint(20) NOT NULL default '0',
1778     +# `accept` bigint(20) NOT NULL default '0',
1779     +# `servername` varchar(30) NOT NULL default '',
1780     +# PRIMARY KEY (`domainsid`)
1781     +#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
1782     +
1783     +# --------------------------------------------------------
1784     +
1785     +#
1786     +# Table structure for table `qpsmtpdcodes`
1787     +#
1788     +
1789     +#CREATE TABLE `qpsmtpdcodes` (
1790     +# `qpsmtpdcodesid` int(11) NOT NULL auto_increment,
1791     +# `dateid` int(11) NOT NULL default '0',
1792     +# `reason` varchar(40) NOT NULL default '',
1793     +# `count` bigint(20) NOT NULL default '0',
1794     +# `servername` varchar(30) NOT NULL default '',
1795     +# PRIMARY KEY (`qpsmtpdcodesid`)
1796     +#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
1797     +
1798     +# --------------------------------------------------------
1799     +
1800     +#
1801     +# Table structure for table `time`
1802     +#
1803     +
1804     +#CREATE TABLE `time` (
1805     +# `timeid` int(11) NOT NULL auto_increment,
1806     +# `time` time NOT NULL default '00:00:00',
1807     +# PRIMARY KEY (`timeid`)
1808     +#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
1809     +#
1810     +#############################################################################
1811     +
1812     +# internal modules (part of core perl distribution)
1813     +use strict;
1814     +use warnings;
1815     +use Getopt::Long;
1816     +use Pod::Usage;
1817     +use POSIX qw/strftime floor/;
1818     +use Time::Local;
1819     +use Date::Manip;
1820     +use Time::TAI64;
1821     +use esmith::ConfigDB;
1822     +use esmith::DomainsDB;
1823     +use Sys::Hostname;
1824     +use Switch;
1825     +
1826     +my $hostname = hostname();
1827     +my $cdb = esmith::ConfigDB->open_ro or die "Couldn't open ConfigDB : $!\n";
1828     +
1829     +my $true = 1;
1830     +my $false = 0;
1831     +#and see if mailstats are disabled
1832     +my $disabled;
1833     +if ($cdb->get('mailstats')){
1834     + $disabled = !(($cdb->get('mailstats')->prop('Status') || 'enabled') eq 'enabled');
1835     +} else {
1836     + my $db = esmith::ConfigDB->open; my $record = $db->new_record('mailstats', { type => 'report', Status => 'enabled', Email => 'admin' });
1837     + $cdb = esmith::ConfigDB->open_ro or die "Couldn't open ConfigDB : $!\n"; #Open up again to pick up new record
1838     + $disabled = $false;
1839     +}
1840     +
1841     +#Configuration section
1842     +my %opt = (
1843     + version => '0.6.22', # please update at each change.
1844     + debug => 0, # guess what ?
1845     + sendmail => '/usr/sbin/sendmail', # Path to sendmail stub
1846     + from => 'spamfilter-stats', # Who is the mail from
1847     + mail => # mailstats email recipient
1848     + $cdb->get('mailstats')->prop('Email') || 'admin',
1849     + timezone => `date +%z`,
1850     +);
1851     +
1852     +Date_Init("TZ=$opt{'timezone'}");
1853     +
1854     +my $FetchmailIP = '127.0.0.200'; #Apparent Ip address of fetchmail deliveries
1855     +my $WebmailIP = '127.0.0.1'; #Apparent Ip of Webmail sender
1856     +my $localhost = 'localhost'; #Apparent sender for webmail
1857     +my $FETCHMAIL = 'FETCHMAIL'; #Sender from fetchmail when Ip address not 127.0.0.200 - when qpsmtpd denies the email
1858     +my $MAILMAN = "bounces"; #sender when mailman sending when orig is localhost
1859     +
1860     +my $MinCol = 8; #Minimum column width
1861     +my $HourColWidth = 16; #Date and time column width
1862     +
1863     +my $SARulethresholdPercent = 10; #If Sa rules less than this of total emails, then cutoff reduced
1864     +my $maxcutoff = 1; #max percent cutoff applied
1865     +my $mincutoff = 0.2; #min percent cutoff applied
1866     +
1867     +my $tstart = time;
1868     +
1869     +#Local variables
1870     +my $YEAR = ( localtime(time) )[5]; # this is years since 1900
1871     +
1872     +my $total = 0;
1873     +my $spamcount = 0;
1874     +my $spamavg = 0;
1875     +my $spamhits = 0;
1876     +my $hamcount = 0;
1877     +my $hamavg = 0;
1878     +my $hamhits = 0;
1879     +my $rejectspamavg = 0;
1880     +my $rejectspamhits= 0;
1881     +
1882     +my $Accepttotal = 0;
1883     +my $localAccepttotal = 0; #Fetchmail connections
1884     +my $localsendtotal = 0; #Connections from local PCs
1885     +my $totalexamined = 0; #total download + RBL etc
1886     +my $WebMailsendtotal = 0; #total from Webmail
1887     +my $mailmansendcount = 0; #total from mailman
1888     +
1889     +my %found_viruses = ();
1890     +my %found_qpcodes = ();
1891     +my %found_SARules = ();
1892     +my %junkcount = ();
1893     +
1894     +# replaced by...
1895     +my %counts = (); #Hold all counts in 2-D matrix
1896     +my @display = (); #used to switch on and off columns - yes, no or auto for each category
1897     +my @colwidth = (); #width of each column
1898     + #(auto means only if non zero) - populated from possible db entries
1899     +my @finaldisplay = (); #final decision on display or not - true or false
1900     +
1901     +#count column names, used for headings - also used for DB mailstats property names
1902     +my $CATHOUR='Hour';
1903     +my $CATFETCHMAIL='Fetchmail';
1904     +my $CATWEBMAIL='WebMail';
1905     +my $CATMAILMAN='Mailman';
1906     +my $CATLOCAL='Local';
1907     +# border between where it came from and where it ended..
1908     +my $countfromhere = 5;
1909     +
1910     +my $CATVIRUS='Virus';
1911     +my $CATRBLDNS='RBL/DNS';
1912     +my $CATEXECUT='Execut.';
1913     +my $CATNONCONF='Non.Conf.';
1914     +my $CATSPAMDEL='Del.Spam';
1915     +my $CATSPAM='Qued.Spam?';
1916     +my $CATHAM='Ham';
1917     +my $CATTOTALS='TOTALS';
1918     +my $CATPERCENT='PERCENT';
1919     +my @categs = ($CATHOUR,$CATFETCHMAIL,$CATWEBMAIL,$CATMAILMAN,$CATLOCAL,$CATVIRUS,$CATRBLDNS,$CATEXECUT,$CATNONCONF,$CATSPAMDEL,$CATSPAM,$CATHAM,$CATTOTALS,$CATPERCENT);
1920     +my $GRANDTOTAL = '99'; #subs for count arrays, for grand total
1921     +my $PERCENT = '98'; # for column percentages
1922     +
1923     +my $categlen = @categs-2; #-2 to avoid the total and percent column
1924     +
1925     +my $above15 = 0;
1926     +my $RBLcount = 0;
1927     +my $MiscDenyCount = 0;
1928     +my $PatternFilterCount = 0;
1929     +my $noninfectedcount = 0;
1930     +my $okemailcount = 0;
1931     +my $infectedcount = 0;
1932     +my $warnnoreject = " ";
1933     +my $rblnotset = ' ';
1934     +
1935     +my $FS = "\t"; # field separator used by logterse plugin
1936     +my %log_items = ( "", "", "", "", "", "", "", "" );
1937     +my $score;
1938     +my %timestamp_items = ();
1939     +my $localflag = 0; #indicate if current email is local or not
1940     +my $WebMailflag = 0; #indicate if current mail is send from webmail
1941     +
1942     +# some storage for by recipient domains stats (PS)
1943     +# my bad : I have to deal with multiple simoultaneous connections
1944     +# will play with the process number.
1945     +# my $currentrcptdomain = '' ;
1946     +my %currentrcptdomain ; # temporay store the recipient domain until end of mail processing
1947     +my %byrcptdomain ; # Store 'by domains stats'
1948     +my @extdomain ; # only useful in some MX-Backup case, when any subdomains are allowed
1949     +my $morethanonercpt = 0 ; # count every 'second' recipients for a mail.
1950     +my $recipcount = 0; # count every recipient email address received.
1951     +
1952     +
1953     +# store the domain of interest. Every other records are stored in a 'Other' zone
1954     +my $ddb = esmith::DomainsDB->open_ro or die "Couldn't open DomainsDB : $!\n";
1955     +
1956     +foreach my $domain( $ddb->get_all_by_prop( type => "domain" ) ) {
1957     + $byrcptdomain{ $domain->key }{ 'type' }='local';
1958     +}
1959     +$byrcptdomain{ $cdb->get('SystemName')->value . "."
1960     + . $cdb->get('DomainName')->value }{ 'type' } = 'local';
1961     +
1962     +# is this system a MX-Backup ?
1963     +if ($cdb->get('mxbackup')){
1964     + if ( ( $cdb->get('mxbackup')->prop('status') || 'disabled' ) eq 'enabled' ) {
1965     + my %MXValues = split( /,/, ( $cdb->get('mxbackup')->prop('name') || '' ) ) ;
1966     + foreach my $data ( keys %MXValues ) {
1967     + $byrcptdomain{ $data }{ 'type' } = "mxbackup-$MXValues{ $data }" ;
1968     + if ( $MXValues{ $data } == 1 ) { # subdomains allowed, must take care of this
1969     + push @extdomain, $data ;
1970     + }
1971     + }
1972     + }
1973     +}
1974     +
1975     +my ( $start, $end ) = analysis_period();
1976     +
1977     +#
1978     +# First check current configuration for logging, DNS enable and Max threshold for spamassassin
1979     +#
1980     +
1981     +my $LogLevel = $cdb->get('qpsmtpd')->prop('LogLevel');
1982     +my $HighLogLevel = ( $LogLevel > 6 );
1983     +
1984     +my $RHSenabled =
1985     + ( $cdb->get('qpsmtpd')->prop('RHSBL') eq 'enabled' );
1986     +my $DNSenabled =
1987     + ( $cdb->get('qpsmtpd')->prop('DNSBL') eq 'enabled' );
1988     +my $SARejectLevel =
1989     + $cdb->get('spamassassin')->prop('RejectLevel');
1990     +my $SATagLevel =
1991     + $cdb->get('spamassassin')->prop('TagLevel');
1992     +my $DomainName =
1993     + $cdb->get('DomainName')->value;
1994     +
1995     +# check that logterse is in use
1996     +#my pluginfile = '/var/service/qpsmtpd/config/peers/0';
1997     +
1998     +if ( !$RHSenabled || !$DNSenabled ) {
1999     + $rblnotset = '*';
2000     +}
2001     +
2002     +if ( $SARejectLevel == 0 ) {
2003     +
2004     + $warnnoreject = "(*Warning* 0 = no reject)";
2005     +
2006     +}
2007     +
2008     +#
2009     +#---------------------------------------
2010     +# Scan the qpsmtpd log file
2011     +#---------------------------------------
2012     +
2013     +
2014     +# Init the hashes
2015     +my $nhour = floor( $start / 3600 );
2016     +my $ncateg;
2017     +while ( $nhour < $end / 3600 ) {
2018     + $counts{$nhour}=();
2019     + $ncateg = 0;
2020     + while ( $ncateg < @categs) {
2021     + $counts{$nhour}{$categs[$ncateg-1]} = 0;
2022     + $ncateg++
2023     + }
2024     + $nhour++;
2025     +}
2026     +# and grand totals and display status from db entries, and column widths
2027     +$ncateg = 0;
2028     +while ( $ncateg < @categs) {
2029     + $counts{$GRANDTOTAL}{$categs[$ncateg]} = 0;
2030     + if ($cdb->get('mailstats')){
2031     + $display[$ncateg] = lc($cdb->get('mailstats')->prop($categs[$ncateg])) || "auto";
2032     + } else {
2033     + $display[$ncateg] = 'auto'
2034     + }
2035     + if ($ncateg == 0) {
2036     + $colwidth[$ncateg] = $HourColWidth
2037     + } else {
2038     + $colwidth[$ncateg] = length($categs[$ncateg])+1
2039     + }
2040     + if ($colwidth[$ncateg] < $MinCol) {$colwidth[$ncateg] = $MinCol}
2041     + $ncateg++
2042     +}
2043     +
2044     +my $starttai = Time::TAI64::unixtai64n($start);
2045     +my $endtai = Time::TAI64::unixtai64n($end);
2046     +my $sum_SARules = 0;
2047     +
2048     +LINE: while (<>) {
2049     + my($tai,$log) = split(' ',$_,2);
2050     +
2051     +
2052     + #If date specified, only process lines matching date
2053     + next LINE if ( $tai lt $starttai );
2054     + last if ( $tai gt $endtai );
2055     +
2056     + # pull out spamasassin rule lists
2057     + if ( $_ =~m/spamassassin plugin: check_spam:.*hits=(.*), required.*tests=(.*)/ )
2058     + {
2059     + my ($SAtests) = split(',',$2);
2060     + foreach my $SAtest ($SAtests) {
2061     + if (!$SAtest eq "") {
2062     + $found_SARules{$SAtest}{'count'}++;
2063     + $found_SARules{$SAtest}{'totalhits'} += $1;
2064     + $sum_SARules++
2065     + }
2066     + }
2067     +
2068     + }
2069     + #only select Logterse output
2070     + next LINE unless m/terse plugin/;
2071     +
2072     +
2073     + my $abstime = Time::TAI64::tai2unix($tai);
2074     + my $abshour = floor( $abstime / 3600 ); # Hours since the epoch
2075     +
2076     +
2077     + my ($timestamp_part, $log_part) = split('`',$_,2); #bjr 0.6.12
2078     + my (@log_items) = split $FS, $log_part;
2079     +
2080     + my (@timestamp_items) = split(' ',$timestamp_part);
2081     +
2082     + # we store the more recent recipient domain, for domain statistics
2083     + # in fact, we only store the first recipient. Could be sort of headhache
2084     + # to obtain precise stats with many recipients on more than one domain !
2085     + my $proc = $timestamp_items[1] ; #numeric Id for the email
2086     +
2087     + $totalexamined++;
2088     +
2089     + # first spot the fetchmail and local deliveries.
2090     +
2091     + # Spot from local workstation
2092     + $localflag = 0;
2093     + $WebMailflag = 0;
2094     + if ( $log_items[1] =~ m/.*$DomainName.*/ ) {
2095     + $localsendtotal++;
2096     + $counts{$abshour}{$CATLOCAL}++;
2097     + $localflag = 1;
2098     + }
2099     +
2100     + # see if from localhost
2101     + elsif ( $log_items[1] =~ m/.*$localhost.*/ ) {
2102     +
2103     + # but not if it comes from fetchmail
2104     + if ( $log_items[3] =~ m/.*$FETCHMAIL.*/ ) { }
2105     + else {
2106     +
2107     + # might still be from mailman here
2108     + if ( $log_items[3] =~ m/.*$MAILMAN.*/ ) {
2109     + $mailmansendcount++;
2110     + $localsendtotal++;
2111     + $counts{$abshour}{$CATMAILMAN}++;
2112     + $localflag = 1;
2113     + }
2114     + else {
2115     +
2116     + # eliminate incoming localhost spoofs
2117     + if ( $log_items[8] =~ m/.*msg denied before queued.*/ ) { }
2118     + else {
2119     + $localflag = 1;
2120     + $WebMailsendtotal++;
2121     + $counts{$abshour}{$CATWEBMAIL}++;
2122     + $WebMailflag = 1;
2123     + }
2124     + }
2125     + }
2126     + }
2127     +
2128     + # try to spot fetchmail emails
2129     + if ( $log_items[0] =~ m/.*$FetchmailIP.*/ ) {
2130     + $localAccepttotal++;
2131     + $counts{$abshour}{$CATFETCHMAIL}++;
2132     + }
2133     + elsif ( $log_items[3] =~ m/.*$FETCHMAIL.*/ ) {
2134     + $localAccepttotal++;
2135     + $counts{$abshour}{$CATFETCHMAIL}++;
2136     + }
2137     +
2138     +# and adjust for recipient field if not set-up by denying plugin - extract from deny msg
2139     +
2140     + if ( length( $log_items[4] ) == 0 ) {
2141     + if ( $log_items[5] eq 'check_goodrcptto' ) {
2142     + if ( $log_items[7] gt "invalid recipient" ) {
2143     + $log_items[4] =
2144     + substr( $log_items[7], 18 ) #Leave only email address
2145     + }
2146     + }
2147     + }
2148     +
2149     + # if ( ( $currentrcptdomain{ $proc } || '' ) eq '' ) {
2150     + # reduce to lc and process each e,mail if a list, pseperatedy commas
2151     + my $recipientmail = lc( $log_items[4] );
2152     + if ( $recipientmail =~ m/.*,/ ) {
2153     +
2154     + #comma - split the line and deal with each domain
2155     + # print $recipientmail."\n";
2156     + my ($recipients) = split( ',', $recipientmail );
2157     + foreach my $recip ($recipients) {
2158     + $proc = $proc . $recip;
2159     +
2160     + # print $proc."\n";
2161     + $currentrcptdomain{$proc} = $recip;
2162     + add_in_domain($proc);
2163     + $recipcount++;
2164     + }
2165     +
2166     + # print "*\n";
2167     + #count emails with more than one recipient
2168     + # $recipientmail =~ m/(.*),/;
2169     + # $currentrcptdomain{ $proc } = $1;
2170     + }
2171     + else {
2172     + $proc = $proc . $recipientmail;
2173     + $currentrcptdomain{$proc} = $recipientmail;
2174     + add_in_domain($proc);
2175     + $recipcount++;
2176     + }
2177     +
2178     + # } else {
2179     + # # there more than a recipient for a mail, how many daily ?
2180     + # $morethanonercpt++;
2181     + # }
2182     +
2183     +
2184     + # then categorise the result
2185     +
2186     +
2187     + if (exists $log_items[5]) {
2188     +
2189     + $found_qpcodes{$log_items[5]}++; ##Count different qpsmtpd result codes
2190     +
2191     + #Check for badly formed lines (from earlier testing)
2192     +
2193     + if ($log_items[5] eq 'check_earlytalker') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
2194     +
2195     + if ($log_items[5] eq 'check_relay') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
2196     +
2197     + if ($log_items[5] eq 'check_norelay') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
2198     +
2199     + if ($log_items[5] eq 'require_resolvable_fromhost') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
2200     +
2201     + if ($log_items[5] eq 'check_basicheaders') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
2202     +
2203     + if ($log_items[5] eq 'rhsbl') { $RBLcount++;$counts{$abshour}{$CATRBLDNS}++;mark_domain_rejected($proc);next LINE}
2204     +
2205     + if ($log_items[5] eq 'dnsbl') { $RBLcount++;$counts{$abshour}{$CATRBLDNS}++;mark_domain_rejected($proc);next LINE}
2206     +
2207     + if ($log_items[5] eq 'check_badmailfrom') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
2208     +
2209     + if ($log_items[5] eq 'check_badrcptto_patterns') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
2210     +
2211     + if ($log_items[5] eq 'check_badrcptto') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
2212     +
2213     + if ($log_items[5] eq 'check_spamhelo') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
2214     +
2215     + if ($log_items[5] eq 'check_goodrcptto extn') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
2216     +
2217     + if ($log_items[5] eq 'rcpt_ok') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
2218     +
2219     + if ($log_items[5] eq 'pattern_filter') { $PatternFilterCount++;$counts{$abshour}{$CATEXECUT}++;mark_domain_rejected($proc);next LINE}
2220     +
2221     + if ($log_items[5] eq 'virus::pattern_filter') { $PatternFilterCount++;$counts{$abshour}{$CATEXECUT}++;mark_domain_rejected($proc);next LINE}
2222     +
2223     + if ($log_items[5] eq 'check_goodrcptto') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
2224     +
2225     + if ($log_items[5] eq 'check_smtp_forward') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
2226     +
2227     + if ($log_items[5] eq 'count_unrecognized_commands') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
2228     +
2229     + if ($log_items[5] eq 'tnef2mime') { next LINE} #Not expecting this one.
2230     +
2231     + if ($log_items[5] eq 'spamassassin') { $above15++;$counts{$abshour}{$CATSPAMDEL}++;
2232     + # and extract the spam score
2233     + if ($log_items[8] =~ "Yes, hits=(.*) required=([0-9\.]+)") {$rejectspamavg += $1}
2234     + mark_domain_rejected($proc);
2235     + next LINE
2236     + }
2237     +
2238     + if ($log_items[5] eq 'virus::clamav') { $infectedcount++;$counts{$abshour}{$CATVIRUS}++;
2239     + #extract the virus name
2240     + if ($log_items[7] =~ "Virus Found: (.*)" ) {$found_viruses{$1}++;}
2241     + mark_domain_rejected($proc);
2242     + next LINE
2243     + }
2244     +
2245     + if ($log_items[5] eq 'queued') { $Accepttotal++;
2246     + #extract the spam score
2247     + if ($log_items[8] =~ ".*hits=(.*) required=([0-9\.]+)") {
2248     + $score = $1;
2249     +# print $log_items[8]."<".$score.">\n";
2250     + if ($score < $SATagLevel) { $hamcount++;$counts{$abshour}{$CATHAM}++;$hamavg += $score}
2251     + else {$spamcount++;$counts{$abshour}{$CATSPAM}++;$spamavg += $score}
2252     + } else {
2253     + # no SA score - so it must be ham
2254     + $hamcount++;$counts{$abshour}{$CATHAM}++;
2255     + }
2256     + if ( ( $currentrcptdomain{ $proc } || '' ) ne '' ) {
2257     + $byrcptdomain{ $currentrcptdomain{ $proc } }{ 'accept' }++ ;
2258     + $currentrcptdomain{ $proc } = '' ;
2259     + }
2260     + next LINE
2261     + }
2262     +
2263     + print $log_items[5]."\n"; #Not detected
2264     +
2265     + }
2266     +
2267     +} #END OF MAIN LOOP
2268     +
2269     +#total up grand total Columns
2270     +$nhour = floor( $start / 3600 );
2271     +while ( $nhour < $end / 3600 ) {
2272     + $ncateg = 0; #past the where it came from columns
2273     + while ( $ncateg < @categs) {
2274     + #total columns
2275     + $counts{$GRANDTOTAL}{$categs[$ncateg]} += $counts{$nhour}{$categs[$ncateg]};
2276     +
2277     + # and total rows
2278     + if ( $ncateg < $categlen && $ncateg>=$countfromhere) {#skip initial columns of non final reasons
2279     + $counts{$nhour}{$categs[@categs-2]} += $counts{$nhour}{$categs[$ncateg]};
2280     + }
2281     + $ncateg++
2282     + }
2283     +
2284     + $nhour++;
2285     +}
2286     +
2287     +
2288     +
2289     +#Compute row totals and row percentages
2290     +$nhour = floor( $start / 3600 );
2291     +while ( $nhour < $end / 3600 ) {
2292     + $counts{$nhour}{$categs[@categs-1]} = $counts{$nhour}{$categs[@categs-2]}*100/$totalexamined if $totalexamined;
2293     + $nhour++;
2294     +
2295     +}
2296     +
2297     +#compute column percentages
2298     + $ncateg = 0;
2299     + while ( $ncateg < @categs) {
2300     + if ($ncateg == @categs-1) {
2301     + $counts{$PERCENT}{$categs[$ncateg]} = $counts{$GRANDTOTAL}{$categs[$ncateg-1]}*100/$totalexamined if $totalexamined;
2302     + } else {
2303     + $counts{$PERCENT}{$categs[$ncateg]} = $counts{$GRANDTOTAL}{$categs[$ncateg]}*100/$totalexamined if $totalexamined;
2304     + }
2305     + $ncateg++
2306     + }
2307     +
2308     +#compute sum of row percentages
2309     +$nhour = floor( $start / 3600 );
2310     +while ( $nhour < $end / 3600 ) {
2311     + $counts{$GRANDTOTAL}{$categs[@categs-1]} += $counts{$nhour}{$categs[@categs-1]};
2312     + $nhour++;
2313     +
2314     +}
2315     +
2316     +my $QueryNoLogTerse = ($totalexamined==0); #might indicate logterse not installed in qpsmtpd plugins
2317     +
2318     +#Calculate some numbers
2319     +
2320     +$spamavg = $spamavg / $spamcount if $spamcount;
2321     +$rejectspamavg = $rejectspamavg / $above15 if $above15;
2322     +$hamavg = $hamavg / $hamcount if $hamcount;
2323     +
2324     +# RBL etc percent of total SMTP sessions
2325     +
2326     +my $rblpercent = ( ( $RBLcount / $totalexamined ) * 100 ) if $totalexamined;
2327     +my $PatternFilterpercent = ( ( $PatternFilterCount / $totalexamined ) * 100 ) if $totalexamined;
2328     +my $Miscpercent = ( ( $MiscDenyCount / $totalexamined ) * 100 ) if $totalexamined;
2329     +
2330     +#Spam and virus percent of total email downloaded
2331     +#Expressed as a % of total examined
2332     +my $spampercent = ( ( $spamcount / $totalexamined ) * 100 ) if $totalexamined;
2333     +my $hampercent = ( ( $hamcount / $totalexamined ) * 100 ) if $totalexamined;
2334     +my $hrsinperiod = ( ( $end - $start ) / 3600 );
2335     +my $emailperhour = ( $totalexamined / $hrsinperiod ) if $totalexamined;
2336     +my $above15percent = ( $above15 / $totalexamined * 100 ) if $totalexamined;
2337     +my $infectedpercent = ( ( $infectedcount / ($totalexamined) ) * 100 ) if $totalexamined;
2338     +my $AcceptPercent = ( ( $Accepttotal / ($totalexamined) ) * 100 ) if $totalexamined;
2339     +
2340     +my $oldfh;
2341     +
2342     +#Open Sendmail if we are mailing it
2343     +if ( $opt{'mail'} && !$disabled ) {
2344     + open( SENDMAIL, "|$opt{'sendmail'} -oi -t -odq" )
2345     + or die "Can't open sendmail: $!\n";
2346     + print SENDMAIL "From: $opt{'from'}\n";
2347     + print SENDMAIL "To: $opt{'mail'}\n";
2348     + print SENDMAIL "Subject: Spam Filter Statistics from $hostname - ",
2349     + strftime( "%F", localtime($start) ), "\n\n";
2350     + $oldfh = select SENDMAIL;
2351     +}
2352     +
2353     +my $telapsed = time - $tstart;
2354     +
2355     +if ( !$disabled ) {
2356     +
2357     + #Output results
2358     + print "SMEServer daily Anti-Virus and Spamfilter statistics", "\n";
2359     + print "----------------------------------------------------", "\n\n";
2360     +
2361     + print "$0 Version : $opt{'version'}", "\n\n";
2362     + print "Period Beginning : ", strftime( "%c", localtime($start) ), "\n";
2363     + print "Period Ending : ", strftime( "%c", localtime($end) ), "\n";
2364     + print "\n";
2365     +
2366     + print "Clam Version : ", `freshclam -V`;
2367     + print "SpamAssassin Version : ", `spamassassin -V`;
2368     + printf "Tag level: %3d; Reject level: %3d $warnnoreject\n", $SATagLevel,
2369     + $SARejectLevel;
2370     + if ($HighLogLevel) {
2371     + printf "*Loglevel is set to: ".$LogLevel. " - you only need it set to 6\n";
2372     + printf "\tYou can set it this way:\n";
2373     + printf "\tconfig setprop qpsmtpd LogLevel 6\n";
2374     + printf "\tsignal-event email-update\n";
2375     + printf "\tsv t /var/service/qpsmtpd\n\n";
2376     + }
2377     + print "\n";
2378     + printf "Reporting Period : %.2f hrs\n", $hrsinperiod;
2379     + print "----------------------------\n";
2380     + print "\n";
2381     +
2382     + printf "All SMTP connections accepted:%8d \n", $totalexamined;
2383     +
2384     + printf "Emails per hour : %8.1f/hr\n", $emailperhour || 0;
2385     + print "\n";
2386     + printf "Average spam score (accepted): %11.2f\n", $spamavg || 0;
2387     + printf "Average spam score (rejected): %11.2f\n", $rejectspamavg || 0;
2388     + printf "Average ham score : %11.2f\n", $hamavg || 0;
2389     + print "\n";
2390     + print "Statistics by Hour\n";
2391     +
2392     + #
2393     + # start by working out which colunns to show - tag the display array
2394     + #
2395     + $ncateg = 1; ##skip the first column
2396     + $finaldisplay[0] = $true;
2397     + while ( $ncateg < $categlen) {
2398     + if ($display[$ncateg] eq 'yes') { $finaldisplay[$ncateg] = $true }
2399     + elsif ($display[$ncateg] eq 'no') { $finaldisplay[$ncateg] = $false }
2400     + else {
2401     + $finaldisplay[$ncateg] = ($counts{$GRANDTOTAL}{$categs[$ncateg]} != 0);
2402     + if ($finaldisplay[$ncateg]) {
2403     + #if it has been non zero and auto, then make it yes for the future.
2404     + esmith::ConfigDB->open->get('mailstats')->set_prop($categs[$ncateg],'yes')
2405     + }
2406     +
2407     + }
2408     + $ncateg++
2409     + }
2410     + #make sure total and percentages are shown
2411     + $finaldisplay[@categs-2] = $true;
2412     + $finaldisplay[@categs-1] = $true;
2413     +
2414     +
2415     + # and put together the print lines
2416     + #
2417     + my $Line1; #Full Line across the page
2418     + my $Line2; #Broken Line across the page
2419     + my $Titles; #Column headers
2420     + my $Values; #Values
2421     + my $Totals; #Corresponding totals
2422     + my $Percent; # and column percentages
2423     +
2424     + my $hour = floor( $start / 3600 );
2425     + $Line1 = '';
2426     + $Line2 = '';
2427     + $Titles = '';
2428     + $Values = '';
2429     + $Totals = '';
2430     + $Percent = '';
2431     + while ( $hour < $end / 3600 ) {
2432     + if ($hour == floor( $start / 3600 )){
2433     + #Do all the once only things
2434     + $ncateg = 0;
2435     + while ( $ncateg < @categs) {
2436     + if ($finaldisplay[$ncateg]){
2437     + $Line1 .= substr('---------------------',0,$colwidth[$ncateg]);
2438     + $Line2 .= substr('---------------------',0,$colwidth[$ncateg]-1);
2439     + $Line2 .= " ";
2440     + $Titles .= sprintf('%'.($colwidth[$ncateg]-1).'s',$categs[$ncateg])." ";
2441     + if ($ncateg == 0) {
2442     + $Totals .= substr('TOTALS ',0,$colwidth[$ncateg]-2);
2443     + $Percent .= substr('PERCENTAGES ',0,$colwidth[$ncateg]-1);
2444     + } else {
2445     + # identify bottom right group and supress unless db->ShowGranPerc set
2446     + if ($ncateg==@categs-1){
2447     + $Totals .= sprintf('%'.$colwidth[$ncateg].'.1f',$counts{$GRANDTOTAL}{$categs[$ncateg]}).'%';
2448     + } else {
2449     + $Totals .= sprintf('%'.$colwidth[$ncateg].'d',$counts{$GRANDTOTAL}{$categs[$ncateg]});
2450     + }
2451     + $Percent .= sprintf('%'.($colwidth[$ncateg]-1).'.1f',$counts{$PERCENT}{$categs[$ncateg]}).'%';
2452     + }
2453     + }
2454     + $ncateg++
2455     + }
2456     + }
2457     +
2458     + $ncateg = 0;
2459     + while ( $ncateg < @categs) {
2460     + if ($finaldisplay[$ncateg]){
2461     + if ($ncateg == 0) {
2462     + $Values .= strftime( "%F, %H", localtime( $hour * 3600 ) )." "
2463     + } elsif ($ncateg == @categs-1) {
2464     + #percentages in last column
2465     + $Values .= sprintf('%'.($colwidth[$ncateg]-2).'.1f',$counts{$hour}{$categs[$ncateg]})."%";
2466     + } else {
2467     + #body numbers
2468     + $Values .= sprintf('%'.($colwidth[$ncateg]-1).'d',$counts{$hour}{$categs[$ncateg]})." ";
2469     + }
2470     + if (($ncateg == @categs-1)){$Values=$Values."\n"} #&& ($hour == floor($end / 3600)-1)
2471     + }
2472     + $ncateg++
2473     + }
2474     +
2475     + $hour++;
2476     + }
2477     +
2478     + # print it.
2479     + print $Line1."\n";
2480     + print $Titles."\n";
2481     + print $Line2."\n";
2482     + print $Values."\n";
2483     + print $Line2."\n";
2484     + print $Totals."\n";
2485     + print $Percent."\n";
2486     + print $Line1."\n";
2487     +
2488     +
2489     + if ($localAccepttotal>0) {
2490     + print "*Fetchml* means connections from Fetchmail delivering email\n";
2491     + }
2492     + print "*Local* means connections from workstations on local LAN.\n";
2493     + print "*Non\.Conf\.* means sending mailserver did not conform to correct protocol.\n";
2494     + print " or email was to non existant address.\n";
2495     + print "\n";
2496     +
2497     + if ($QueryNoLogTerse) {
2498     + print "* - as no records where found, it looks as though you may not have the *logterse* \nplugin running as part of qpsmtpd \n";
2499     +# print " to enable it follow the instructions at .............................\n";
2500     + }
2501     +
2502     +
2503     + if ( !$RHSenabled || !$DNSenabled ) {
2504     +
2505     + # comment about RBL not set
2506     + print
2507     +"* - This means that one or more of the possible spam black listing services\n that are available have not been enabled.\n";
2508     + print " You have not enabled:\n";
2509     +
2510     + if ( !$RHSenabled ) {
2511     + print " RHSBL\n";
2512     + }
2513     +
2514     + if ( !$DNSenabled ) {
2515     + print " DNSBL\n";
2516     + }
2517     +
2518     +
2519     + print " To enable these you can use the following commands:\n";
2520     + if ( !$RHSenabled ) {
2521     + print " config setprop qpsmtpd RHSBL enabled\n";
2522     + }
2523     +
2524     + if ( !$DNSenabled ) {
2525     + print " config setprop qpsmtpd DNSBL enabled\n";
2526     + }
2527     +
2528     + # there so much templates to expand... (PS)
2529     + print " Followed by:\n signal-event email-update and\n sv t /var/service/qpsmtpd\n\n";
2530     + }
2531     +
2532     +# if ($Webmailsendtotal > 0) {print "If you have the mailman contrib installed, then the webmail totals might include some mailman emails\n"}
2533     +
2534     + # time to do a 'by recipient domain' report
2535     + print "\nIncoming mails by recipient domains usage\n";
2536     + print "-----------------------------------------\n";
2537     + print
2538     + "Domains Type Total Denied XferErr Accept \%accept\n";
2539     + print
2540     + "---------------------------- ---------- ------ ------ ------- ------ -------\n";
2541     + my %total = (
2542     + total => 0,
2543     + deny => 0,
2544     + xfer => 0,
2545     + accept => 0,
2546     + );
2547     + foreach my $domain (
2548     + sort {
2549     + join( "\.", reverse( split /\./, $a ) ) cmp
2550     + join( "\.", reverse( split /\./, $b ) )
2551     + } keys %byrcptdomain
2552     + )
2553     + {
2554     + next if ( ( $byrcptdomain{$domain}{'total'} || 0 ) == 0 );
2555     + my $tp = $byrcptdomain{$domain}{'type'} || 'other';
2556     + my $to = $byrcptdomain{$domain}{'total'} || 0;
2557     + my $de = $byrcptdomain{$domain}{'deny'} || 0;
2558     + my $xr = $byrcptdomain{$domain}{'xfer'} || 0;
2559     + my $ac = $byrcptdomain{$domain}{'accept'} || 0;
2560     + printf "%-28s %-10s %6d %6d %7d %6d %6.2f%%\n", $domain, $tp, $to,
2561     + $de, $xr, $ac, $ac * 100 / $to;
2562     + $total{'total'} += $to;
2563     + $total{'deny'} += $de;
2564     + $total{'xfer'} += $xr;
2565     + $total{'accept'} += $ac;
2566     + }
2567     + print
2568     + "---------------------------- ---------- ------ ------- ------ ------ -------\n";
2569     +
2570     + # $total{ 'total' } can be equal to 0, bad for divisions...
2571     + my $perc1 = 0;
2572     + my $perc2 = 0;
2573     +
2574     +
2575     + if ( $total{'total'} != 0 ) {
2576     + $perc1 = $total{'accept'} * 100 / $total{'total'};
2577     + $perc2 = ( ( $total{'total'} + $morethanonercpt ) / $total{'total'} );
2578     + }
2579     + printf
2580     + "Total %6d %6d %7d %6d %6.2f%%\n\n",
2581     + $total{'total'}, $total{'deny'}, $total{'xfer'}, $total{'accept'},
2582     + $perc1;
2583     + printf
2584     + "%d mails were processed for %d Recipients\nThe average recipients by mail is %4.2f\n\n",
2585     + $total{'total'}, ( $total{'total'} + $morethanonercpt ), $perc2;
2586     +
2587     + if ( $infectedcount > 0 ) {
2588     + show_virus_variants();
2589     + }
2590     +
2591     + # get enable/disable subsections
2592     + my $enableqpsmtpdcodes;
2593     + my $enableSARules;
2594     + my $enablejunkMailList;
2595     + my $savedata;
2596     + if ($cdb->get('mailstats')){
2597     + $enableqpsmtpdcodes = ($cdb->get('mailstats')->prop("QpsmtpdCodes") || "enabled") eq "enabled" || $false;
2598     + $enableSARules = ($cdb->get('mailstats')->prop("SARules") || "enabled") eq "enabled" || $false;
2599     + $enablejunkMailList = ($cdb->get('mailstats')->prop("JunkMailList") || "enabled") eq "enabled" || $false;
2600     + $savedata = ($cdb->get('mailstats')->prop("SaveDataToMySQL") || "no") eq "yes" || $false;
2601     + } else {
2602     + $enableqpsmtpdcodes = $true;
2603     + $enableSARules = $true;
2604     + $enablejunkMailList = $true;
2605     + $savedata = $false;
2606     + }
2607     +
2608     + if ($enableqpsmtpdcodes) {show_qpsmtpd_codes();}
2609     +
2610     + if ($enableSARules) {show_SARules_codes();}
2611     +
2612     + if ($enablejunkMailList) {List_Junkmail();}
2613     +
2614     + print "\nDone. Report generated in $telapsed sec.\n\n";
2615     +
2616     + if ($savedata) { save_data(); }
2617     + else
2618     + { print "No data saved - if you want to save data to a MySQL database, then please use:\n".
2619     + "config setprop mailstats SaveDataToMySQL yes\nYou must have created the database first.";
2620     + }
2621     +
2622     +
2623     + #Close Senmdmail if it was opened
2624     + if ( $opt{'mail'} ) {
2625     + select $oldfh;
2626     + close(SENDMAIL);
2627     + }
2628     +
2629     +} ##report disabled
2630     +
2631     +#All done
2632     +exit 0;
2633     +
2634     +#############################################################################
2635     +# Subroutines ###############################################################
2636     +#############################################################################
2637     +
2638     +
2639     +################################################
2640     +# Determine analysis period (start and end time)
2641     +################################################
2642     +sub analysis_period {
2643     + my $startdate = shift;
2644     + my $enddate = shift;
2645     +
2646     + my $secsininterval = 86400; #daily default
2647     + my $time;
2648     +
2649     + if ($cdb->get('mailstats'))
2650     + {
2651     + my $interval = $cdb->get('mailstats')->prop('Interval') || 'daily';
2652     + if ($interval eq "weekly") {
2653     + $secsininterval = 86400*7;
2654     + } elsif ($interval eq "fortnightly") {
2655     + $secsininterval = 86400*14;
2656     + } elsif ($interval eq "monthly") {
2657     + $secsininterval = 86400;
2658     + } elsif ($interval =~m/\d+/) {
2659     + $secsininterval = $interval*3600;
2660     + };
2661     + my $base = $cdb->get('mailstats')->prop('Base') || 'Midnight';
2662     + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
2663     + localtime(time);
2664     + if ($base eq "Midnight"){
2665     + $sec = 0;$min=0;$hour=0;
2666     + } elsif ($base eq "Midday"){
2667     + $sec = 0;$min=0;$hour=12;
2668     + } elsif ($base =~m/\d+/){
2669     + $sec=0;$min=0;$hour=$base;
2670     + };
2671     + $time = timelocal($sec,$min,$hour,$mday,$mon,$year)
2672     + }
2673     + my $start = UnixDate( $startdate, "%s" );
2674     + my $end = $enddate ? UnixDate( $enddate, "%s" ) :
2675     + $startdate ? $start + $secsininterval : $time;
2676     + $start = $startdate ? $start : $end - $secsininterval;
2677     + return ( $start > $end ) ? ( $end, $start ) : ( $start, $end );
2678     +}
2679     +
2680     +sub dbg {
2681     + my $msg = shift;
2682     +
2683     + if ( $opt{debug} ) {
2684     + print STDERR $msg;
2685     + }
2686     +}
2687     +
2688     +sub List_Junkmail {
2689     +
2690     + #
2691     + # Show how many junkmails in each user's junkmail folder.
2692     + #
2693     + use esmith::AccountsDB;
2694     + my $adb = esmith::AccountsDB->open_ro;
2695     + my $entry;
2696     + foreach my $user ( $adb->users ) {
2697     + my $found = 0;
2698     + my $junkmail_dir =
2699     + "/home/e-smith/files/users/" . $user->key . "/Maildir/.junkmail";
2700     + foreach my $dir (qw(new cur)) {
2701     +
2702     + # Now get the content list for the directory.
2703     + if ( opendir( QDIR, "$junkmail_dir/$dir" ) ) {
2704     + while ( $entry = readdir(QDIR) ) {
2705     + next if $entry =~ /^\./;
2706     + $found++;
2707     + }
2708     + closedir(QDIR);
2709     + }
2710     + }
2711     + if ( $found != 0 ) {
2712     + $junkcount{ $user->key } = $found;
2713     + }
2714     + }
2715     + my $i = keys %junkcount;
2716     + if ( $i > 0 ) {
2717     + print("Junk Mails left in folder:\n");
2718     + print("-------------------------\n");
2719     + print("Count\tUser\n");
2720     + print("-------------------------\n");
2721     + foreach my $thisuser (
2722     + sort { $junkcount{$b} <=> $junkcount{$a} }
2723     + keys %junkcount
2724     + )
2725     + {
2726     + printf "%d", $junkcount{$thisuser};
2727     + print "\t" . $thisuser . "\n";
2728     + }
2729     + print("-------------------------\n");
2730     + }
2731     + else {
2732     + print "***No junkmail folders with emails***\n";
2733     + }
2734     +}
2735     +
2736     +sub show_virus_variants
2737     +
2738     +#
2739     +# Show a league table of the different virus types found today
2740     +#
2741     +
2742     +{
2743     +
2744     + print("Virus Statistics by name:\n");
2745     + print("---------------------------------------------\n");
2746     + foreach my $virus (sort { $found_viruses{$b} <=> $found_viruses{$a} }
2747     + keys %found_viruses)
2748     + {
2749     + print "Rejected $found_viruses{$virus}\t$virus\n";
2750     + }
2751     + print("---------------------------------------------\n\n");
2752     +}
2753     +
2754     +sub show_qpsmtpd_codes
2755     +
2756     +#
2757     +# Show a league table of the qpsmtpd result codes found today
2758     +#
2759     +
2760     +{
2761     +
2762     + print("Qpsmtpd codes league table:\n");
2763     + print("---------------------------------------------\n");
2764     + print("Count\tPercent\tReason\t\n");
2765     + print("---------------------------------------------\n");
2766     + foreach my $qpcode (sort { $found_qpcodes{$b} <=> $found_qpcodes{$a} }
2767     + keys %found_qpcodes)
2768     + {
2769     + print "$found_qpcodes{$qpcode}\t".sprintf('%4.1f',$found_qpcodes{$qpcode}*100/$totalexamined)."%\t$qpcode\n" if $totalexamined;
2770     + }
2771     + print("---------------------------------------------\n\n");
2772     +}
2773     +
2774     +sub show_SARules_codes
2775     +
2776     +#
2777     +# Show a league table of the SARules result codes found today
2778     +# suppress any lower than DB mailstats/SARulePercentThreshold
2779     +#
2780     +
2781     +{
2782     +
2783     + my ($percentthreshold);
2784     + my ($defaultpercentthreshold);
2785     +
2786     + if ($totalexamined >0 && $sum_SARules*100/$totalexamined > $SARulethresholdPercent) {
2787     + $defaultpercentthreshold = $maxcutoff
2788     + } else {
2789     + $defaultpercentthreshold = $mincutoff
2790     + }
2791     + if ($cdb->get('mailstats')){
2792     + $percentthreshold = $cdb->get('mailstats')->prop("SARulePercentThreshold") || $defaultpercentthreshold;
2793     + } else {
2794     + $percentthreshold = $defaultpercentthreshold
2795     + }
2796     + print("Spamassassin Rules:\n");
2797     + print("---------------------------------------------\n");
2798     + print("Count\tPercent\tRule\t\n");
2799     + print("---------------------------------------------\n");
2800     + foreach my $SARule (sort { $found_SARules{$b}{'count'} <=> $found_SARules{$a}{'count'} }
2801     + keys %found_SARules)
2802     + {
2803     + my $percent = $found_SARules{$SARule}{'count'} * 100 / $totalexamined
2804     + if $totalexamined;
2805     + my $avehits = $found_SARules{$SARule}{'totalhits'} /
2806     + $found_SARules{$SARule}{'count'}
2807     + if $found_SARules{$SARule}{'count'};
2808     + if ( $percent > $percentthreshold ) {
2809     + print "$found_SARules{$SARule}{'count'}\t"
2810     + . sprintf( '%4.1f', $percent ) . "%\t"
2811     + . sprintf( '%4.1f', $avehits )
2812     + . "\t$SARule\n"
2813     + if $totalexamined;
2814     + }
2815     + }
2816     + print("---------------------------------------------\n\n");
2817     +
2818     +
2819     +}
2820     +
2821     +sub mark_domain_rejected
2822     +
2823     +#
2824     +# Tag domain as having a rejected email
2825     +#
2826     +{
2827     +my ($proc) = @_;
2828     +if ( ( $currentrcptdomain{ $proc } || '' ) ne '' ) {
2829     + $byrcptdomain{ $currentrcptdomain{ $proc } }{ 'deny' }++ ;
2830     + $currentrcptdomain{ $proc } = '' ;
2831     + }
2832     +}
2833     +
2834     +sub mark_domain_err
2835     +
2836     + #
2837     + # Tag domain as having an error on email transfer
2838     + #
2839     +{
2840     + my ($proc) = @_;
2841     + if ( ( $currentrcptdomain{$proc} || '' ) ne '' ) {
2842     + $byrcptdomain{ $currentrcptdomain{$proc} }{'xfer'}++;
2843     + $currentrcptdomain{$proc} = '';
2844     + }
2845     +}
2846     +
2847     +sub add_in_domain
2848     +
2849     + #
2850     + # add recipient domain into hash
2851     + #
2852     +{
2853     + my ($proc) = @_;
2854     +
2855     + #split to just domain bit.
2856     + $currentrcptdomain{$proc} =~ s/.*@//;
2857     + $currentrcptdomain{$proc} =~ s/[^\w\-\.]//g;
2858     + $currentrcptdomain{$proc} =~ s/>//g;
2859     + my $NotableDomain = 0;
2860     + if ( defined( $byrcptdomain{ $currentrcptdomain{$proc} }{'type'} ) ) {
2861     + $NotableDomain = 1;
2862     + }
2863     + else {
2864     + foreach (@extdomain) {
2865     + if ( $currentrcptdomain{$proc} =~ m/$_$/ ) {
2866     + $NotableDomain = 1;
2867     + last;
2868     + }
2869     + }
2870     + }
2871     + if ( !$NotableDomain ) {
2872     +
2873     + # check for outgoing email
2874     + if ( $localflag == 1 ) { $currentrcptdomain{$proc} = 'Outgoing' }
2875     + else { $currentrcptdomain{$proc} = 'Others' }
2876     + }
2877     + else {
2878     + if ( $localflag == 1 ) { $currentrcptdomain{$proc} = 'Internal' }
2879     + }
2880     + $byrcptdomain{ $currentrcptdomain{$proc} }{'total'}++;
2881     +}
2882     +
2883     +sub save_data
2884     +
2885     + #
2886     + # Save the data to a MySQL database
2887     + #
2888     +{
2889     + use DBI;
2890     + my $tstart = time;
2891     + my $DBname = "mailstats";
2892     + my $host = esmith::ConfigDB->open_ro->get('mailstats')->prop('DBHost') || "localhost";
2893     + my $port = esmith::ConfigDB->open_ro->get('mailstats')->prop('DBPort') || "3306";
2894     + print "Saving data..";
2895     + my $dbh = DBI->connect( "DBI:mysql:database=$DBname;host=$host;port=$port",
2896     + "mailstats", "mailstats" )
2897     + or die "Cannot open mailstats db - has it beeen created?";
2898     +
2899     + my $hour = floor( $start / 3600 );
2900     + my $reportdate = strftime( "%F", localtime( $hour * 3600 ) );
2901     + my $dateid = get_dateid($dbh,$reportdate);
2902     + my $reccount = 0; #count number of records written
2903     + my $servername = esmith::ConfigDB->open_ro->get('SystemName')->value . "."
2904     + . esmith::ConfigDB->open_ro->get('DomainName')->value;
2905     + # now fill in day related stats - must always check for it already there
2906     + # incase the module is run more than once in a day
2907     + my $SAScoresid = check_date_rec($dbh,"SAscores",$dateid,$servername);
2908     + $dbh->do( "UPDATE SAscores SET ".
2909     + "acceptedcount=".$spamcount.
2910     + ",rejectedcount=".$above15.
2911     + ",hamcount=".$hamcount.
2912     + ",acceptedscore=".$spamhits.
2913     + ",rejectedscore=".$rejectspamhits.
2914     + ",hamscore=".$hamhits.
2915     + ",totalsmtp=".$totalexamined.
2916     + ",totalrecip=".$recipcount.
2917     + ",servername='".$servername.
2918     + "' WHERE SAscoresid =".$SAScoresid);
2919     + # Junkmail stats
2920     + # delete if already there
2921     + $dbh->do("DELETE from JunkMailStats WHERE dateid = ".$dateid." AND servername='".$servername."'");
2922     + # and add records
2923     + foreach my $thisuser (keys %junkcount){
2924     + $dbh->do("INSERT INTO JunkMailStats (dateid,user,count,servername) VALUES ('".
2925     + $dateid."','".$thisuser."','".$junkcount{$thisuser}."','".$servername."')");
2926     + $reccount++;
2927     + }
2928     + #SA rules - delete any first
2929     + $dbh->do("DELETE from SARules WHERE dateid = ".$dateid." AND servername='".$servername."'");
2930     + # and add records
2931     + foreach my $thisrule (keys %found_SARules){
2932     + $dbh->do("INSERT INTO SARules (dateid,rule,count,totalhits,servername) VALUES ('".
2933     + $dateid."','".$thisrule."','".$found_SARules{$thisrule}{'count'}."','".
2934     + $found_SARules{$thisrule}{'totalhits'}."','".$servername."')");
2935     + $reccount++;
2936     + }
2937     + #qpsmtpd result codes
2938     + $dbh->do("DELETE from qpsmtpdcodes WHERE dateid = ".$dateid." AND servername='".$servername."'");
2939     + # and add records
2940     + foreach my $thiscode (keys %found_qpcodes){
2941     + $dbh->do("INSERT INTO qpsmtpdcodes (dateid,reason,count,servername) VALUES ('".
2942     + $dateid."','".$thiscode."','".$found_qpcodes{$thiscode}."','".$servername."')");
2943     + $reccount++;
2944     +}
2945     + # virus stats
2946     + $dbh->do("DELETE from VirusStats WHERE dateid = ".$dateid." AND servername='".$servername."'");
2947     + # and add records
2948     + foreach my $thisvirus (keys %found_viruses){
2949     + $dbh->do("INSERT INTO VirusStats (dateid,descr,count,servername) VALUES ('".
2950     + $dateid."','".$thisvirus."','".$found_viruses{$thisvirus}."','".$servername."')");
2951     + $reccount++;
2952     +
2953     + }
2954     + # domain details
2955     + $dbh->do("DELETE from domains WHERE dateid = ".$dateid." AND servername='".$servername."'");
2956     + # and add records
2957     + foreach my $domain (keys %byrcptdomain){
2958     + next if ( ( $byrcptdomain{$domain}{'total'} || 0 ) == 0 );
2959     + $dbh->do("INSERT INTO domains (dateid,domain,type,total,denied,xfererr,accept,servername) VALUES ('".
2960     + $dateid."','".$domain."','".($byrcptdomain{$domain}{'type'}||'other')."','"
2961     + .$byrcptdomain{$domain}{'total'}."','"
2962     + .($byrcptdomain{$domain}{'deny'}||0)."','"
2963     + .($byrcptdomain{$domain}{'xfer'}||0)."','"
2964     + .($byrcptdomain{$domain}{'accept'}||0)."','"
2965     + .$servername
2966     + ."')");
2967     + $reccount++;
2968     +
2969     + }
2970     + # finally - the hourly breakdown
2971     + # need to remember here that the date might change during the 24 hour span
2972     + my $nhour = floor( $start / 3600 );
2973     + my $ncateg;
2974     + while ( $nhour < $end / 3600 ) {
2975     + #see if the time record has been created
2976     + # print strftime("%H",localtime( $nhour * 3600 ) ).":00:00\n";
2977     + my $sth =
2978     + $dbh->prepare( "SELECT timeid FROM time WHERE time = '" . strftime("%H",localtime( $nhour * 3600 ) ).":00:00'");
2979     + $sth->execute();
2980     + if ( $sth->rows == 0 ) {
2981     + #create entry
2982     + $dbh->do( "INSERT INTO time (time) VALUES ('" .strftime("%H",localtime( $nhour * 3600 ) ).":00:00')" );
2983     + # and pick up timeid
2984     + $sth = $dbh->prepare("SELECT last_insert_id() AS timeid FROM time");
2985     + $sth->execute();
2986     + $reccount++;
2987     + }
2988     + my $timerec = $sth->fetchrow_hashref();
2989     + my $timeid = $timerec->{"timeid"};
2990     + $ncateg = 0;
2991     + # and extract date from first column of $count array
2992     + my $currentdate = strftime( "%F", localtime( $hour * 3600 ) );
2993     + # print "$currentdate.\n";
2994     + if ($currentdate ne $reportdate) {
2995     + #same as before?
2996     + $dateid = get_dateid($dbh,$currentdate);
2997     + $reportdate = $currentdate;
2998     + }
2999     + # delete for this date and time
3000     + $dbh->do("DELETE from ColumnStats WHERE dateid = ".$dateid." AND timeid = ".$timeid." AND servername='".$servername."'");
3001     + while ( $ncateg < @categs-1 ) {
3002     + # then add in each entry
3003     + if (($counts{$nhour}{$categs[$ncateg]} || 0) != 0) {
3004     + $dbh->do("INSERT INTO ColumnStats (dateid,timeid,descr,count,servername) VALUES ("
3005     + .$dateid.",".$timeid.",'".$categs[$ncateg]."',"
3006     + .$counts{$nhour}{$categs[$ncateg]}.",'".$servername."')");
3007     + $reccount++;
3008     + }
3009     +
3010     +# print("INSERT INTO ColumnStats (dateid,timeid,descr,count) VALUES ("
3011     +# .$dateid.",".$timeid.",'".$categs[$ncateg]."',"
3012     +# .$counts{$nhour}{$categs[$ncateg]}.")\n");
3013     +
3014     + $ncateg++;
3015     + }
3016     + $nhour++;
3017     + }
3018     + $dbh->disconnect();
3019     + my $telapsed = time - $tstart;
3020     + print "Saved $reccount records in $telapsed sec.";
3021     +}
3022     +
3023     +sub check_date_rec
3024     +
3025     + #
3026     + # check that a specific dated rec is there, create if not
3027     + #
3028     +{
3029     + my ( $dbh, $table, $dateid ) = @_;
3030     + my $sth =
3031     + $dbh->prepare(
3032     + "SELECT " . $table . "id FROM ".$table." WHERE dateid = '$dateid'" );
3033     + $sth->execute();
3034     + if ( $sth->rows == 0 ) {
3035     + #create entry
3036     + $dbh->do( "INSERT INTO ".$table." (dateid) VALUES ('" . $dateid . "')" );
3037     + # and pick up recordid
3038     + $sth = $dbh->prepare("SELECT last_insert_id() AS ".$table."id FROM ".$table);
3039     + $sth->execute();
3040     + }
3041     + my $rec = $sth->fetchrow_hashref();
3042     + $rec->{$table."id"}; #return the id of the reocrd (new or not)
3043     + }
3044     +
3045     + sub check_time_rec
3046     +
3047     + #
3048     + # check that a specific dated amd timed rec is there, create if not
3049     + #
3050     +{
3051     + my ( $dbh, $table, $dateid, $timeid ) = @_;
3052     + my $sth =
3053     + $dbh->prepare(
3054     + "SELECT " . $table . "id FROM ".$table." WHERE dateid = '$dateid' AND timeid = ".$timeid );
3055     + $sth->execute();
3056     + if ( $sth->rows == 0 ) {
3057     + #create entry
3058     + $dbh->do( "INSERT INTO ".$table." (dateid,timeid) VALUES ('" . $dateid . "', '".$timeid."')" );
3059     + # and pick up recordid
3060     + $sth = $dbh->prepare("SELECT last_insert_id() AS ".$table."id FROM ".$table);
3061     + $sth->execute();
3062     + }
3063     + my $rec = $sth->fetchrow_hashref();
3064     + $rec->{$table."id"}; #return the id of the record (new or not)
3065     + }
3066     +
3067     +sub get_dateid
3068     +
3069     +#
3070     +# Check that date is in db, and return corresponding id
3071     +#
3072     +{
3073     + my ($dbh,$reportdate) = @_;
3074     + my $sth =
3075     + $dbh->prepare( "SELECT dateid FROM date WHERE date = '" . $reportdate."'" );
3076     + $sth->execute();
3077     + if ( $sth->rows == 0 ) {
3078     + #create entry
3079     + $dbh->do( "INSERT INTO date (date) VALUES ('" . $reportdate . "')" );
3080     + # and pick up dateid
3081     + $sth = $dbh->prepare("SELECT last_insert_id() AS dateid FROM date");
3082     + $sth->execute();
3083     + }
3084     + my $daterec = $sth->fetchrow_hashref();
3085     + $daterec->{"dateid"};
3086     + }

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