/[smecontribs]/rpms/smeserver-mailstats/contribs9/smeserver-mailstats-1.1.bz9588.qpsmtpd0_96compatible.patch
ViewVC logotype

Annotation of /rpms/smeserver-mailstats/contribs9/smeserver-mailstats-1.1.bz9588.qpsmtpd0_96compatible.patch

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


Revision 1.1 - (hide annotations) (download)
Sat Jul 2 08:44:13 2016 UTC (7 years, 10 months ago) by unnilennium
Branch: MAIN
CVS Tags: smeserver-mailstats-1_1-9_el6_sme, smeserver-mailstats-1_1-6_el6_sme, smeserver-mailstats-1_1-7_el6_sme, smeserver-mailstats-1_1-11_el6_sme, smeserver-mailstats-1_1-8_el6_sme, smeserver-mailstats-1_1-12_el6_sme, smeserver-mailstats-1_1-10_el6_sme, HEAD
* Sat Jul 02 2016 Jean-Philipe Pialasse <tests@pialasse.com> 1.1-6.sme
- make compatible with qpmstmpd 0.96 and new plugins [SME: 9588]
- html version of email
- code by Brian Read <brianr@bjsystems.co.uk>
- thanks to Michael Doerner for his extensive testing

1 unnilennium 1.1 diff -Nur smeserver-mailstats-1.1.old/root/usr/bin/spamfilter-stats-7.pl smeserver-mailstats-1.1/root/usr/bin/spamfilter-stats-7.pl
2     --- smeserver-mailstats-1.1.old/root/usr/bin/spamfilter-stats-7.pl 2016-07-02 04:29:21.671000000 -0400
3     +++ smeserver-mailstats-1.1/root/usr/bin/spamfilter-stats-7.pl 2016-07-02 04:39:34.037000000 -0400
4     @@ -18,6 +18,8 @@
5     # bjr - 19Jun15 - Add totals for the League tables
6     # bjr and Unnilennium - 08Apr16 - Add in else for unrecognised plugin detection
7     # bjr - 08Apr16 - Add in link for SaneSecurity "extra" virus detection
8     +# bjr - 14Jun16 - make compatible with qpsmtpd 0.96
9     +# bjr - 16Jun16 - Add code to create an html equivalent of the text email (v0.7)
10     #
11     #############################################################################
12     #
13     @@ -37,8 +39,10 @@
14     # / ShowLeagueTotals - Show totals row after league tables - (default is "yes")
15     # / DBHost - MySQL server hostname (default is "localhost").
16     # / DBPort - MySQL server post (default is "3306")
17     -# / Interval - "day", "week", "fortnight", "month", "99999" - last is number of seconds (default is day)
18     +# / Interval - "daily", "weekly", "fortnightly", "monthly", "99999" - last is number of hours (default is daily)
19     # / Base - "Midnight", "Midday", "Now", "99" hour (0-23) (default is midnight)
20     +# / HTMLEmail - "yes", "no", "both" - default is "No" - Send email in HTML
21     +# / HTMLPage - "yes" / "no" - default is "yes" if HTMLEmail is "yes" or "both" otherwise "no"
22     #
23     #############################################################################
24    
25     @@ -55,6 +59,10 @@
26     use esmith::DomainsDB;
27     use Sys::Hostname;
28     use Switch;
29     +use DBIx::Simple;
30     +
31     +#use CGI;
32     +#use HTML::TextToHTML;
33    
34     my $hostname = hostname();
35     my $cdb = esmith::ConfigDB->open_ro or die "Couldn't open ConfigDB : $!\n";
36     @@ -73,12 +81,11 @@
37    
38     #Configuration section
39     my %opt = (
40     - version => '0.6.29', # please update at each change.
41     + version => '0.7.5a', # please update at each change.
42     debug => 0, # guess what ?
43     sendmail => '/usr/sbin/sendmail', # Path to sendmail stub
44     from => 'spamfilter-stats', # Who is the mail from
45     - mail => # mailstats email recipient
46     - $cdb->get('mailstats')->prop('Email') || 'admin',
47     + mail => $cdb->get('mailstats')->prop('Email') || 'admin', # mailstats email recipient
48     timezone => `date +%z`,
49     );
50    
51     @@ -87,8 +94,10 @@
52     my $localhost = 'localhost'; #Apparent sender for webmail
53     my $FETCHMAIL = 'FETCHMAIL'; #Sender from fetchmail when Ip address not 127.0.0.200 - when qpsmtpd denies the email
54     my $MAILMAN = "bounces"; #sender when mailman sending when orig is localhost
55     +my $DMARCDomain="dmarc"; #Pattern to recognised DMARC sent emails (this not very reliable, as the email address could be anything)
56     +my $DMARCOkPattern="dmarc: pass"; #Pattern to use to detect DMARC approval
57    
58     -my $MinCol = 8; #Minimum column width
59     +my $MinCol = 6; #Minimum column width
60     my $HourColWidth = 16; #Date and time column width
61    
62     my $SARulethresholdPercent = 10; #If Sa rules less than this of total emails, then cutoff reduced
63     @@ -116,6 +125,10 @@
64     my $totalexamined = 0; #total download + RBL etc
65     my $WebMailsendtotal = 0; #total from Webmail
66     my $mailmansendcount = 0; #total from mailman
67     +my $DMARCSendCount = 0; #total DMARC reporting emails sent (approx)
68     +my $DMARCOkCount = 0; #Total emails approved through DMARC
69     +
70     +
71    
72     my %found_viruses = ();
73     my %found_qpcodes = ();
74     @@ -137,25 +150,35 @@
75     my $CATMAILMAN='Mailman';
76     my $CATLOCAL='Local';
77     # border between where it came from and where it ended..
78     -my $countfromhere = 5;
79     -
80     +my $countfromhere = 5; #Temp - Check this not moved!!
81     +
82     my $CATVIRUS='Virus';
83     my $CATRBLDNS='RBL/DNS';
84     my $CATEXECUT='Execut.';
85     my $CATNONCONF='Non.Conf.';
86     my $CATBADCOUNTRIES='Geoip.';
87     -my $BadCountryCateg=8; #Careful here this number could change if more added before.
88     +my $CATKARMA="Karma";
89     +
90     my $CATSPAMDEL='Del.Spam';
91     my $CATSPAM='Qued.Spam?';
92     my $CATHAM='Ham';
93     my $CATTOTALS='TOTALS';
94     my $CATPERCENT='PERCENT';
95     -my @categs = ($CATHOUR,$CATFETCHMAIL,$CATWEBMAIL,$CATMAILMAN,$CATLOCAL,$CATVIRUS,$CATRBLDNS,$CATEXECUT,$CATBADCOUNTRIES,$CATNONCONF,$CATSPAMDEL,$CATSPAM,$CATHAM,$CATTOTALS,$CATPERCENT);
96     +my $CATDMARC="DMARC Rej.";
97     +my $CATLOAD="Rej.Load";
98     +my @categs = ($CATHOUR,$CATFETCHMAIL,$CATWEBMAIL,$CATMAILMAN,$CATLOCAL,$CATDMARC,$CATVIRUS,$CATRBLDNS,$CATEXECUT,$CATBADCOUNTRIES,$CATNONCONF,$CATLOAD,$CATKARMA,$CATSPAMDEL,$CATSPAM,$CATHAM,$CATTOTALS,$CATPERCENT);
99     my $GRANDTOTAL = '99'; #subs for count arrays, for grand total
100     my $PERCENT = '98'; # for column percentages
101    
102     my $categlen = @categs-2; #-2 to avoid the total and percent column
103    
104     +#
105     +# Index for certain columns - check these do not move if we add columns
106     +#
107     +my $BadCountryCateg=9;
108     +my $DMARCcateg = 5; #Not used.
109     +my $KarmaCateg=$BadCountryCateg+3;
110     +
111     my $above15 = 0;
112     my $RBLcount = 0;
113     my $MiscDenyCount = 0;
114     @@ -187,6 +210,38 @@
115     my $morethanonercpt = 0 ; # count every 'second' recipients for a mail.
116     my $recipcount = 0; # count every recipient email address received.
117    
118     +#
119     +#Load up the emails curreently stored for DMARC reporting - so that we cna spot the reports being sent.
120     +#Held in an slqite db, created by the DMARC perl lib.
121     +#
122     +my $dsn = "dbi:SQLite:dbname=/var/lib/qpsmtpd/dmarc/reports.sqlite"; #Taken from /etc/mail-dmarc.ini
123     +# doesn't seem to need
124     +my $user = "";
125     +my $pass = "";
126     +my $DMARC_Report_emails = ""; #Flat string of all email addresses
127     +
128     + if (my $dbix = DBIx::Simple->connect( $dsn, $user, $pass )){
129     + my $result = $dbix->query("select rua from report_policy_published;");
130     + $result->bind(my ($emailaddress));
131     + while ($result->fetch){
132     + #print STDERR "$emailaddress";
133     + #remember email from logterse entry has chevrons round it - so we add them here to guarantee the alighment of the match
134     + #Remove the mailto:
135     + $emailaddress =~ s/mailto://g;
136     + # and map any commas to ><
137     + $emailaddress =~ s/,/></g;
138     + $DMARC_Report_emails .= "<".$emailaddress.">\n"
139     + }
140     + $dbix->disconnect();
141     + } else { $DMARC_Report_emails = "None found - DB not opened"}
142     +
143     +
144     +#dbg("DMARC-EMAILS:".$DMARC_Report_emails);
145     +
146     +# Saving the Log lines processed
147     +my %LogLines = (); #Save all the log lines processed for writing to the DB
148     +my %LogId = (); #Save the Log Ids.
149     +my $CurrentLogId = "";
150    
151     # store the domain of interest. Every other records are stored in a 'Other' zone
152     my $ddb = esmith::DomainsDB->open_ro or die "Couldn't open DomainsDB : $!\n";
153     @@ -212,6 +267,8 @@
154    
155     my ( $start, $end ) = analysis_period();
156    
157     +dbg("Time interval:".strftime("%a %b %e %H:%M:%S %Y", localtime($start))."->".strftime("%a %b %e %H:%M:%S %Y", localtime($end))."\n");
158     +
159     #
160     # First check current configuration for logging, DNS enable and Max threshold for spamassassin
161     #
162     @@ -243,6 +300,27 @@
163    
164     }
165    
166     +# get enable/disable subsections
167     +my $enableqpsmtpdcodes;
168     +my $enableSARules;
169     +my $enableGeoiptable;
170     +my $enablejunkMailList;
171     +my $savedata;
172     +if ($cdb->get('mailstats')){
173     + $enableqpsmtpdcodes = ($cdb->get('mailstats')->prop("QpsmtpdCodes") || "enabled") eq "enabled" || $false;
174     + $enableSARules = ($cdb->get('mailstats')->prop("SARules") || "enabled") eq "enabled" || $false;
175     + $enablejunkMailList = ($cdb->get('mailstats')->prop("JunkMailList") || "enabled") eq "enabled" || $false;
176     + $enableGeoiptable = ($cdb->get('mailstats')->prop("Geoiptable") || "enabled") eq "enabled" || $false;
177     + $savedata = ($cdb->get('mailstats')->prop("SaveDataToMySQL") || "no") eq "yes" || $false;
178     + } else {
179     + $enableqpsmtpdcodes = $true;
180     + $enableSARules = $true;
181     + $enablejunkMailList = $true;
182     + $enableGeoiptable = $true;
183     + $savedata = $false;
184     + }
185     + $savedata = $false; #TEMP!!
186     +
187     #
188     #---------------------------------------
189     # Scan the qpsmtpd log file(s)
190     @@ -263,6 +341,7 @@
191     }
192     # and grand totals, percent and display status from db entries, and column widths
193     $ncateg = 0;
194     +my $colpadding = 0;
195     while ( $ncateg < @categs) {
196     $counts{$GRANDTOTAL}{$categs[$ncateg]} = 0;
197     $counts{$PERCENT}{$categs[$ncateg]} = 0;
198     @@ -273,11 +352,11 @@
199     $display[$ncateg] = 'auto'
200     }
201     if ($ncateg == 0) {
202     - $colwidth[$ncateg] = $HourColWidth
203     + $colwidth[$ncateg] = $HourColWidth + $colpadding;
204     } else {
205     - $colwidth[$ncateg] = length($categs[$ncateg])+1
206     + $colwidth[$ncateg] = length($categs[$ncateg])+1+$colpadding;
207     }
208     - if ($colwidth[$ncateg] < $MinCol) {$colwidth[$ncateg] = $MinCol}
209     + if ($colwidth[$ncateg] < $MinCol) {$colwidth[$ncateg] = $MinCol + $colpadding}
210     $ncateg++
211     }
212    
213     @@ -292,39 +371,91 @@
214     }
215     @ARGV=@ARGV2;
216    
217     +my $count = -1; #for loop reduction in debugging mode
218     +
219     +my $CurrentMailId = "";
220     +
221     LINE: while (<>) {
222     - my($tai,$log) = split(' ',$_,2);
223    
224     + #print STDERR $starttai,$endtai,$_,"\n";
225     +
226    
227     + next LINE if !(my($tai,$log) = split(' ',$_,2));
228     + #dbg("TAI:".$tai);
229     +
230     + #dbg("REST1:".$log);
231     +
232     #If date specified, only process lines matching date
233     next LINE if ( $tai lt $starttai );
234     next LINE if ( $tai gt $endtai );
235    
236     + #Count lines and skip out if debugging
237     + $count++;
238     + last LINE if ($opt{debug} && $count >= 100000);
239     + #dbg("REST:".$log);
240     +
241     + #Loglines to Saved String for later DB write
242     + if ($savedata) {
243     + my $CurrentLine = $_;
244     + $CurrentLine = /^\@([0-9a-z]*) ([0-9]*) .*$/;
245     + if ($2 ne $CurrentMailId) {
246     + $CurrentLogId = $1."-".$2;
247     + $CurrentMailId = $2;
248     + }
249     + $LogLines{$CurrentLogId} = $_;
250     + #print $CurrentLogId.":".$LogLines{$CurrentLogId}."\n";
251     + }
252     +
253     + #Count lines and skip out if debugging
254     + $count++;
255     + last LINE if ($opt{debug} && $count >= 100);
256     + #dbg("REST:".$log);
257     +
258     +
259     # pull out spamasassin rule lists
260     - if ( $_ =~m/spamassassin plugin.*: check_spam:.*hits=(.*), required.*tests=(.*)/ )
261     + if ( $_ =~m/spamassassin: pass, Ham,(.*)</ )
262     + #if ( $_ =~m/spamassassin plugin.*: check_spam:.*hits=(.*), required.*tests=(.*)/ )
263     {
264     - my (@SAtests) = split(',',$2);
265     - foreach my $SAtest (@SAtests) {
266     - if (!$SAtest eq "") {
267     - $found_SARules{$SAtest}{'count'}++;
268     - $found_SARules{$SAtest}{'totalhits'} += $1;
269     - $sum_SARules++
270     - }
271     - }
272     -
273     + dbg("SPAM:".$log);
274     +
275     +
276     + #New version does not seem to have spammassasin tests in logs
277     +
278     + #if (exists($2){
279     + #my (@SAtests) = split(',',$2);
280     + #foreach my $SAtest (@SAtests) {
281     + #if (!$SAtest eq "") {
282     + #$found_SARules{$SAtest}{'count'}++;
283     + #$found_SARules{$SAtest}{'totalhits'} += $1;
284     + #$sum_SARules++
285     + #}
286     + #}
287     + #}
288     +
289     }
290     -
291     +
292     +
293     #Pull out Geoip countries for analysis table
294     - if ( $_ =~m/check_badcountries plugin \(connect\): GeoIP Country: (.*)/ )
295     + if ( $_ =~m/check_badcountries: GeoIP Country: (.*)/ )
296     {
297     $found_countries{$1}++;
298     $total_countries++;
299     }
300     +
301     + #Pull out DMARC approvals
302     + if ( $_ =~m/.*$DMARCOkPattern.*/ )
303     + {
304     + $DMARCOkCount++;
305     + }
306     +
307    
308     #only select Logterse output
309     - next LINE unless m/terse plugin/;
310     -
311     -
312     + next LINE unless m/logging::logterse:/;
313     +
314     + #Count lines and skip out if debugging
315     + $count++;
316     + last LINE if ($opt{debug} && $count >= 100000);
317     + #dbg("REST:".$log);
318    
319     my $abstime = Time::TAI64::tai2unix($tai);
320     my $abshour = floor( $abstime / 3600 ); # Hours since the epoch
321     @@ -342,6 +473,9 @@
322    
323     $totalexamined++;
324    
325     + #dbg("LOG1:".$log_items[1]);
326     + #dbg("LOG3:".$log_items[3]);
327     +
328     # first spot the fetchmail and local deliveries.
329    
330     # Spot from local workstation
331     @@ -355,11 +489,9 @@
332    
333     # see if from localhost
334     elsif ( $log_items[1] =~ m/.*$localhost.*/ ) {
335     -
336     # but not if it comes from fetchmail
337     if ( $log_items[3] =~ m/.*$FETCHMAIL.*/ ) { }
338     else {
339     -
340     # might still be from mailman here
341     if ( $log_items[3] =~ m/.*$MAILMAN.*/ ) {
342     $mailmansendcount++;
343     @@ -368,21 +500,44 @@
344     $localflag = 1;
345     }
346     else {
347     -
348     - # eliminate incoming localhost spoofs
349     - if ( $log_items[8] =~ m/.*msg denied before queued.*/ ) { }
350     - else {
351     - $localflag = 1;
352     - $WebMailsendtotal++;
353     - $counts{$abshour}{$CATWEBMAIL}++;
354     - $WebMailflag = 1;
355     - }
356     + #Or sent to the DMARC server
357     + dbg("LOG4:".$log_items[4]);
358     + #check for email address in $DMARC_Report_emails string
359     + #if ($log_items[4] =~ m/.*$DMARCDomain.*/) {
360     + my $logemail = $log_items[4];
361     + #print STDERR "/",$log_items[4]."/\n";
362     + if ((index($DMARC_Report_emails,$logemail)>=0) || ($logemail =~ m/.*$DMARCDomain.*/)){
363     + $localsendtotal++;
364     + $DMARCSendCount++;
365     + $localflag = 1;
366     + }
367     + else {
368     + #print STDERR "no match:.".$logemail;
369     + if (exists $log_items[8]){
370     + dbg("LOG8:".$log_items[8]);
371     + # ignore incoming localhost spoofs
372     + if ( $log_items[8] =~ m/.*msg denied before queued.*/ ) { }
373     + else {
374     + $localflag = 1;
375     + $WebMailsendtotal++;
376     + $counts{$abshour}{$CATWEBMAIL}++;
377     + $WebMailflag = 1;
378     + }
379     + }
380     + else {
381     + $localflag = 1;
382     + $WebMailsendtotal++;
383     + $counts{$abshour}{$CATWEBMAIL}++;
384     + $WebMailflag = 1;
385     + }
386     + }
387     }
388     }
389     }
390    
391     # try to spot fetchmail emails
392     if ( $log_items[0] =~ m/.*$FetchmailIP.*/ ) {
393     + dbg("LOG0:".$log_items[0]);
394     $localAccepttotal++;
395     $counts{$abshour}{$CATFETCHMAIL}++;
396     }
397     @@ -394,10 +549,13 @@
398     # and adjust for recipient field if not set-up by denying plugin - extract from deny msg
399    
400     if ( length( $log_items[4] ) == 0 ) {
401     + dbg("LOG7:".$log_items[0]);
402     if ( $log_items[5] eq 'check_goodrcptto' ) {
403     if ( $log_items[7] gt "invalid recipient" ) {
404     $log_items[4] =
405     - substr( $log_items[7], 18 ) #Leave only email address
406     + substr( $log_items[7], 18 ); #Leave only email address
407     + dbg("LOG4:".$log_items[0]);
408     +
409     }
410     }
411     }
412     @@ -405,6 +563,7 @@
413     # if ( ( $currentrcptdomain{ $proc } || '' ) eq '' ) {
414     # reduce to lc and process each e,mail if a list, pseperatedy commas
415     my $recipientmail = lc( $log_items[4] );
416     + dbg("LOG4:".$log_items[0]);
417     if ( $recipientmail =~ m/.*,/ ) {
418    
419     #comma - split the line and deal with each domain
420     @@ -442,7 +601,12 @@
421    
422     if (exists $log_items[5]) {
423    
424     - $found_qpcodes{$log_items[5]}++; ##Count different qpsmtpd result codes
425     + if ($log_items[5] eq 'naughty') {
426     + my $rejreason = $log_items[7];
427     + $rejreason = /.*(\(.*\)).*/;
428     + $rejreason = $1;
429     + $found_qpcodes{$log_items[5]."-".$rejreason}++}
430     + else {$found_qpcodes{$log_items[5]}++} ##Count different qpsmtpd result codes
431    
432     #Check for badly formed lines (from earlier testing)
433    
434     @@ -488,27 +652,31 @@
435    
436     elsif ($log_items[5] eq 'spamassassin') { $above15++;$counts{$abshour}{$CATSPAMDEL}++;
437     # and extract the spam score
438     - if ($log_items[8] =~ "Yes, hits=(.*) required=([0-9\.]+)") {$rejectspamavg += $1}
439     +# if ($log_items[8] =~ "Yes, hits=(.*) required=([0-9\.]+)")
440     + if ($log_items[8] =~ "Yes, score=(.*) required=([0-9\.]+)")
441     + {$rejectspamavg += $1}
442     mark_domain_rejected($proc);
443     next LINE
444     }
445    
446     - elsif ($log_items[5] eq 'virus::clamav') { $infectedcount++;$counts{$abshour}{$CATVIRUS}++;
447     + elsif (($log_items[5] eq 'virus::clamav') || ($log_items[5] eq 'virus::clamdscan')) { $infectedcount++;$counts{$abshour}{$CATVIRUS}++;
448     #extract the virus name
449     - if ($log_items[7] =~ "Virus Found: (.*)" ) {$found_viruses{$1}++;}
450     + if ($log_items[7] =~ "Virus found: (.*)" ) {$found_viruses{$1}++;}
451     + else {$found_viruses{$log_items[7]}++} #Some other message!!
452     + dbg("LOG7:".$log_items[7]);
453     mark_domain_rejected($proc);
454     next LINE
455     }
456    
457     elsif ($log_items[5] eq 'queued') { $Accepttotal++;
458     #extract the spam score
459     - if ($log_items[8] =~ ".*hits=(.*) required=([0-9\.]+)") {
460     + if ($log_items[8] =~ ".*score=(.*) required=([0-9\.]+)") {
461     $score = $1;
462     # print $log_items[8]."<".$score.">\n";
463     if ($score < $SATagLevel) { $hamcount++;$counts{$abshour}{$CATHAM}++;$hamavg += $score}
464     else {$spamcount++;$counts{$abshour}{$CATSPAM}++;$spamavg += $score}
465     } else {
466     - # no SA score - so it must be ham
467     + # no SA score - treat it as ham
468     $hamcount++;$counts{$abshour}{$CATHAM}++;
469     }
470     if ( ( $currentrcptdomain{ $proc } || '' ) ne '' ) {
471     @@ -523,15 +691,53 @@
472    
473     elsif ($log_items[5] eq 'auth::auth_cvm_unix_local') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
474    
475     + elsif ($log_items[5] eq 'earlytalker') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
476     +
477     + elsif ($log_items[5] eq 'uribl') {$RBLcount++;$counts{$abshour}{$CATRBLDNS}++;mark_domain_rejected($proc);next LINE}
478     +
479     + elsif ($log_items[5] eq 'naughty') {
480     + #Naughty plugin seems to span a number of rejection reasons - so we have to use the next but one log_item[7] to identify
481     + if ($log_items[7] =~ m/(karma)/) {
482     + $MiscDenyCount++;$counts{$abshour}{$CATKARMA}++;mark_domain_rejected($proc);next LINE}
483     + elsif ($log_items[7] =~ m/(dnsbl)/){
484     + $RBLcount++;$counts{$abshour}{$CATRBLDNS}++;mark_domain_rejected($proc);next LINE}
485     + elsif ($log_items[7] =~ m/(helo)/){
486     + $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
487     + else {
488     + #Unidentified Naughty rejection
489     + $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);$unrecog_plugin{$log_items[5]."-".$log_items[7]}++;next LINE}
490     + }
491     + elsif ($log_items[5] eq 'resolvable_fromhost') {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
492     +
493     + elsif ($log_items[5] eq 'loadcheck') {$MiscDenyCount++;$counts{$abshour}{$CATLOAD}++;mark_domain_rejected($proc);next LINE}
494     +
495     + elsif ($log_items[5] eq 'karma') {$MiscDenyCount++;$counts{$abshour}{$CATKARMA}++;mark_domain_rejected($proc);next LINE}
496     +
497     + elsif ($log_items[5] eq 'dmarc') {$MiscDenyCount++;$counts{$abshour}{$CATDMARC}++;mark_domain_rejected($proc);next LINE}
498     +
499     + elsif ($log_items[5] eq 'relay') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
500     +
501     + elsif ($log_items[5] eq 'headers') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
502     +
503     + elsif ($log_items[5] eq 'mailfrom') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
504     +
505     + elsif ($log_items[5] eq 'badrcptto') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
506     +
507     + elsif ($log_items[5] eq 'helo') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
508     +
509     + elsif ($log_items[5] eq 'check_smtp_forward') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
510     +
511     + elsif ($log_items[5] eq 'sender_permitted_from') { $MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);next LINE}
512     +
513     #Treat it as Unconf if not recognised
514     else {$MiscDenyCount++;$counts{$abshour}{$CATNONCONF}++;mark_domain_rejected($proc);$unrecog_plugin{$log_items[5]}++;next LINE}
515     + } #Log[5] exists
516     +
517     +
518     +# print "Unexpected failure string in log file: ".$log_items[5]."\n"; #Not detected
519     +# next LINE
520    
521     -/*
522     - print "Unexpected failure string in log file: ".$log_items[5]."\n"; #Not detected
523     - next LINE
524     -*/
525    
526     - }
527    
528     } #END OF MAIN LOOP
529    
530     @@ -624,18 +830,28 @@
531     if ( !$disabled ) {
532    
533     #Output results
534     +
535     + # NEW - save the print to a variable so that it can be processed into html.
536     + #
537     + #Save current output selection and divert into variable
538     + #
539     + my $output;
540     + my $tablestr="";
541     + open(my $outputFH, '>', \$tablestr) or die; # This shouldn't fail
542     + my $oldFH = select $outputFH;
543     +
544     +
545     print "SMEServer daily Anti-Virus and Spamfilter statistics", "\n";
546     print "----------------------------------------------------", "\n\n";
547    
548     print "$0 Version : $opt{'version'}", "\n\n";
549     - print "Period Beginning : ", strftime( "%c", localtime($start) ), "\n";
550     + print "Period Beginning : ", strftime( "%c", localtime($start) ), "\n\n";
551     print "Period Ending : ", strftime( "%c", localtime($end) ), "\n";
552     print "\n";
553    
554     - print "Clam Version : ", `freshclam -V`;
555     - print "SpamAssassin Version : ", `spamassassin -V`;
556     - printf "Tag level: %3d; Reject level: %3d $warnnoreject\n", $SATagLevel,
557     - $SARejectLevel;
558     + print "Clam Version/DB Count/Last DB update: ", `freshclam -V`."\n";
559     + print "SpamAssassin Version : ", `spamassassin -V`."\n";
560     + printf "Tag level: %3d; Reject level: %3d $warnnoreject", $SATagLevel,$SARejectLevel;
561     if ($HighLogLevel) {
562     printf "*Loglevel is set to: ".$LogLevel. " - you only need it set to 6\n";
563     printf "\tYou can set it this way:\n";
564     @@ -643,10 +859,10 @@
565     printf "\tsignal-event email-update\n";
566     printf "\tsv t /var/service/qpsmtpd\n\n";
567     }
568     - print "\n";
569     + print "\n\n";
570     printf "Reporting Period : %.2f hrs\n", $hrsinperiod;
571     - print "----------------------------\n";
572     - print "\n";
573     + #print "----------------------------\n";
574     + #print "\n";
575    
576     printf "All SMTP connections accepted:%8d \n", $totalexamined;
577    
578     @@ -655,8 +871,13 @@
579     printf "Average spam score (accepted): %11.2f\n", $spamavg || 0;
580     printf "Average spam score (rejected): %11.2f\n", $rejectspamavg || 0;
581     printf "Average ham score : %11.2f\n", $hamavg || 0;
582     - print "\n";
583     - print "Statistics by Hour\n";
584     + printf "\nNumber of DMARC reporting emails sent: %11d (not shown on table)\n", $DMARCSendCount || 0;
585     + if ($hamcount != 0){ printf "Number of emails approved through DMARC: %11d (%4d%% of Ham count)\n", $DMARCOkCount|| 0,$DMARCOkCount*100/$hamcount || 0;}
586     +
587     + print "\n\n";
588     + print "\nStatistics by Hour\n";
589     + print "-------------------\n";
590     + #print "\n";
591    
592     #
593     # start by working out which colunns to show - tag the display array
594     @@ -682,13 +903,13 @@
595    
596    
597     # and put together the print lines
598     - #
599     +
600     my $Line1; #Full Line across the page
601     my $Line2; #Broken Line across the page
602     my $Titles; #Column headers
603     my $Values; #Values
604     my $Totals; #Corresponding totals
605     - my $Percent; # and column percentages
606     + my $Percent; # and column percentages
607    
608     my $hour = floor( $start / 3600 );
609     $Line1 = '';
610     @@ -706,7 +927,7 @@
611     $Line1 .= substr('---------------------',0,$colwidth[$ncateg]);
612     $Line2 .= substr('---------------------',0,$colwidth[$ncateg]-1);
613     $Line2 .= " ";
614     - $Titles .= sprintf('%'.($colwidth[$ncateg]-1).'s',$categs[$ncateg])." ";
615     + $Titles .= sprintf('%'.($colwidth[$ncateg]-1).'s',$categs[$ncateg])."|";
616     if ($ncateg == 0) {
617     $Totals .= substr('TOTALS ',0,$colwidth[$ncateg]-2);
618     $Percent .= substr('PERCENTAGES ',0,$colwidth[$ncateg]-1);
619     @@ -744,39 +965,58 @@
620     $hour++;
621     }
622    
623     - # print it.
624     - print $Line1."\n";
625     + #
626     + # print it.
627     + #
628     + my $makeHTMLemail = "no";
629     + #if ($cdb->get('mailstats')){$makeHTMLemail = $cdb->get('mailstats')->prop('HTMLEmail') || "no"} #TEMP!!
630     + my $makeHTMLpage = "no";
631     + if ($makeHTMLemail eq "yes" || $makeHTMLemail eq "both") {$makeHTMLpage = "yes"}
632     + #if ($cdb->get('mailstats')){$makeHTMLpage = $cdb->get('mailstats')->prop('HTMLPage') || "no"}
633     +
634     + if ($makeHTMLemail eq "no" && $makeHTMLpage eq "no"){print $Line1."\n";} #These lines mess up the HTML conversion ....
635     print $Titles."\n";
636     - print $Line2."\n";
637     + if ($makeHTMLemail eq "no" && $makeHTMLpage eq "no"){print $Line2."\n";} #ditto
638     + #$Line2 =~ s/-/a/g;
639     + #print $Line2."\n";
640     + #print "\n";
641     print $Values."\n";
642     print $Line2."\n";
643     print $Totals."\n";
644     print $Percent."\n";
645     print $Line1."\n";
646     -
647     + print "\n";
648    
649     if ($localAccepttotal>0) {
650     print "*Fetchml* means connections from Fetchmail delivering email\n";
651     }
652     - print "*Local* means connections from workstations on local LAN.\n";
653     + print "*Local* means connections from workstations on local LAN.\n\n";
654     print "*Non\.Conf\.* means sending mailserver did not conform to correct protocol";
655     - print " or email was to non existant address.\n";
656     + print " or email was to non existant address.\n\n";
657     +
658     + if ($finaldisplay[$KarmaCateg]){
659     + print "*Karma* means email was rejected based on the mailserver's previous activities.\n\n";
660     + }
661     +
662    
663     if ($finaldisplay[$BadCountryCateg]){
664     $BadCountries = $cdb->get('qpsmtpd')->prop('BadCountries') || "*none*";
665     - print "*Geoip\.*:Bad Countries mask is:".$BadCountries."\n";
666     + print "*Geoip\.*:Bad Countries mask is:".$BadCountries."\n\n";
667     }
668    
669     +
670     +
671     if (scalar keys %unrecog_plugin > 0){
672     #Show unrecog plugins found
673     print "*Unrecognised plugins found - categorised as Non-Conf\n";
674     foreach my $unrec (keys %unrecog_plugin){
675     print "\t$unrec\t($unrecog_plugin{$unrec})\n";
676     - }
677     + }
678     + print "\n";
679     }
680    
681     if ($QueryNoLogTerse) {
682     - print "* - as no records where found, it looks as though you may not have the *logterse* \nplugin running as part of qpsmtpd \n";
683     + print "* - as no records where found, it looks as though you may not have the *logterse* \nplugin running as part of qpsmtpd \n\n";
684     # print " to enable it follow the instructions at .............................\n";
685     }
686    
687     @@ -813,7 +1053,7 @@
688     # if ($Webmailsendtotal > 0) {print "If you have the mailman contrib installed, then the webmail totals might include some mailman emails\n"}
689    
690     # time to do a 'by recipient domain' report
691     - print "\nIncoming mails by recipient domains usage\n";
692     + print "Incoming mails by recipient domains usage\n";
693     print "-----------------------------------------\n";
694     print
695     "Domains Type Total Denied XferErr Accept \%accept\n";
696     @@ -869,25 +1109,6 @@
697     show_virus_variants();
698     }
699    
700     - # get enable/disable subsections
701     - my $enableqpsmtpdcodes;
702     - my $enableSARules;
703     - my $enableGeoiptable;
704     - my $enablejunkMailList;
705     - my $savedata;
706     - if ($cdb->get('mailstats')){
707     - $enableqpsmtpdcodes = ($cdb->get('mailstats')->prop("QpsmtpdCodes") || "enabled") eq "enabled" || $false;
708     - $enableSARules = ($cdb->get('mailstats')->prop("SARules") || "enabled") eq "enabled" || $false;
709     - $enablejunkMailList = ($cdb->get('mailstats')->prop("JunkMailList") || "enabled") eq "enabled" || $false;
710     - $enableGeoiptable = ($cdb->get('mailstats')->prop("Geoiptable") || "enabled") eq "enabled" || $false;
711     - $savedata = ($cdb->get('mailstats')->prop("SaveDataToMySQL") || "no") eq "yes" || $false;
712     - } else {
713     - $enableqpsmtpdcodes = $true;
714     - $enableSARules = $true;
715     - $enablejunkMailList = $true;
716     - $enableGeoiptable = $true;
717     - $savedata = $false;
718     - }
719    
720     if ($enableqpsmtpdcodes) {show_qpsmtpd_codes();}
721    
722     @@ -905,8 +1126,39 @@
723     "config setprop mailstats SaveDataToMySQL yes\n";
724     }
725    
726     + select $oldFH;
727     + close $outputFH;
728     + if ($makeHTMLemail eq "no" || $makeHTMLemail eq "both") {print $tablestr}
729     + if ($makeHTMLemail eq "yes" || $makeHTMLemail eq "both" || $makeHTMLpage eq "yes"){
730     + #Convert text to html and send it
731     + require CGI;
732     + require TextToHTML;
733     + my $cgi = new CGI;
734     + my $text = $tablestr;
735     + print $cgi->header();
736     + my %paramhash = (default_link_dict=>'',make_tables=>1,preformat_trigger_lines=>10,tab_width=>20);
737     + my $conv = new HTML::TextToHTML();
738     + $conv->args(default_link_dict=>'',make_tables=>1,preformat_trigger_lines=>2,preformat_whitespace_min=>2,
739     + underline_length_tolerance=>1);
740     + my $html="<!DOCTYPE html> <html>\n";
741     + $html .= "<head><title>Mailstats -".strftime( "%F", localtime($start) )."</title>";
742     + $html .= "<link rel='stylesheet' type='text/css' href='mailstats.css' /></head>\n";
743     + $html .= "<body>\n";
744     + $html .= $conv->process_chunk($text);
745     + $html .= "</body></html>\n";
746     + if ($makeHTMLemail eq "yes" || $makeHTMLemail eq "both" ) {print $html}
747     + #And drop it into a file
748     + if ($makeHTMLpage eq "yes") {
749     + my $filename = "mailstats.html";
750     + open(my $fh, '>', $filename) or die "Could not open file '$filename' $!";
751     + print $fh $html;
752     + close $fh;
753     + }
754     +
755     + }
756    
757     - #Close Senmdmail if it was opened
758     +
759     + #Close Sendmail if it was opened
760     if ( $opt{'mail'} ) {
761     select $oldfh;
762     close(SENDMAIL);
763     @@ -934,17 +1186,17 @@
764    
765     if ($cdb->get('mailstats'))
766     {
767     - my $interval = $cdb->get('mailstats')->prop('Interval') || 'daily';
768     + my $interval = $cdb->get('mailstats')->prop('Interval') || 'daily'; #"fortnightly"; #"daily";# #; TEMP!!
769     if ($interval eq "weekly") {
770     $secsininterval = 86400*7;
771     } elsif ($interval eq "fortnightly") {
772     $secsininterval = 86400*14;
773     } elsif ($interval eq "monthly") {
774     - $secsininterval = 86400;
775     + $secsininterval = 86400*30;
776     } elsif ($interval =~m/\d+/) {
777     $secsininterval = $interval*3600;
778     };
779     - my $base = $cdb->get('mailstats')->prop('Base') || 'Midnight';
780     + my $base = $cdb->get('mailstats')->prop('Base') || 'Midnight';
781     my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
782     localtime(time);
783     if ($base eq "Midnight"){
784     @@ -954,7 +1206,8 @@
785     } elsif ($base =~m/\d+/){
786     $sec=0;$min=0;$hour=$base;
787     };
788     - $time = timelocal($sec,$min,$hour,$mday,$mon,$year)
789     + #$mday="17"; #$mday="03"; #$mday="16"; #Temp!!
790     + $time = timelocal($sec,$min,$hour,$mday,$mon,$year);
791     }
792    
793     my $start = str2time( $startdate );
794     @@ -966,7 +1219,8 @@
795    
796     sub dbg {
797     my $msg = shift;
798     -
799     + my $time = scalar localtime;
800     + $msg = $time.":".$msg."\n";
801     if ( $opt{debug} ) {
802     print STDERR $msg;
803     }
804     @@ -1001,9 +1255,10 @@
805     }
806     my $i = keys %junkcount;
807     if ( $i > 0 ) {
808     - print("Junk Mails left in folder:\n");
809     - print("-------------------------\n");
810     - print("Count\tUser\n");
811     + print "\n\n";
812     + print("\nJunk Mails left in folder:\n");
813     + print("---------------------------\n\n");
814     + print("\nCount\tUser\n");
815     print("-------------------------\n");
816     foreach my $thisuser (
817     sort { $junkcount{$b} <=> $junkcount{$a} }
818     @@ -1033,7 +1288,7 @@
819     foreach my $virus (sort { $found_viruses{$b} <=> $found_viruses{$a} }
820     keys %found_viruses)
821     {
822     - if (index($virus,"Sanesecurity")!=-1){
823     + if (index($virus,"Sanesecurity") !=-1 || index($virus,"UNOFFICIAL") !=-1){
824     print "Rejected $found_viruses{$virus}\thttp://sane.mxuptime.com/s.aspx?id=$virus\n";
825     } else {
826     print "Rejected $found_viruses{$virus}\t$virus\n";
827     @@ -1061,6 +1316,7 @@
828     print "$found_qpcodes{$qpcode}\t".sprintf('%4.1f',$found_qpcodes{$qpcode}*100/$totalexamined)."%\t$qpcode\n" if $totalexamined;
829     }
830     print("---------------------------------------------\n\n");
831     + print "\n\n";
832     }
833    
834     sub show_Geoip_results
835     @@ -1078,38 +1334,40 @@
836     } else {
837     $percentthreshold = 0.5;
838     }
839     - print("Geoip results: (cutoff at $percentthreshold%) \n");
840     - print("---------------------------------\n");
841     - print("Country\tPercent\tCount\tRejected?\n");
842     - print("---------------------------------\n");
843     - foreach my $country (sort { $found_countries{$b} <=> $found_countries{$a} }
844     - keys %found_countries)
845     - {
846     - $percent = $found_countries{$country} * 100 / $total_countries
847     - if $total_countries;
848     - $totalpercent = $totalpercent + $percent;
849     - if (index($BadCountries, $country) != -1) {$reject = "*";} else { $reject = " ";}
850     - if ( $percent >= $percentthreshold ) {
851     - print "$country\t"
852     - . sprintf( '%4.1f', $percent )
853     - . "%\t$found_countries{$country}","\t$reject\n"
854     - if $total_countries;
855     - }
856     -
857     - }
858     - print("---------------------------------\n");
859     - my ($showtotals);
860     - if ($cdb->get('mailstats')){
861     - $showtotals = ((($cdb->get('mailstats')->prop("ShowLeagueTotals")|| 'yes')) eq "yes");
862     - } else {
863     - $showtotals = $true;
864     - }
865     -
866     - if ($showtotals){
867     - print "TOTALS\t$totalpercent%\t$total_countries\n";
868     - print("---------------------------------\n\n");
869     + if ($total_countries > 0) {
870     + print("Geoip results: (cutoff at $percentthreshold%) \n");
871     + print("---------------------------------\n");
872     + print("Country\tPercent\tCount\tRejected?\n");
873     + print("---------------------------------\n");
874     + foreach my $country (sort { $found_countries{$b} <=> $found_countries{$a} }
875     + keys %found_countries)
876     + {
877     + $percent = $found_countries{$country} * 100 / $total_countries
878     + if $total_countries;
879     + $totalpercent = $totalpercent + $percent;
880     + if (index($BadCountries, $country) != -1) {$reject = "*";} else { $reject = " ";}
881     + if ( $percent >= $percentthreshold ) {
882     + print "$country\t"
883     + . sprintf( '%4.1f', $percent )
884     + . "%\t$found_countries{$country}","\t$reject\n"
885     + if $total_countries;
886     + }
887     +
888     + }
889     + print("---------------------------------\n");
890     + my ($showtotals);
891     + if ($cdb->get('mailstats')){
892     + $showtotals = ((($cdb->get('mailstats')->prop("ShowLeagueTotals")|| 'yes')) eq "yes");
893     + } else {
894     + $showtotals = $true;
895     + }
896     +
897     + if ($showtotals){
898     + print "TOTALS\t".sprintf("%4.1f",$totalpercent)."%\t$total_countries\n";
899     + print("---------------------------------\n\n");
900     + }
901     + print "\n";
902     }
903     - print "\n";
904     }
905    
906     sub show_SARules_codes
907     @@ -1123,52 +1381,55 @@
908     my ($percentthreshold);
909     my ($defaultpercentthreshold);
910     my ($totalpercent) = 0;
911     -
912     - if ($totalexamined >0 && $sum_SARules*100/$totalexamined > $SARulethresholdPercent) {
913     - $defaultpercentthreshold = $maxcutoff
914     - } else {
915     - $defaultpercentthreshold = $mincutoff
916     - }
917     - if ($cdb->get('mailstats')){
918     - $percentthreshold = $cdb->get('mailstats')->prop("SARulePercentThreshold") || $defaultpercentthreshold;
919     - } else {
920     - $percentthreshold = $defaultpercentthreshold
921     - }
922     -
923     - print("Spamassassin Rules:(cutoff at ".sprintf('%4.1f',$percentthreshold)."%)\n");
924     - print("---------------------------------------------\n");
925     - print("Count\tPercent\tScore\t\t\n");
926     - print("---------------------------------------------\n");
927     - foreach my $SARule (sort { $found_SARules{$b}{'count'} <=> $found_SARules{$a}{'count'} }
928     - keys %found_SARules)
929     - {
930     - my $percent = $found_SARules{$SARule}{'count'} * 100 / $totalexamined
931     - if $totalexamined;
932     - #$totalpercent = $totalpercent + $percent;
933     - my $avehits = $found_SARules{$SARule}{'totalhits'} /
934     - $found_SARules{$SARule}{'count'}
935     - if $found_SARules{$SARule}{'count'};
936     - if ( $percent >= $percentthreshold ) {
937     - print "$found_SARules{$SARule}{'count'}\t"
938     - . sprintf( '%4.1f', $percent ) . "%\t"
939     - . sprintf( '%4.1f', $avehits )
940     - . "\t$SARule\n"
941     +
942     + if ($sum_SARules > 0){
943     +
944     + if ($totalexamined >0 && $sum_SARules*100/$totalexamined > $SARulethresholdPercent) {
945     + $defaultpercentthreshold = $maxcutoff
946     + } else {
947     + $defaultpercentthreshold = $mincutoff
948     + }
949     + if ($cdb->get('mailstats')){
950     + $percentthreshold = $cdb->get('mailstats')->prop("SARulePercentThreshold") || $defaultpercentthreshold;
951     + } else {
952     + $percentthreshold = $defaultpercentthreshold
953     + }
954     +
955     + print("Spamassassin Rules:(cutoff at ".sprintf('%4.1f',$percentthreshold)."%)\n");
956     + print("---------------------------------------------\n");
957     + print("Count\tPercent\tScore\t\t\n");
958     + print("---------------------------------------------\n");
959     + foreach my $SARule (sort { $found_SARules{$b}{'count'} <=> $found_SARules{$a}{'count'} }
960     + keys %found_SARules)
961     + {
962     + my $percent = $found_SARules{$SARule}{'count'} * 100 / $totalexamined
963     if $totalexamined;
964     -}
965     - }
966     - print("---------------------------------------------\n");
967     - my ($showtotals);
968     - if ($cdb->get('mailstats')){
969     - $showtotals = ((($cdb->get('mailstats')->prop("ShowLeagueTotals")|| 'yes')) eq "yes");
970     - } else {
971     - $showtotals = $true;
972     - }
973     -
974     - if ($showtotals){
975     - print "$totalexamined\t(TOTALS)\n";
976     + #$totalpercent = $totalpercent + $percent;
977     + my $avehits = $found_SARules{$SARule}{'totalhits'} /
978     + $found_SARules{$SARule}{'count'}
979     + if $found_SARules{$SARule}{'count'};
980     + if ( $percent >= $percentthreshold ) {
981     + print "$found_SARules{$SARule}{'count'}\t"
982     + . sprintf( '%4.1f', $percent ) . "%\t"
983     + . sprintf( '%4.1f', $avehits )
984     + . "\t$SARule\n"
985     + if $totalexamined;
986     + }
987     + }
988     print("---------------------------------------------\n");
989     + my ($showtotals);
990     + if ($cdb->get('mailstats')){
991     + $showtotals = ((($cdb->get('mailstats')->prop("ShowLeagueTotals")|| 'yes')) eq "yes");
992     + } else {
993     + $showtotals = $true;
994     + }
995     +
996     + if ($showtotals){
997     + print "$totalexamined\t(TOTALS)\n";
998     + print("---------------------------------------------\n");
999     + }
1000     + print "\n";
1001     }
1002     - print "\n";
1003    
1004    
1005     }
1006     @@ -1370,9 +1631,15 @@
1007     }
1008     $nhour++;
1009     }
1010     - $dbh->disconnect();
1011     - my $telapsed = time - $tstart;
1012     - print "Saved $reccount records in $telapsed sec.";
1013     + # and write out the log lines saved
1014     +
1015     + foreach my $logid (keys %LogLines){
1016     +
1017     + $dbh->do("INSERT INTO LogData (MailID,Sequence,LogStr) VALUES ('".$logid."','"."1','".$LogLines{$logid}."')");
1018     + }
1019     + $dbh->disconnect();
1020     + my $telapsed = time - $tstart;
1021     + print "Saved $reccount records in $telapsed sec.";
1022     }
1023    
1024     sub check_date_rec
1025     @@ -1439,5 +1706,3 @@
1026     my $daterec = $sth->fetchrow_hashref();
1027     $daterec->{"dateid"};
1028     }
1029     -
1030     -

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