/[smecontribs]/rpms/smeserver-mailstats/contribs7/smeserver-mailstats-0.0.2_spamfilter-stats.patch
ViewVC logotype

Annotation of /rpms/smeserver-mailstats/contribs7/smeserver-mailstats-0.0.2_spamfilter-stats.patch

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


Revision 1.1 - (hide annotations) (download)
Fri Feb 15 18:10:51 2008 UTC (16 years, 3 months ago) by slords
Branch: MAIN
Import on branch contribs7 of package smeserver-mailstats-0.0.2-07.src.rpm

1 slords 1.1 diff -Nur -x '*.orig' -x '*.rej' smeserver-mailstats-0.0.2/root/usr/bin/spamfilter-stats-7.pl mezzanine_patched_smeserver-mailstats-0.0.2/root/usr/bin/spamfilter-stats-7.pl
2     --- smeserver-mailstats-0.0.2/root/usr/bin/spamfilter-stats-7.pl 2007-04-11 16:48:31.000000000 -0400
3     +++ mezzanine_patched_smeserver-mailstats-0.0.2/root/usr/bin/spamfilter-stats-7.pl 2007-09-10 17:06:26.000000000 -0400
4     @@ -1,774 +1,831 @@
5     -#!/usr/bin/perl -w
6     -
7     -#############################################################################
8     -#
9     -# This script provides daily SpamFilter statistics and deletes all users
10     -# junkmails. Configuration of the script is done by the Spam Filter
11     -# Server-Manager module
12     -#
13     -# April 2006 - no longer controlled by server manager, and does not delete files
14     -#
15     -# This script has been developed
16     -# by Jesper Knudsen at http://sme.swerts-knudsen.dk
17     -#
18     -# Revision History:
19     -#
20     -# August 13, 2003: Initial version
21     -# August 25, 2004: fixed problem when hostname had no-ASCII chars
22     -# March 23, 2006 Revised for sme7 RM
23     -# March 27, 2006 ditto BJR (http://www.abandonmicrosoft.co.uk)
24     -# - Merged Clamav and SA stats
25     -# - Moved all analysis to qsmtpd log
26     -# - Removed parameterised interval (for simplicity - not sure of format anyway)
27     -# - add in archived log files for people who have high turnover
28     -# - Alter labels to be more accurate
29     -# - Detect deleted spam (over threshold) without using spam score
30     -# - Detect RBL rejections
31     -# - Detect pattern (executible) rejections
32     -# - Look for the DENY labels - add in Miscellaneous category
33     -# April 6, 2006 - check qpsmtp log level and also DNS enable properties
34     -# - Average spam scores for under and over threshold seperatly
35     -# - Log tag and Reject levels
36     -# - TBD - check that RBL DENY are being detected (I have no date to check this)
37     -# April 7, 2007 - re-written by Charlie Brady totally in Perl
38     -# April 16, 2006 - move warnings to report
39     -# - Spot fetchmail deliveries
40     -# - Spot Internal connections from client PCs
41     -# - TBD check that RBL DENY are being detected (I have no data to check this)
42     -# April 30, 2006 - Pascal Schirrmann Start Time and End Time to noon - should be a param
43     -# so the script can be run at any time in the day.
44     -# - adds 'by recipients domains' stats Useful for MX-Backup or multi domains hosts
45     -# - Add a 'recipients per mail' stat. Useful : until now the sums are correct :-)
46     -# - Correct some messages about rbl who can led to wrong entry in the config database
47     -# ( and without expected results, of course !)
48     -# - improve a regexp in the SPAM detection
49     -# May 1, 2006 - BJR - Fix situation where mxbackup prop is not defined
50     -# - fix a spelling and minor format of domain report
51     -# May 9, 2006 - bjr - Make RBL percentage a percentage of total connections (else it >100%)
52     -# May 9, 2006 - ps - some 'sanity check' in the 'per domains part of the stats (to avoid / 0)
53     -# May 12, 2006 - ps - some cleanup in the 'per domains' stats
54     -# - Add a version number, logged in the mail
55     -# June 20, 2006 - bjr - Minor change to RBL instructions, and adjust domain table format
56     -# Feb 19, 2007 - bjr - Adjust table lines oin a couple of places
57     -# - bjr - and add documentation details about percentages etc
58     -# - bjr - Alter misc to "non conforming" anmd accumulated these hourly
59     -# - bjr - Express change over tag count to exclude spam rejected over threshold
60     -# - bjr - Change "processsed" to "fully downloaded"
61     -# - bjr - Change percentages so that they are all a percetnage of the total emails received
62     -# 0.6.1 - bjr - Change to use output from the logterse qpsmtpd plugin
63     -# 0.6.2 - bjr - Fix fetchmail tests
64     -# 0.6.3 - bjr - adjust for log-items change in order
65     -# 0.6.4&5 - bjr - Adjust table formatting
66     -# 0.6.6 - bjr - Take outgoing emails out of "others", add "Outgoing" and "Internal"
67     -# 0.6.7 - bjr - Fix missing plugins/wrong names. pull invalid recipient out of deny msg for goodrcptto
68     -# 0.6.8 - bjr - catch a few more plugin name failures
69     -#############################################################################
70     -
71     -
72     -# internal modules (part of core perl distribution)
73     -use strict;
74     -use warnings;
75     -use Getopt::Long;
76     -use Pod::Usage;
77     -use POSIX qw/strftime floor/;
78     -use Time::Local;
79     -use Date::Manip;
80     -use Time::TAI64;
81     -use esmith::ConfigDB;
82     -use esmith::DomainsDB;
83     -use Sys::Hostname;
84     -use Switch;
85     -
86     -my $hostname = hostname();
87     -
88     -#Configuration section
89     -my %opt = ();
90     -
91     -$opt{'version'} = '0.6.8'; # please update at each change.
92     -$opt{'debug'} = 0; # guess what ?
93     -$opt{'sendmail'} = '/usr/sbin/sendmail'; # Path to sendmail stub
94     -$opt{'from'} = 'spamfilter-stats'; # Who is the mail from
95     -$opt{'end'} = `date --iso-8601`; # midnight today
96     -my $yesterday = $opt{ 'end' };
97     -$yesterday =~ s/\-//g ;
98     -$yesterday--;
99     -$opt{'start'} = `date --iso-8601 -d $yesterday`; # midnight yesterday
100     -$opt{'mail'} = "admin";
101     -$opt{'timezone'} = `date +%z`;
102     -Date_Init("TZ=$opt{'timezone'}");
103     -my $FetchmailIP = '127.0.0.200'; #Apparent Ip address of fetchmail deliveries
104     -
105     -my $disabled = 0;
106     -
107     -my $tstart = time;
108     -
109     -#Local variables
110     -my $YEAR = ( localtime(time) )[5]; # this is years since 1900
111     -
112     -my $total = 0;
113     -my $spamcount = 0;
114     -my $spamavg = 0;
115     -my $hamcount = 0;
116     -my $hamavg = 0;
117     -my $rejectspamavg = 0;
118     -
119     -my $Accepttotal = 0;
120     -my $localAccepttotal = 0; #Fetchmail connections
121     -my $localsendtotal = 0; #Connections from local PCs
122     -my $totalexamined = 0; #total download + RBL etc
123     -my %sendtotalbyhour = ();
124     -my %localLANbyhour = ();
125     -my %localacceptbyhour = ();
126     -
127     -my $above15 = 0;
128     -my %above15byhour = ();
129     -
130     -my $RBLcount = 0;
131     -my %RBLbyhour = ();
132     -
133     -my $MiscDenyCount = 0;
134     -my %MiscDenybyhour = ();
135     -
136     -my $PatternFilterCount = 0;
137     -my %patternfilterbyhour = ();
138     -
139     -my $noninfectedcount = 0;
140     -my $okemailcount = 0;
141     -
142     -my $infectedcount = 0;
143     -my %infectedbyhour = ();
144     -my %found_viruses = ();
145     -
146     -my %spambyhour = ();
147     -my %hambyhour = ();
148     -
149     -my $warnnoreject = " ";
150     -my $rblnotset = ' ';
151     -
152     -my $FS = "\t"; # field separator used by logterse plugin
153     -my %log_items = ();
154     -my $score;
155     -my %timestamp_items = ();
156     -my $localflag = 0; #indicate if current email is local or not
157     -
158     -# some storage for by recipient domains stats (PS)
159     -# my bad : I have to deal with multiple simoultaneous connections
160     -# will play with the process number.
161     -# my $currentrcptdomain = '' ;
162     -my %currentrcptdomain ; # temporay store the recipient domain until end of mail processing
163     -my %byrcptdomain ; # Store 'by domains stats'
164     -my @extdomain ; # only useful in some MX-Backup case, when any subdomains are allowed
165     -my $morethanonercpt = 0 ; # count every 'second' recipients for a mail.
166     -
167     -# store the domain of interest. Every other records are stored in a 'Other' zone
168     -my $ddb = esmith::DomainsDB->open_ro or die "Couldn't open DomainsDB : $!\n";
169     -foreach my $domain( $ddb->get_all_by_prop( type => "domain" ) ) {
170     - $byrcptdomain{ $domain->key }{ 'type' }='local';
171     -}
172     -$byrcptdomain{ esmith::ConfigDB->open_ro->get('SystemName')->value . "."
173     - . esmith::ConfigDB->open_ro->get('DomainName')->value }{ 'type' } = 'local';
174     -
175     -# is this system a MX-Backup ?
176     -if (esmith::ConfigDB->open_ro->get('mxbackup')){
177     - if ( ( esmith::ConfigDB->open_ro->get('mxbackup')->prop('status') || 'disabled' ) eq 'enabled' ) {
178     - my %MXValues = split( /,/, ( esmith::ConfigDB->open_ro->get('mxbackup')->prop('name') || '' ) ) ;
179     - foreach my $data ( keys %MXValues ) {
180     - $byrcptdomain{ $data }{ 'type' } = "mxbackup-$MXValues{ $data }" ;
181     - if ( $MXValues{ $data } == 1 ) { # subdomains allowed, must take care of this
182     - push @extdomain, $data ;
183     - }
184     - }
185     - }
186     -}
187     -
188     -my ( $start, $end ) = parse_arg( $opt{'start'}, $opt{'end'} );
189     -
190     -#
191     -# First check current configuration for logging, DNS enable and Max threshold for spamassassin
192     -#
193     -
194     -#my $LogLevel = esmith::ConfigDB->open_ro->get('qpsmtpd')->prop('LogLevel');
195     -#my $LowLogLevel = ( $LogLevel < 8 );
196     -
197     -my $RHSenabled =
198     - ( esmith::ConfigDB->open_ro->get('qpsmtpd')->prop('RHSBL') eq 'enabled' );
199     -my $DNSenabled =
200     - ( esmith::ConfigDB->open_ro->get('qpsmtpd')->prop('DNSBL') eq 'enabled' );
201     -my $SARejectLevel =
202     - esmith::ConfigDB->open_ro->get('spamassassin')->prop('RejectLevel');
203     -my $SATagLevel =
204     - esmith::ConfigDB->open_ro->get('spamassassin')->prop('TagLevel');
205     -my $DomainName =
206     - esmith::ConfigDB->open_ro->get('DomainName')->value;
207     -
208     -# check that logterse is in use
209     -#my pluginfile = '/var/service/qpsmtpd/config/peers/0';
210     -
211     -
212     -
213     -if ( !$RHSenabled || !$DNSenabled ) {
214     - $rblnotset = '*';
215     -}
216     -
217     -if ( $SARejectLevel == 0 ) {
218     -
219     - $warnnoreject = "(*Warning* 0 = no reject)";
220     -
221     -}
222     -
223     -#
224     -#---------------------------------------
225     -# Scan the qpsmtpd log file
226     -#---------------------------------------
227     -
228     -
229     -# Init the hashes
230     -my $nhour = floor( $start / 3600 );
231     -while ( $nhour < $end / 3600 ) {
232     - $MiscDenybyhour{$nhour}=0;
233     - $RBLbyhour{$nhour}=0;
234     - $above15byhour{$nhour}=0;
235     - $patternfilterbyhour{$nhour}=0;
236     - $nhour++;
237     -}
238     -
239     -my $starttai = Time::TAI64::unixtai64n($start);
240     -my $endtai = Time::TAI64::unixtai64n($end);
241     -
242     -LINE: while (<>) {
243     - my($tai,$log) = split(' ',$_,2);
244     -
245     -
246     - #If date specified, only process lines matching date
247     - next LINE if ( $tai lt $starttai );
248     - last if ( $tai gt $endtai );
249     -
250     - #only select Logterse output
251     - next LINE unless m/terse plugin/;
252     -
253     -
254     - my $abstime = Time::TAI64::tai2unix($tai);
255     - my $abshour = floor( $abstime / 3600 ); # Hours since the epoch
256     -
257     -
258     - my ($timestamp_part, $log_part) = split '`';
259     - my (@log_items) = split $FS, $log_part;
260     -
261     - my (@timestamp_items) = split(' ',$timestamp_part);
262     -
263     - # we store the more recent recipient domain, for domain statistics
264     - # in fact, we only store the first recipient. Could be sort of headhache
265     - # to obtain precise stats with many recipients on more than one domain !
266     - my $proc = $timestamp_items[1] ; #numeric Id for the email
267     -
268     - $totalexamined++;
269     -
270     - # first spot the fetchmail and local deliveries.
271     -
272     - # print '<'.$log_items[1].'><'.$log_items[5].'><'.$log_items[8].">\n";
273     -
274     - if ($log_items[1] =~ m/.*$DomainName.*/) {$localsendtotal++;$localLANbyhour{$abshour}++;$localflag=1}
275     - else {$localflag=0}
276     -
277     - if ($log_items[0] =~ m/.*$FetchmailIP.*/) {$localAccepttotal++;$localacceptbyhour{$abshour}++}
278     -
279     -
280     - # and adjust for recipient field if not set-up by denying plugin - extract from deny msg
281     -
282     - if (length($log_items[4])==0) {
283     - if ($log_items[5] eq 'check_goodrcptto') {
284     - if ($log_items[7] gt "invalid recipient") {
285     - $log_items[4] = substr($log_items[7],18) #Leave only email address
286     - }
287     - }
288     - }
289     -
290     -
291     -
292     - if ( ( $currentrcptdomain{ $proc } || '' ) eq '' ) {
293     - $currentrcptdomain{ $proc } = lc($log_items[4]) ;
294     - $currentrcptdomain{ $proc } =~ s/.*@//;
295     -
296     -
297     -
298     -# print $proc,$log_items[4]."\n";
299     -
300     - $currentrcptdomain{ $proc } =~ s/[^\w\-\.]//g ;
301     -
302     -# print $currentrcptdomain{ $proc }."\n";
303     -
304     - my $NotableDomain = 0 ;
305     - if ( defined ( $byrcptdomain{ $currentrcptdomain{ $proc } }{ 'type' } ) ) {
306     - $NotableDomain = 1 ;
307     - } else {
308     - foreach ( @extdomain ) {
309     - if ( $currentrcptdomain{ $proc } =~ m/$_$/ ) {
310     - $NotableDomain = 1 ;
311     - last ;
312     - }
313     - }
314     - }
315     - if ( !$NotableDomain ) {
316     - # check for outgoing email
317     - if ($localflag==1) {$currentrcptdomain{ $proc } = 'Outgoing'}
318     - else {$currentrcptdomain{ $proc } = 'Others'}
319     - } else {
320     - if ($localflag==1) {$currentrcptdomain{ $proc } = 'Internal'}
321     - }
322     - $byrcptdomain{ $currentrcptdomain{ $proc } }{ 'total' }++ ;
323     - } else {
324     - # there more than a recipient for a mail, how many daily ?
325     - $morethanonercpt++;
326     - }
327     -
328     - # then categorise the result
329     - if (exists $log_items[5]) {
330     - #Check for badly formed lines (from earlier testing)
331     -
332     - if ($log_items[5] eq 'check_earlytalker') {$MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
333     -
334     - if ($log_items[5] eq 'check_relay') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
335     -
336     - if ($log_items[5] eq 'check_norelay') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
337     -
338     - if ($log_items[5] eq 'require_resolvable_fromhost') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
339     -
340     - if ($log_items[5] eq 'check_basicheaders') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
341     -
342     - if ($log_items[5] eq 'rhsbl') { $RBLcount++;$RBLbyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
343     -
344     - if ($log_items[5] eq 'dnsbl') { $RBLcount++;$RBLbyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
345     -
346     - if ($log_items[5] eq 'check_badmailfrom') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
347     -
348     - if ($log_items[5] eq 'check_badrcptto_patterns') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
349     -
350     - if ($log_items[5] eq 'check_badrcptto') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
351     -
352     - if ($log_items[5] eq 'check_spamhelo') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
353     -
354     - if ($log_items[5] eq 'check_goodrcptto extn') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
355     -
356     - if ($log_items[5] eq 'rcpt_ok') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
357     -
358     - if ($log_items[5] eq 'pattern_filter') { $PatternFilterCount++;$patternfilterbyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
359     -
360     - if ($log_items[5] eq 'virus::pattern_filter') { $PatternFilterCount++;$patternfilterbyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
361     -
362     - if ($log_items[5] eq 'check_goodrcptto') {$MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
363     -
364     - if ($log_items[5] eq 'check_smtp_forward') {$MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
365     -
366     - if ($log_items[5] eq 'count_unrecognized_commands') {$MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
367     -
368     - if ($log_items[5] eq 'tnef2mime') { next LINE} #Not expecting this one.
369     -
370     - if ($log_items[5] eq 'spamassassin') { $above15++;$above15byhour{$abshour}++;
371     - # and extract the spam score
372     - if ($log_items[8] =~ "Yes, hits=(.*) required=([0-9\.]+)") {$rejectspamavg += $1}
373     - mark_domain_rejected($proc);
374     - next LINE
375     - }
376     -
377     - if ($log_items[5] eq 'virus::clamav') { $infectedcount++;$infectedbyhour{$abshour}++;
378     - #extract the virus name
379     - if ($log_items[7] =~ "Virus Found: (.*)" ) {$found_viruses{$1}++;}
380     - mark_domain_rejected($proc);
381     - next LINE
382     - }
383     -
384     - if ($log_items[5] eq 'queued') { $Accepttotal++;
385     - #extract the spam score
386     - if ($log_items[8] =~ ".*hits=(.*) required=([0-9\.]+)") {
387     - $score = $1;
388     - if ($score < $SATagLevel) { $hamcount++;$hamavg += $score}
389     - else {$spamcount++;$spamavg += $score}
390     - }
391     - if ( ( $currentrcptdomain{ $proc } || '' ) ne '' ) {
392     - $byrcptdomain{ $currentrcptdomain{ $proc } }{ 'accept' }++ ;
393     - $currentrcptdomain{ $proc } = '' ;
394     - }
395     - next LINE
396     - }
397     -
398     - print $log_items[5]."\n"; #Not detected
399     -
400     - }
401     -
402     -
403     -
404     -}
405     -
406     -my $QueryNoLogTerse = ($totalexamined==0); #might indicate logterse not installed in qpsmtpd plugins
407     -
408     -#Calculate some numbers
409     -
410     -$spamavg = $spamavg / $spamcount if $spamcount;
411     -$rejectspamavg = $rejectspamavg / $above15 if $above15;
412     -$hamavg = $hamavg / $hamcount if $hamcount;
413     -
414     -# RBL etc percent of total SMTP sessions
415     -
416     -my $rblpercent = ( ( $RBLcount / $totalexamined ) * 100 ) if $totalexamined;
417     -my $PatternFilterpercent = ( ( $PatternFilterCount / $totalexamined ) * 100 ) if $totalexamined;
418     -my $Miscpercent = ( ( $MiscDenyCount / $totalexamined ) * 100 ) if $totalexamined;
419     -
420     -#Spam and virus percent of total email downloaded
421     -#Expressed as a % of total examined
422     -my $spampercent = ( ( $spamcount / $totalexamined ) * 100 ) if $totalexamined;
423     -my $hampercent = ( ( $hamcount / $totalexamined ) * 100 ) if $totalexamined;
424     -my $hrsinperiod = ( ( $end - $start ) / 3600 );
425     -my $emailperhour = ( $totalexamined / $hrsinperiod ) if $totalexamined;
426     -my $above15percent = ( $above15 / $totalexamined * 100 ) if $totalexamined;
427     -my $infectedpercent = ( ( $infectedcount / ($totalexamined) ) * 100 ) if $totalexamined;
428     -my $AcceptPercent = ( ( $Accepttotal / ($totalexamined) ) * 100 ) if $totalexamined;
429     -
430     -my $oldfh;
431     -#Open Sendmail if we are mailing it
432     -if ( $opt{'mail'} && !$disabled ) {
433     - open( SENDMAIL, "|$opt{'sendmail'} -oi -t -odq" )
434     - or die "Can't open sendmail: $!\n";
435     - print SENDMAIL "From: $opt{'from'}\n";
436     - print SENDMAIL "To: $opt{'mail'}\n";
437     - print SENDMAIL "Subject: Spam Filter Statistics from $hostname - ",
438     - strftime( "%F", localtime($start) ), "\n\n";
439     - $oldfh = select SENDMAIL;
440     -}
441     -
442     -my $telapsed = time - $tstart;
443     -
444     -if ( !$disabled ) {
445     -
446     - #Output results
447     - print "SMEServer daily Anti-Virus and Spamfilter statistics", "\n";
448     - print "----------------------------------------------------", "\n\n";
449     -
450     - print "$0 Version : $opt{'version'}", "\n\n";
451     - print "Period Beginning : ", strftime( "%c", localtime($start) ), "\n";
452     - print "Period Ending : ", strftime( "%c", localtime($end) ), "\n";
453     - print "\n";
454     -
455     - print "Clam Version : ", `freshclam -V`;
456     - print "SpamAssassin Version : ", `spamassassin -V`;
457     - printf "Tag level: %3d; Reject level: %3d $warnnoreject\n", $SATagLevel,
458     - $SARejectLevel;
459     -
460     - print "\n";
461     - printf "Reporting Period : %.2f hrs\n", $hrsinperiod;
462     - print "----------------------------\n";
463     - print "\n";
464     -
465     - printf "All SMTP connections accepted : %8d \n", $totalexamined;
466     -
467     - if ($localAccepttotal>0) {
468     - printf "Connections from Fetchmail : %8d \n",
469     - $localAccepttotal;
470     - }
471     - printf "SMTP from local workstations : %8d \n\n", $localsendtotal;
472     -
473     -
474     - printf "RBL rejected : %8d (%6.2f%%)\n", $RBLcount,
475     - $rblpercent || 0;
476     - printf "Pattern filter rejected : %8d (%6.2f%%)\n",
477     - $PatternFilterCount, $PatternFilterpercent || 0;
478     - printf "Rejected due to non conformance : %8d (%6.2f%%)\n", $MiscDenyCount,
479     - $Miscpercent || 0;
480     -
481     - printf "Infected by Virus : %8d (%6.2f%%)\n", $infectedcount,
482     - $infectedpercent || 0;
483     -
484     - printf "Spam rejected (over reject level): %8d (%6.2f%%)\n", $above15,
485     - $above15percent || 0;
486     - printf "Spam detected (over tag level) : %8d (%6.2f%%)\n", $spamcount,
487     - $spampercent || 0;
488     - printf "Ham detected (under tag level) : %8d (%6.2f%%)\n", $hamcount,
489     - $hampercent || 0;
490     - print " --------------------\n";
491     - printf "Total emails accepted : %8d (%6.2f%%)\n", $Accepttotal,
492     - $AcceptPercent || 0;
493     -
494     - printf "Emails per hour : (%8.1f/hr)\n", $emailperhour || 0;
495     - print "\n";
496     - printf "Average spam score (accepted): %11.2f\n", $spamavg || 0;
497     - printf "Average spam score (rejected): %11.2f\n", $rejectspamavg || 0;
498     - printf "Average ham score : %11.2f\n", $hamavg || 0;
499     - print "\n";
500     - print "Statistics by Hour\n";
501     - if ($localAccepttotal>0) {
502     - print "---------------------------------------------------------------------------------------- \n";
503     - print
504     - "Hour Fetchml Local Virus Spam Ham RBL/DNS$rblnotset Execut. Non.Conf.\n";
505     - print "-------------- -------- -------- -------- -------- -------- -------- -------- ---------\n";
506     -
507     - my $hour = floor( $start / 3600 );
508     - while ( $hour < $end / 3600 ) {
509     - printf(
510     - "%s %8d %8d %8d %8d %8d %8d %8d %8d\n",
511     - strftime( "%F, %H", localtime( $hour * 3600 ) ),
512     - $localacceptbyhour{$hour} || 0,
513     - $localLANbyhour{$hour} || 0,
514     - $infectedbyhour{$hour} || 0,
515     - $spambyhour{$hour} || 0,
516     - $hambyhour{$hour} || 0,
517     - $RBLbyhour{$hour} || 0,
518     - $patternfilterbyhour{$hour} || 0,
519     - $MiscDenybyhour{$hour} || 0
520     - );
521     - $hour++;
522     - }
523     - print "---------------------------------------------------------------------------------------- \n";
524     -
525     - } else {
526     - print "------------------------------------------------------------------------------- \n";
527     - print
528     - "Hour Local Virus Spam Ham RBL/DNS$rblnotset Execut. Non.Conf.\n";
529     - print "--------------- -------- -------- -------- -------- -------- -------- --------- \n";
530     -
531     - my $hour = floor( $start / 3600 );
532     - while ( $hour < $end / 3600 ) {
533     - printf(
534     - "%s %8d %8d %8d %8d %8d %8d %8d\n",
535     - strftime( "%F, %H", localtime( $hour * 3600 ) ),
536     - $localLANbyhour{$hour} || 0,
537     - $infectedbyhour{$hour} || 0,
538     - $spambyhour{$hour} || 0,
539     - $hambyhour{$hour} || 0,
540     - $RBLbyhour{$hour} || 0,
541     - $patternfilterbyhour{$hour} || 0,
542     - $MiscDenybyhour{$hour} || 0
543     - );
544     - $hour++;
545     - }
546     -
547     - print "------------------------------------------------------------------------------ \n";
548     -
549     - }
550     - if ($localAccepttotal>0) {
551     - print "*Fetchml* means connections from Fetchmail delivering email\n";
552     - }
553     - print "*Local* means connections from workstations on local LAN.\n";
554     - print "*Non\.Conf\.* means sending mailserver did not conform to correct protocol.\n";
555     - print " or email was to non existant address.\n";
556     - print "\n";
557     -
558     - if ($QueryNoLogTerse) {
559     - print "* - as no records where found, it looks as though you may not have the *logterse* \nplugin running as part of qpsmtpd \n";
560     - print " to enable it follow the instructions at .............................\n";
561     - }
562     -
563     -
564     - if ( !$RHSenabled || !$DNSenabled ) {
565     -
566     - # comment about RBL not set
567     - print
568     -"* - This means that one or more of the possible spam black listing services\n that are available have not been enabled.\n";
569     - print " You have not enabled:\n";
570     -
571     - if ( !$RHSenabled ) {
572     - print " RHSBL\n";
573     - }
574     -
575     - if ( !$DNSenabled ) {
576     - print " DNSBL\n";
577     - }
578     -
579     -
580     - print " To enable these you can use the following commands:\n";
581     - if ( !$RHSenabled ) {
582     - print " config setprop qpsmtpd RHSBL enabled\n";
583     - }
584     -
585     - if ( !$DNSenabled ) {
586     - print " config setprop qpsmtpd DNSBL enabled\n";
587     - }
588     -
589     - # there so much templates to expand... (PS)
590     - print " Followed by:\n signal-event email-update and\n svc -t /service/qpsmtpd\n\n";
591     - }
592     -
593     -
594     - # time to do a 'by recipient domain' report
595     - print "\nIncoming mails by recipient domains usage\n";
596     - print "-----------------------------------------\n";
597     - print
598     - "Domains Type Total Denied XferErr Accept \%accept\n";
599     - print
600     - "---------------------------- ---------- ------ ------ ------- ------ -------\n";
601     - my %total = (
602     - total => 0,
603     - deny => 0,
604     - xfer => 0,
605     - accept => 0,
606     - );
607     - foreach my $domain (
608     - sort {
609     - join( "\.", reverse( split /\./, $a ) ) cmp
610     - join( "\.", reverse( split /\./, $b ) )
611     - } keys %byrcptdomain
612     - )
613     - {
614     - next if ( ( $byrcptdomain{$domain}{'total'} || 0 ) == 0 );
615     - my $tp = $byrcptdomain{$domain}{'type'} || 'other';
616     - my $to = $byrcptdomain{$domain}{'total'} || 0;
617     - my $de = $byrcptdomain{$domain}{'deny'} || 0;
618     - my $xr = $byrcptdomain{$domain}{'xfer'} || 0;
619     - my $ac = $byrcptdomain{$domain}{'accept'} || 0;
620     - printf "%-28s %-10s %6d %6d %7d %6d %6.2f%%\n", $domain, $tp, $to,
621     - $de, $xr, $ac, $ac * 100 / $to;
622     - $total{'total'} += $to;
623     - $total{'deny'} += $de;
624     - $total{'xfer'} += $xr;
625     - $total{'accept'} += $ac;
626     - }
627     - print
628     - "---------------------------- ---------- ------ ------- ------ ------ -------\n";
629     -
630     - # $total{ 'total' } can be equal to 0, bad for divisions...
631     - my $perc1 = 0;
632     - my $perc2 = 0;
633     - if ( $total{'total'} != 0 ) {
634     - $perc1 = $total{'accept'} * 100 / $total{'total'};
635     - $perc2 = ( ( $total{'total'} + $morethanonercpt ) / $total{'total'} );
636     - }
637     - printf
638     - "Total %6d %6d %7d %6d %6.2f%%\n\n",
639     - $total{'total'}, $total{'deny'}, $total{'xfer'}, $total{'accept'},
640     - $perc1;
641     - printf
642     - "%d mails were processed for %d Recipients\nThe average recipients by mail is %4.2f\n\n",
643     - $total{'total'}, ( $total{'total'} + $morethanonercpt ), $perc2;
644     -
645     - if ( $infectedcount > 0 ) {
646     - show_virus_variants();
647     - }
648     -
649     -} # not disabled
650     -
651     -List_Junkmail();
652     -
653     -if ( !$disabled ) {
654     -
655     - print "\nDone. Report generated in $telapsed sec.\n\n";
656     -
657     - #Close Senmdmail if it was opened
658     - if ( $opt{'mail'} ) {
659     - select $oldfh;
660     - close(SENDMAIL);
661     - }
662     -
663     -}
664     -
665     -#All done
666     -exit 0;
667     -
668     -#############################################################################
669     -# Subroutines ###############################################################
670     -#############################################################################
671     -
672     -
673     -########################################
674     -# Process parms #
675     -########################################
676     -sub parse_arg {
677     - my $startdate = shift;
678     - my $enddate = shift;
679     -
680     - my $secsinday = 86400;
681     - my $time = 0;
682     -
683     - my $start = UnixDate( $startdate, "%s" );
684     - my $end = UnixDate( $enddate, "%s" );
685     -
686     - if ( !$start && !$end ) {
687     - $end = time;
688     - $start = $end - $secsinday;
689     - return ( $start, $end );
690     - }
691     -
692     - if ( !$start ) {
693     - $start = $end - $secsinday;
694     - return ( $start, $end );
695     - }
696     -
697     - if ( !$end ) {
698     - $end = $start + $secsinday;
699     - return ( $start, $end );
700     - }
701     -
702     - if ( $start > $end ) {
703     - return ( $end, $start );
704     - }
705     -
706     - return ( $start, $end );
707     -
708     -}
709     -
710     -sub dbg {
711     - my $msg = shift;
712     -
713     - if ( $opt{debug} ) {
714     - print STDERR $msg;
715     - }
716     -}
717     -
718     -sub List_Junkmail {
719     -
720     - #
721     - # Show how many junkmails in each user's junkmail folder.
722     - #
723     - use esmith::AccountsDB;
724     - my $adb = esmith::AccountsDB->open_ro;
725     - my $entry;
726     - foreach my $user ($adb->users) {
727     - my $found = 0;
728     - my $junkmail_dir = "/home/e-smith/files/users/" .
729     - $user->key . "/Maildir/.junkmail";
730     -# print $user->key;
731     - foreach my $dir (qw(new cur)) {
732     - # Now get the content list for the directory.
733     - if (opendir( QDIR, "$junkmail_dir/$dir" )) {
734     - while ($entry=readdir(QDIR) ) {
735     - next if $entry =~ /^\./;
736     - $found++;
737     - }
738     -
739     - closedir(QDIR);
740     - }
741     - }
742     - if ( !$disabled ) {
743     - printf "User \"%s\" ", $user->key;
744     - printf "- %d email(s) left in junkmail folder\n", $found;
745     - }
746     - }
747     -}
748     -
749     -sub show_virus_variants
750     -
751     -#
752     -# Show a league table of the different virus types found today
753     -#
754     -
755     -{
756     -
757     - print("Virus Statistics by name:\n");
758     - print("---------------------------------------------\n");
759     - foreach my $virus (sort { $found_viruses{$b} <=> $found_viruses{$a} }
760     - keys %found_viruses)
761     - {
762     - print "Rejected $found_viruses{$virus}\t$virus\n";
763     - }
764     - print("---------------------------------------------\n");
765     -}
766     -
767     -sub mark_domain_rejected
768     -
769     -#
770     -# Tag domain as having a rejected email
771     -#
772     -{
773     -my ($proc) = @_;
774     -if ( ( $currentrcptdomain{ $proc } || '' ) ne '' ) {
775     - $byrcptdomain{ $currentrcptdomain{ $proc } }{ 'deny' }++ ;
776     - $currentrcptdomain{ $proc } = '' ;
777     - }
778     +#!/usr/bin/perl -w
779     +
780     +#############################################################################
781     +#
782     +# This script provides daily SpamFilter statistics and deletes all users
783     +# junkmails. Configuration of the script is done by the Spam Filter
784     +# Server-Manager module
785     +#
786     +# April 2006 - no longer controlled by server manager, and does not delete files
787     +#
788     +# This script has been developed
789     +# by Jesper Knudsen at http://sme.swerts-knudsen.dk
790     +#
791     +# Revision History:
792     +#
793     +# August 13, 2003: Initial version
794     +# August 25, 2004: fixed problem when hostname had no-ASCII chars
795     +# March 23, 2006 Revised for sme7 RM
796     +# March 27, 2006 ditto BJR (http://www.abandonmicrosoft.co.uk)
797     +# - Merged Clamav and SA stats
798     +# - Moved all analysis to qsmtpd log
799     +# - Removed parameterised interval (for simplicity - not sure of format anyway)
800     +# - add in archived log files for people who have high turnover
801     +# - Alter labels to be more accurate
802     +# - Detect deleted spam (over threshold) without using spam score
803     +# - Detect RBL rejections
804     +# - Detect pattern (executible) rejections
805     +# - Look for the DENY labels - add in Miscellaneous category
806     +# April 6, 2006 - check qpsmtp log level and also DNS enable properties
807     +# - Average spam scores for under and over threshold seperatly
808     +# - Log tag and Reject levels
809     +# - TBD - check that RBL DENY are being detected (I have no date to check this)
810     +# April 7, 2007 - re-written by Charlie Brady totally in Perl
811     +# April 16, 2006 - move warnings to report
812     +# - Spot fetchmail deliveries
813     +# - Spot Internal connections from client PCs
814     +# - TBD check that RBL DENY are being detected (I have no data to check this)
815     +# April 30, 2006 - Pascal Schirrmann Start Time and End Time to noon - should be a param
816     +# so the script can be run at any time in the day.
817     +# - adds 'by recipients domains' stats Useful for MX-Backup or multi domains hosts
818     +# - Add a 'recipients per mail' stat. Useful : until now the sums are correct :-)
819     +# - Correct some messages about rbl who can led to wrong entry in the config database
820     +# ( and without expected results, of course !)
821     +# - improve a regexp in the SPAM detection
822     +# May 1, 2006 - BJR - Fix situation where mxbackup prop is not defined
823     +# - fix a spelling and minor format of domain report
824     +# May 9, 2006 - bjr - Make RBL percentage a percentage of total connections (else it >100%)
825     +# May 9, 2006 - ps - some 'sanity check' in the 'per domains part of the stats (to avoid / 0)
826     +# May 12, 2006 - ps - some cleanup in the 'per domains' stats
827     +# - Add a version number, logged in the mail
828     +# June 20, 2006 - bjr - Minor change to RBL instructions, and adjust domain table format
829     +# Feb 19, 2007 - bjr - Adjust table lines oin a couple of places
830     +# - bjr - and add documentation details about percentages etc
831     +# - bjr - Alter misc to "non conforming" anmd accumulated these hourly
832     +# - bjr - Express change over tag count to exclude spam rejected over threshold
833     +# - bjr - Change "processsed" to "fully downloaded"
834     +# - bjr - Change percentages so that they are all a percetnage of the total emails received
835     +# 0.6.1 - bjr - Change to use output from the logterse qpsmtpd plugin
836     +# 0.6.2 - bjr - Fix fetchmail tests
837     +# 0.6.3 - bjr - adjust for log-items change in order
838     +# 0.6.4&5 - bjr - Adjust table formatting
839     +# 0.6.6 - bjr - Take outgoing emails out of "others", add "Outgoing" and "Internal"
840     +# 0.6.7 - bjr - Fix missing plugins/wrong names. pull invalid recipient out of deny msg for goodrcptto
841     +# 0.6.8 - bjr - catch a few more plugin name failures
842     +# 0.6.9 - bjr - Catch webmail and mailman
843     +# 0.6.10 - bjr - Refine Webmail identification
844     +# 0.6.11 - bjr - Fix Webmail identification
845     +#
846     +#
847     +#
848     +# TODO
849     +# ----
850     +#
851     +# 1. Re-Write the table so that it does not show certain columns if they are zero, this will allow EXTRA
852     +# columns to be shown (e.g Mailman, fetchmail, webmail) only if there is some activity
853     +#
854     +#############################################################################
855     +
856     +
857     +# internal modules (part of core perl distribution)
858     +use strict;
859     +use warnings;
860     +use Getopt::Long;
861     +use Pod::Usage;
862     +use POSIX qw/strftime floor/;
863     +use Time::Local;
864     +use Date::Manip;
865     +use Time::TAI64;
866     +use esmith::ConfigDB;
867     +use esmith::DomainsDB;
868     +use Sys::Hostname;
869     +use Switch;
870     +
871     +my $hostname = hostname();
872     +
873     +#Configuration section
874     +my %opt = ();
875     +
876     +$opt{'version'} = '0.6.11'; # please update at each change.
877     +$opt{'debug'} = 0; # guess what ?
878     +$opt{'sendmail'} = '/usr/sbin/sendmail'; # Path to sendmail stub
879     +$opt{'from'} = 'spamfilter-stats'; # Who is the mail from
880     +$opt{'end'} = `date --iso-8601`; # midnight today
881     +my $yesterday = $opt{ 'end' };
882     +$yesterday =~ s/\-//g ;
883     +$yesterday--;
884     +$opt{'start'} = `date --iso-8601 -d $yesterday`; # midnight yesterday
885     +$opt{'mail'} = "admin";
886     +$opt{'timezone'} = `date +%z`;
887     +Date_Init("TZ=$opt{'timezone'}");
888     +my $FetchmailIP = '127.0.0.200'; #Apparent Ip address of fetchmail deliveries
889     +my $WebmailIP = '127.0.0.1'; #Apparent Ip of Webmail sender
890     +my $localhost = 'localhost'; #Apparent sender for webmail
891     +my $FETCHMAIL = 'FETCHMAIL'; #Sender from fetchmail when Ip address not 127.0.0.200 - when qpsmtpd denies the email
892     +my $MAILMAN = "bounces"; #sender when mailman sending when orig is localhost
893     +
894     +my $disabled = 0;
895     +
896     +my $tstart = time;
897     +
898     +#Local variables
899     +my $YEAR = ( localtime(time) )[5]; # this is years since 1900
900     +
901     +my $total = 0;
902     +my $spamcount = 0;
903     +my $spamavg = 0;
904     +my $hamcount = 0;
905     +my $hamavg = 0;
906     +my $rejectspamavg = 0;
907     +
908     +my $Accepttotal = 0;
909     +my $localAccepttotal = 0; #Fetchmail connections
910     +my $localsendtotal = 0; #Connections from local PCs
911     +my $totalexamined = 0; #total download + RBL etc
912     +my $WebMailsendtotal = 0; #total from Webmail
913     +my $mailmansendcount = 0; #total from mailman
914     +my %sendtotalbyhour = ();
915     +my %localLANbyhour = ();
916     +my %localacceptbyhour = ();
917     +my %WebMailbyhour = ();
918     +
919     +my $above15 = 0;
920     +my %above15byhour = ();
921     +
922     +my $RBLcount = 0;
923     +my %RBLbyhour = ();
924     +
925     +my $MiscDenyCount = 0;
926     +my %MiscDenybyhour = ();
927     +
928     +my $PatternFilterCount = 0;
929     +my %patternfilterbyhour = ();
930     +
931     +my $noninfectedcount = 0;
932     +my $okemailcount = 0;
933     +
934     +my $infectedcount = 0;
935     +my %infectedbyhour = ();
936     +my %found_viruses = ();
937     +
938     +my %spambyhour = ();
939     +my %hambyhour = ();
940     +
941     +my $warnnoreject = " ";
942     +my $rblnotset = ' ';
943     +
944     +my $FS = "\t"; # field separator used by logterse plugin
945     +my %log_items = ();
946     +my $score;
947     +my %timestamp_items = ();
948     +my $localflag = 0; #indicate if current email is local or not
949     +my $WebMailflag = 0; #indicate if current mail is send from webmail
950     +
951     +# some storage for by recipient domains stats (PS)
952     +# my bad : I have to deal with multiple simoultaneous connections
953     +# will play with the process number.
954     +# my $currentrcptdomain = '' ;
955     +my %currentrcptdomain ; # temporay store the recipient domain until end of mail processing
956     +my %byrcptdomain ; # Store 'by domains stats'
957     +my @extdomain ; # only useful in some MX-Backup case, when any subdomains are allowed
958     +my $morethanonercpt = 0 ; # count every 'second' recipients for a mail.
959     +
960     +# store the domain of interest. Every other records are stored in a 'Other' zone
961     +my $ddb = esmith::DomainsDB->open_ro or die "Couldn't open DomainsDB : $!\n";
962     +foreach my $domain( $ddb->get_all_by_prop( type => "domain" ) ) {
963     + $byrcptdomain{ $domain->key }{ 'type' }='local';
964     +}
965     +$byrcptdomain{ esmith::ConfigDB->open_ro->get('SystemName')->value . "."
966     + . esmith::ConfigDB->open_ro->get('DomainName')->value }{ 'type' } = 'local';
967     +
968     +# is this system a MX-Backup ?
969     +if (esmith::ConfigDB->open_ro->get('mxbackup')){
970     + if ( ( esmith::ConfigDB->open_ro->get('mxbackup')->prop('status') || 'disabled' ) eq 'enabled' ) {
971     + my %MXValues = split( /,/, ( esmith::ConfigDB->open_ro->get('mxbackup')->prop('name') || '' ) ) ;
972     + foreach my $data ( keys %MXValues ) {
973     + $byrcptdomain{ $data }{ 'type' } = "mxbackup-$MXValues{ $data }" ;
974     + if ( $MXValues{ $data } == 1 ) { # subdomains allowed, must take care of this
975     + push @extdomain, $data ;
976     + }
977     + }
978     + }
979     +}
980     +
981     +my ( $start, $end ) = parse_arg( $opt{'start'}, $opt{'end'} );
982     +
983     +#
984     +# First check current configuration for logging, DNS enable and Max threshold for spamassassin
985     +#
986     +
987     +#my $LogLevel = esmith::ConfigDB->open_ro->get('qpsmtpd')->prop('LogLevel');
988     +#my $LowLogLevel = ( $LogLevel < 8 );
989     +
990     +my $RHSenabled =
991     + ( esmith::ConfigDB->open_ro->get('qpsmtpd')->prop('RHSBL') eq 'enabled' );
992     +my $DNSenabled =
993     + ( esmith::ConfigDB->open_ro->get('qpsmtpd')->prop('DNSBL') eq 'enabled' );
994     +my $SARejectLevel =
995     + esmith::ConfigDB->open_ro->get('spamassassin')->prop('RejectLevel');
996     +my $SATagLevel =
997     + esmith::ConfigDB->open_ro->get('spamassassin')->prop('TagLevel');
998     +my $DomainName =
999     + esmith::ConfigDB->open_ro->get('DomainName')->value;
1000     +
1001     +# check that logterse is in use
1002     +#my pluginfile = '/var/service/qpsmtpd/config/peers/0';
1003     +
1004     +
1005     +
1006     +if ( !$RHSenabled || !$DNSenabled ) {
1007     + $rblnotset = '*';
1008     +}
1009     +
1010     +if ( $SARejectLevel == 0 ) {
1011     +
1012     + $warnnoreject = "(*Warning* 0 = no reject)";
1013     +
1014     +}
1015     +
1016     +#
1017     +#---------------------------------------
1018     +# Scan the qpsmtpd log file
1019     +#---------------------------------------
1020     +
1021     +
1022     +# Init the hashes
1023     +my $nhour = floor( $start / 3600 );
1024     +while ( $nhour < $end / 3600 ) {
1025     + $MiscDenybyhour{$nhour}=0;
1026     + $RBLbyhour{$nhour}=0;
1027     + $above15byhour{$nhour}=0;
1028     + $patternfilterbyhour{$nhour}=0;
1029     + $WebMailbyhour{$nhour}=0;
1030     + $nhour++;
1031     +}
1032     +
1033     +my $starttai = Time::TAI64::unixtai64n($start);
1034     +my $endtai = Time::TAI64::unixtai64n($end);
1035     +
1036     +LINE: while (<>) {
1037     + my($tai,$log) = split(' ',$_,2);
1038     +
1039     +
1040     + #If date specified, only process lines matching date
1041     + next LINE if ( $tai lt $starttai );
1042     + last if ( $tai gt $endtai );
1043     +
1044     + #only select Logterse output
1045     + next LINE unless m/terse plugin/;
1046     +
1047     +
1048     + my $abstime = Time::TAI64::tai2unix($tai);
1049     + my $abshour = floor( $abstime / 3600 ); # Hours since the epoch
1050     +
1051     +
1052     + my ($timestamp_part, $log_part) = split '`';
1053     + my (@log_items) = split $FS, $log_part;
1054     +
1055     + my (@timestamp_items) = split(' ',$timestamp_part);
1056     +
1057     + # we store the more recent recipient domain, for domain statistics
1058     + # in fact, we only store the first recipient. Could be sort of headhache
1059     + # to obtain precise stats with many recipients on more than one domain !
1060     + my $proc = $timestamp_items[1] ; #numeric Id for the email
1061     +
1062     + $totalexamined++;
1063     +
1064     + # first spot the fetchmail and local deliveries.
1065     +
1066     + # print '<'.$log_items[1].'><'.$log_items[5].'><'.$log_items[8].">\n";
1067     +
1068     + # Spot from local workstation
1069     + $localflag = 0;
1070     + $WebMailflag=0;
1071     + if ($log_items[1] =~ m/.*$DomainName.*/) {$localsendtotal++;$localLANbyhour{$abshour}++;$localflag=1}
1072     + # see if from localhost
1073     + elsif ($log_items[1] =~ m/.*$localhost.*/){
1074     + # but not if it comes from fetchmail
1075     +# print $log_items[3]."\n";
1076     + if ($log_items[3] =~ m/.*$FETCHMAIL.*/){}
1077     + else {
1078     + # might still be from mailman here
1079     +# print "got webmail\n";
1080     + if ($log_items[3] =~ m/.*$MAILMAN.*/){$mailmansendcount++;$localsendtotal++;$localLANbyhour{$abshour}++;$localflag=1}
1081     + else {
1082     + # eliminate incoming localhost spoofs
1083     + if ($log_items[8] =~ m/.*msg denied before queued.*/){}
1084     + else {$localflag = 1;$WebMailsendtotal++;$WebMailbyhour{$abshour}++;$WebMailflag=1}
1085     + }
1086     + }
1087     + }
1088     +
1089     + # try to spot fetchmail emails
1090     + if ($log_items[0] =~ m/.*$FetchmailIP.*/) {$localAccepttotal++;$localacceptbyhour{$abshour}++}
1091     + elsif ($log_items[3] =~ m/.*$FETCHMAIL.*/) {$localAccepttotal++;$localacceptbyhour{$abshour}++}
1092     +
1093     +
1094     + # and adjust for recipient field if not set-up by denying plugin - extract from deny msg
1095     +
1096     + if (length($log_items[4])==0) {
1097     + if ($log_items[5] eq 'check_goodrcptto') {
1098     + if ($log_items[7] gt "invalid recipient") {
1099     + $log_items[4] = substr($log_items[7],18) #Leave only email address
1100     + }
1101     + }
1102     + }
1103     +
1104     +
1105     +
1106     + if ( ( $currentrcptdomain{ $proc } || '' ) eq '' ) {
1107     + $currentrcptdomain{ $proc } = lc($log_items[4]) ;
1108     + $currentrcptdomain{ $proc } =~ s/.*@//;
1109     +
1110     +
1111     +
1112     +# print $proc,$log_items[4]."\n";
1113     +
1114     + $currentrcptdomain{ $proc } =~ s/[^\w\-\.]//g ;
1115     +
1116     +# print $currentrcptdomain{ $proc }."\n";
1117     +
1118     + my $NotableDomain = 0 ;
1119     + if ( defined ( $byrcptdomain{ $currentrcptdomain{ $proc } }{ 'type' } ) ) {
1120     + $NotableDomain = 1 ;
1121     + } else {
1122     + foreach ( @extdomain ) {
1123     + if ( $currentrcptdomain{ $proc } =~ m/$_$/ ) {
1124     + $NotableDomain = 1 ;
1125     + last ;
1126     + }
1127     + }
1128     + }
1129     + if ( !$NotableDomain ) {
1130     + # check for outgoing email
1131     + if ($localflag==1) {$currentrcptdomain{ $proc } = 'Outgoing'}
1132     + else {$currentrcptdomain{ $proc } = 'Others'}
1133     + } else {
1134     + if ($localflag==1) {$currentrcptdomain{ $proc } = 'Internal'}
1135     + }
1136     + $byrcptdomain{ $currentrcptdomain{ $proc } }{ 'total' }++ ;
1137     + } else {
1138     + # there more than a recipient for a mail, how many daily ?
1139     + $morethanonercpt++;
1140     + }
1141     +
1142     + # then categorise the result
1143     + if (exists $log_items[5]) {
1144     + #Check for badly formed lines (from earlier testing)
1145     +
1146     + if ($log_items[5] eq 'check_earlytalker') {$MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1147     +
1148     + if ($log_items[5] eq 'check_relay') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1149     +
1150     + if ($log_items[5] eq 'check_norelay') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1151     +
1152     + if ($log_items[5] eq 'require_resolvable_fromhost') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1153     +
1154     + if ($log_items[5] eq 'check_basicheaders') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1155     +
1156     + if ($log_items[5] eq 'rhsbl') { $RBLcount++;$RBLbyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1157     +
1158     + if ($log_items[5] eq 'dnsbl') { $RBLcount++;$RBLbyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1159     +
1160     + if ($log_items[5] eq 'check_badmailfrom') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1161     +
1162     + if ($log_items[5] eq 'check_badrcptto_patterns') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1163     +
1164     + if ($log_items[5] eq 'check_badrcptto') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1165     +
1166     + if ($log_items[5] eq 'check_spamhelo') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1167     +
1168     + if ($log_items[5] eq 'check_goodrcptto extn') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1169     +
1170     + if ($log_items[5] eq 'rcpt_ok') { $MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1171     +
1172     + if ($log_items[5] eq 'pattern_filter') { $PatternFilterCount++;$patternfilterbyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1173     +
1174     + if ($log_items[5] eq 'virus::pattern_filter') { $PatternFilterCount++;$patternfilterbyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1175     +
1176     + if ($log_items[5] eq 'check_goodrcptto') {$MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1177     +
1178     + if ($log_items[5] eq 'check_smtp_forward') {$MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1179     +
1180     + if ($log_items[5] eq 'count_unrecognized_commands') {$MiscDenyCount++;$MiscDenybyhour{$abshour}++;mark_domain_rejected($proc);next LINE}
1181     +
1182     + if ($log_items[5] eq 'tnef2mime') { next LINE} #Not expecting this one.
1183     +
1184     + if ($log_items[5] eq 'spamassassin') { $above15++;$above15byhour{$abshour}++;
1185     + # and extract the spam score
1186     + if ($log_items[8] =~ "Yes, hits=(.*) required=([0-9\.]+)") {$rejectspamavg += $1}
1187     + mark_domain_rejected($proc);
1188     + next LINE
1189     + }
1190     +
1191     + if ($log_items[5] eq 'virus::clamav') { $infectedcount++;$infectedbyhour{$abshour}++;
1192     + #extract the virus name
1193     + if ($log_items[7] =~ "Virus Found: (.*)" ) {$found_viruses{$1}++;}
1194     + mark_domain_rejected($proc);
1195     + next LINE
1196     + }
1197     +
1198     + if ($log_items[5] eq 'queued') { $Accepttotal++;
1199     + #extract the spam score
1200     + if ($log_items[8] =~ ".*hits=(.*) required=([0-9\.]+)") {
1201     + $score = $1;
1202     +# print $log_items[8]."<".$score.">\n";
1203     + if ($score < $SATagLevel) { $hamcount++;$hambyhour{$abshour}++;$hamavg += $score}
1204     + else {$spamcount++;$spambyhour{$abshour}++;$spamavg += $score}
1205     + }
1206     + if ( ( $currentrcptdomain{ $proc } || '' ) ne '' ) {
1207     + $byrcptdomain{ $currentrcptdomain{ $proc } }{ 'accept' }++ ;
1208     + $currentrcptdomain{ $proc } = '' ;
1209     + }
1210     + next LINE
1211     + }
1212     +
1213     + print $log_items[5]."\n"; #Not detected
1214     +
1215     + }
1216     +
1217     +
1218     +
1219     +}
1220     +
1221     +my $QueryNoLogTerse = ($totalexamined==0); #might indicate logterse not installed in qpsmtpd plugins
1222     +
1223     +#Calculate some numbers
1224     +
1225     +$spamavg = $spamavg / $spamcount if $spamcount;
1226     +$rejectspamavg = $rejectspamavg / $above15 if $above15;
1227     +$hamavg = $hamavg / $hamcount if $hamcount;
1228     +
1229     +# RBL etc percent of total SMTP sessions
1230     +
1231     +my $rblpercent = ( ( $RBLcount / $totalexamined ) * 100 ) if $totalexamined;
1232     +my $PatternFilterpercent = ( ( $PatternFilterCount / $totalexamined ) * 100 ) if $totalexamined;
1233     +my $Miscpercent = ( ( $MiscDenyCount / $totalexamined ) * 100 ) if $totalexamined;
1234     +
1235     +#Spam and virus percent of total email downloaded
1236     +#Expressed as a % of total examined
1237     +my $spampercent = ( ( $spamcount / $totalexamined ) * 100 ) if $totalexamined;
1238     +my $hampercent = ( ( $hamcount / $totalexamined ) * 100 ) if $totalexamined;
1239     +my $hrsinperiod = ( ( $end - $start ) / 3600 );
1240     +my $emailperhour = ( $totalexamined / $hrsinperiod ) if $totalexamined;
1241     +my $above15percent = ( $above15 / $totalexamined * 100 ) if $totalexamined;
1242     +my $infectedpercent = ( ( $infectedcount / ($totalexamined) ) * 100 ) if $totalexamined;
1243     +my $AcceptPercent = ( ( $Accepttotal / ($totalexamined) ) * 100 ) if $totalexamined;
1244     +
1245     +my $oldfh;
1246     +
1247     +#Open Sendmail if we are mailing it
1248     +if ( $opt{'mail'} && !$disabled ) {
1249     + open( SENDMAIL, "|$opt{'sendmail'} -oi -t -odq" )
1250     + or die "Can't open sendmail: $!\n";
1251     + print SENDMAIL "From: $opt{'from'}\n";
1252     + print SENDMAIL "To: $opt{'mail'}\n";
1253     + print SENDMAIL "Subject: Spam Filter Statistics from $hostname - ",
1254     + strftime( "%F", localtime($start) ), "\n\n";
1255     + $oldfh = select SENDMAIL;
1256     +}
1257     +
1258     +my $telapsed = time - $tstart;
1259     +
1260     +if ( !$disabled ) {
1261     +
1262     + #Output results
1263     + print "SMEServer daily Anti-Virus and Spamfilter statistics", "\n";
1264     + print "----------------------------------------------------", "\n\n";
1265     +
1266     + print "$0 Version : $opt{'version'}", "\n\n";
1267     + print "Period Beginning : ", strftime( "%c", localtime($start) ), "\n";
1268     + print "Period Ending : ", strftime( "%c", localtime($end) ), "\n";
1269     + print "\n";
1270     +
1271     + print "Clam Version : ", `freshclam -V`;
1272     + print "SpamAssassin Version : ", `spamassassin -V`;
1273     + printf "Tag level: %3d; Reject level: %3d $warnnoreject\n", $SATagLevel,
1274     + $SARejectLevel;
1275     +
1276     + print "\n";
1277     + printf "Reporting Period : %.2f hrs\n", $hrsinperiod;
1278     + print "----------------------------\n";
1279     + print "\n";
1280     +
1281     + printf "All SMTP connections accepted : %8d \n", $totalexamined;
1282     +
1283     + if ($localAccepttotal>0) {
1284     + printf "Connections from Fetchmail : %8d \n",
1285     + $localAccepttotal;
1286     + }
1287     +
1288     + if ($WebMailsendtotal>0) {
1289     + printf "Emails sent from WebMail : %8d \n",
1290     + $WebMailsendtotal;
1291     + }
1292     +
1293     + if ($mailmansendcount > 0) {
1294     + printf "Emails sent from Mailman : %8d \n",
1295     + $mailmansendcount;
1296     + }
1297     +
1298     + printf "SMTP from local workstations : %8d \n\n", $localsendtotal;
1299     +
1300     +
1301     + printf "RBL rejected : %8d (%6.2f%%)\n", $RBLcount,
1302     + $rblpercent || 0;
1303     + printf "Pattern filter rejected : %8d (%6.2f%%)\n",
1304     + $PatternFilterCount, $PatternFilterpercent || 0;
1305     + printf "Rejected due to non conformance : %8d (%6.2f%%)\n", $MiscDenyCount,
1306     + $Miscpercent || 0;
1307     +
1308     + printf "Infected by Virus : %8d (%6.2f%%)\n", $infectedcount,
1309     + $infectedpercent || 0;
1310     +
1311     + printf "Spam rejected (over reject level): %8d (%6.2f%%)\n", $above15,
1312     + $above15percent || 0;
1313     + printf "Spam detected (over tag level) : %8d (%6.2f%%)\n", $spamcount,
1314     + $spampercent || 0;
1315     + printf "Ham detected (under tag level) : %8d (%6.2f%%)\n", $hamcount,
1316     + $hampercent || 0;
1317     + print " --------------------\n";
1318     + printf "Total emails accepted : %8d (%6.2f%%)\n", $Accepttotal,
1319     + $AcceptPercent || 0;
1320     +
1321     + printf "Emails per hour : (%8.1f/hr)\n", $emailperhour || 0;
1322     + print "\n";
1323     + printf "Average spam score (accepted): %11.2f\n", $spamavg || 0;
1324     + printf "Average spam score (rejected): %11.2f\n", $rejectspamavg || 0;
1325     + printf "Average ham score : %11.2f\n", $hamavg || 0;
1326     + print "\n";
1327     + print "Statistics by Hour\n";
1328     + if ($localAccepttotal>0) {
1329     + print "------------------------------------------------------------------------------------------------- \n";
1330     + print
1331     + "Hour Fetchml Local WebMail Virus Spam Ham RBL/DNS$rblnotset Execut. Non.Conf.\n";
1332     + print "-------------- -------- -------- -------- -------- -------- -------- -------- -------- ---------\n";
1333     +
1334     + my $hour = floor( $start / 3600 );
1335     + while ( $hour < $end / 3600 ) {
1336     + printf(
1337     + "%s %8d %8d %8d %8d %8d %8d %8d %8d %8d\n",
1338     + strftime( "%F, %H", localtime( $hour * 3600 ) ),
1339     + $localacceptbyhour{$hour} || 0,
1340     + $localLANbyhour{$hour} || 0,
1341     + $WebMailbyhour{$hour} || 0,
1342     + $infectedbyhour{$hour} || 0,
1343     + $spambyhour{$hour} || 0,
1344     + $hambyhour{$hour} || 0,
1345     + $RBLbyhour{$hour} || 0,
1346     + $patternfilterbyhour{$hour} || 0,
1347     + $MiscDenybyhour{$hour} || 0
1348     + );
1349     + $hour++;
1350     + }
1351     + print "------------------------------------------------------------------------------------------------- \n";
1352     +
1353     + } else {
1354     + print "---------------------------------------------------------------------------------------- \n";
1355     + print
1356     + "Hour Local WebMail Virus Spam Ham RBL/DNS$rblnotset Execut. Non.Conf.\n";
1357     + print "--------------- -------- -------- -------- -------- -------- -------- -------- --------- \n";
1358     +
1359     + my $hour = floor( $start / 3600 );
1360     + while ( $hour < $end / 3600 ) {
1361     + printf(
1362     + "%s %8d %8d %8d %8d %8d %8d %8d %8d\n",
1363     + strftime( "%F, %H", localtime( $hour * 3600 ) ),
1364     + $localLANbyhour{$hour} || 0,
1365     + $WebMailbyhour{$hour} || 0,
1366     + $infectedbyhour{$hour} || 0,
1367     + $spambyhour{$hour} || 0,
1368     + $hambyhour{$hour} || 0,
1369     + $RBLbyhour{$hour} || 0,
1370     + $patternfilterbyhour{$hour} || 0,
1371     + $MiscDenybyhour{$hour} || 0
1372     + );
1373     + $hour++;
1374     + }
1375     +
1376     + print "---------------------------------------------------------------------------------------- \n";
1377     +
1378     + }
1379     + if ($localAccepttotal>0) {
1380     + print "*Fetchml* means connections from Fetchmail delivering email\n";
1381     + }
1382     + print "*Local* means connections from workstations on local LAN.\n";
1383     + print "*Non\.Conf\.* means sending mailserver did not conform to correct protocol.\n";
1384     + print " or email was to non existant address.\n";
1385     + print "\n";
1386     +
1387     + if ($QueryNoLogTerse) {
1388     + print "* - as no records where found, it looks as though you may not have the *logterse* \nplugin running as part of qpsmtpd \n";
1389     +# print " to enable it follow the instructions at .............................\n";
1390     + }
1391     +
1392     +
1393     + if ( !$RHSenabled || !$DNSenabled ) {
1394     +
1395     + # comment about RBL not set
1396     + print
1397     +"* - This means that one or more of the possible spam black listing services\n that are available have not been enabled.\n";
1398     + print " You have not enabled:\n";
1399     +
1400     + if ( !$RHSenabled ) {
1401     + print " RHSBL\n";
1402     + }
1403     +
1404     + if ( !$DNSenabled ) {
1405     + print " DNSBL\n";
1406     + }
1407     +
1408     +
1409     + print " To enable these you can use the following commands:\n";
1410     + if ( !$RHSenabled ) {
1411     + print " config setprop qpsmtpd RHSBL enabled\n";
1412     + }
1413     +
1414     + if ( !$DNSenabled ) {
1415     + print " config setprop qpsmtpd DNSBL enabled\n";
1416     + }
1417     +
1418     + # there so much templates to expand... (PS)
1419     + print " Followed by:\n signal-event email-update and\n svc -t /service/qpsmtpd\n\n";
1420     + }
1421     +
1422     +# if ($Webmailsendtotal > 0) {print "If you have the mailman contrib installed, then the webmail totals might include some mailman emails\n"}
1423     +
1424     + # time to do a 'by recipient domain' report
1425     + print "\nIncoming mails by recipient domains usage\n";
1426     + print "-----------------------------------------\n";
1427     + print
1428     + "Domains Type Total Denied XferErr Accept \%accept\n";
1429     + print
1430     + "---------------------------- ---------- ------ ------ ------- ------ -------\n";
1431     + my %total = (
1432     + total => 0,
1433     + deny => 0,
1434     + xfer => 0,
1435     + accept => 0,
1436     + );
1437     + foreach my $domain (
1438     + sort {
1439     + join( "\.", reverse( split /\./, $a ) ) cmp
1440     + join( "\.", reverse( split /\./, $b ) )
1441     + } keys %byrcptdomain
1442     + )
1443     + {
1444     + next if ( ( $byrcptdomain{$domain}{'total'} || 0 ) == 0 );
1445     + my $tp = $byrcptdomain{$domain}{'type'} || 'other';
1446     + my $to = $byrcptdomain{$domain}{'total'} || 0;
1447     + my $de = $byrcptdomain{$domain}{'deny'} || 0;
1448     + my $xr = $byrcptdomain{$domain}{'xfer'} || 0;
1449     + my $ac = $byrcptdomain{$domain}{'accept'} || 0;
1450     + printf "%-28s %-10s %6d %6d %7d %6d %6.2f%%\n", $domain, $tp, $to,
1451     + $de, $xr, $ac, $ac * 100 / $to;
1452     + $total{'total'} += $to;
1453     + $total{'deny'} += $de;
1454     + $total{'xfer'} += $xr;
1455     + $total{'accept'} += $ac;
1456     + }
1457     + print
1458     + "---------------------------- ---------- ------ ------- ------ ------ -------\n";
1459     +
1460     + # $total{ 'total' } can be equal to 0, bad for divisions...
1461     + my $perc1 = 0;
1462     + my $perc2 = 0;
1463     + if ( $total{'total'} != 0 ) {
1464     + $perc1 = $total{'accept'} * 100 / $total{'total'};
1465     + $perc2 = ( ( $total{'total'} + $morethanonercpt ) / $total{'total'} );
1466     + }
1467     + printf
1468     + "Total %6d %6d %7d %6d %6.2f%%\n\n",
1469     + $total{'total'}, $total{'deny'}, $total{'xfer'}, $total{'accept'},
1470     + $perc1;
1471     + printf
1472     + "%d mails were processed for %d Recipients\nThe average recipients by mail is %4.2f\n\n",
1473     + $total{'total'}, ( $total{'total'} + $morethanonercpt ), $perc2;
1474     +
1475     + if ( $infectedcount > 0 ) {
1476     + show_virus_variants();
1477     + }
1478     +
1479     +} # not disabled
1480     +
1481     +List_Junkmail();
1482     +
1483     +if ( !$disabled ) {
1484     +
1485     + print "\nDone. Report generated in $telapsed sec.\n\n";
1486     +
1487     + #Close Senmdmail if it was opened
1488     + if ( $opt{'mail'} ) {
1489     + select $oldfh;
1490     + close(SENDMAIL);
1491     + }
1492     +
1493     +}
1494     +
1495     +#All done
1496     +exit 0;
1497     +
1498     +#############################################################################
1499     +# Subroutines ###############################################################
1500     +#############################################################################
1501     +
1502     +
1503     +########################################
1504     +# Process parms #
1505     +########################################
1506     +sub parse_arg {
1507     + my $startdate = shift;
1508     + my $enddate = shift;
1509     +
1510     + my $secsinday = 86400;
1511     + my $time = 0;
1512     +
1513     + my $start = UnixDate( $startdate, "%s" );
1514     + my $end = UnixDate( $enddate, "%s" );
1515     +
1516     + if ( !$start && !$end ) {
1517     + $end = time;
1518     + $start = $end - $secsinday;
1519     + return ( $start, $end );
1520     + }
1521     +
1522     + if ( !$start ) {
1523     + $start = $end - $secsinday;
1524     + return ( $start, $end );
1525     + }
1526     +
1527     + if ( !$end ) {
1528     + $end = $start + $secsinday;
1529     + return ( $start, $end );
1530     + }
1531     +
1532     + if ( $start > $end ) {
1533     + return ( $end, $start );
1534     + }
1535     +
1536     + return ( $start, $end );
1537     +
1538     +}
1539     +
1540     +sub dbg {
1541     + my $msg = shift;
1542     +
1543     + if ( $opt{debug} ) {
1544     + print STDERR $msg;
1545     + }
1546     +}
1547     +
1548     +sub List_Junkmail {
1549     +
1550     + #
1551     + # Show how many junkmails in each user's junkmail folder.
1552     + #
1553     + use esmith::AccountsDB;
1554     + my $adb = esmith::AccountsDB->open_ro;
1555     + my $entry;
1556     + foreach my $user ($adb->users) {
1557     + my $found = 0;
1558     + my $junkmail_dir = "/home/e-smith/files/users/" .
1559     + $user->key . "/Maildir/.junkmail";
1560     +# print $user->key;
1561     + foreach my $dir (qw(new cur)) {
1562     + # Now get the content list for the directory.
1563     + if (opendir( QDIR, "$junkmail_dir/$dir" )) {
1564     + while ($entry=readdir(QDIR) ) {
1565     + next if $entry =~ /^\./;
1566     + $found++;
1567     + }
1568     +
1569     + closedir(QDIR);
1570     + }
1571     + }
1572     + if ( !$disabled ) {
1573     + printf "User \"%s\" ", $user->key;
1574     + printf "- %d email(s) left in junkmail folder\n", $found;
1575     + }
1576     + }
1577     +}
1578     +
1579     +sub show_virus_variants
1580     +
1581     +#
1582     +# Show a league table of the different virus types found today
1583     +#
1584     +
1585     +{
1586     +
1587     + print("Virus Statistics by name:\n");
1588     + print("---------------------------------------------\n");
1589     + foreach my $virus (sort { $found_viruses{$b} <=> $found_viruses{$a} }
1590     + keys %found_viruses)
1591     + {
1592     + print "Rejected $found_viruses{$virus}\t$virus\n";
1593     + }
1594     + print("---------------------------------------------\n");
1595     +}
1596     +
1597     +sub mark_domain_rejected
1598     +
1599     +#
1600     +# Tag domain as having a rejected email
1601     +#
1602     +{
1603     +my ($proc) = @_;
1604     +if ( ( $currentrcptdomain{ $proc } || '' ) ne '' ) {
1605     + $byrcptdomain{ $currentrcptdomain{ $proc } }{ 'deny' }++ ;
1606     + $currentrcptdomain{ $proc } = '' ;
1607     + }
1608     }
1609     \ No newline at end of file

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