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

Contents 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.3 - (show annotations) (download)
Tue Nov 25 16:20:25 2008 UTC (15 years, 11 months ago) by slords
Branch: MAIN
CVS Tags: smeserver-mailstats-0_0_3-15_el4_sme, smeserver-mailstats-0_0_3-14_el4_sme, HEAD
Changes since 1.2: +0 -0 lines
Restore

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