diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3aa8108 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.rpm +*.log +*spec-20* +*.tgz diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a80e142 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +# Makefile for source rpm: smeserver-unjunkmgr +# $Id: Makefile,v 1.1 2020/11/16 11:07:56 brianr Exp $ +NAME := smeserver-unjunkmgr +SPECFILE = $(firstword $(wildcard *.spec)) + +define find-makefile-common +for d in common ../common ../../common ; do if [ -f $$d/Makefile.common ] ; then if [ -f $$d/CVS/Root -a -w $$/Makefile.common ] ; then cd $$d ; cvs -Q update ; fi ; echo "$$d/Makefile.common" ; break ; fi ; done +endef + +MAKEFILE_COMMON := $(shell $(find-makefile-common)) + +ifeq ($(MAKEFILE_COMMON),) +# attept a checkout +define checkout-makefile-common +test -f CVS/Root && { cvs -Q -d $$(cat CVS/Root) checkout common && echo "common/Makefile.common" ; } || { echo "ERROR: I can't figure out how to checkout the 'common' module." ; exit -1 ; } >&2 +endef + +MAKEFILE_COMMON := $(shell $(checkout-makefile-common)) +endif + +include $(MAKEFILE_COMMON) diff --git a/README.md b/README.md index 5db2858..b05de76 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,16 @@ -# smeserver-unjunkmgr +# smeserver-unjunkmgr -SMEServer Koozali developed git repo for smeserver-unjunkmgr smecontribs \ No newline at end of file +SMEServer Koozali developed git repo for smeserver-unjunkmgr smecontribs + +## Wiki +
https://wiki.koozali.org/Unjunkmgr +
https://wiki.koozali.org/Sme-unjunkmgr + +## Bugzilla +Show list of outstanding bugs: [here](https://bugs.koozali.org/buglist.cgi?component=smeserver-unjunkmgr&product=SME%20Contribs&query_format=advanced&limit=0&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_status=CONFIRMED) + +## Description + +
*This description has been generated by an LLM AI system and cannot be relied on to be fully correct.* +*Once it has been checked, then this comment will be deleted* +
diff --git a/contriborbase b/contriborbase new file mode 100644 index 0000000..9b7fd51 --- /dev/null +++ b/contriborbase @@ -0,0 +1 @@ +contribs10 diff --git a/createlinks b/createlinks new file mode 100644 index 0000000..8b001d0 --- /dev/null +++ b/createlinks @@ -0,0 +1,63 @@ +#!/usr/bin/perl -w +use esmith::Build::CreateLinks qw(:all); +# our event specific for updating with yum without reboot +$event = 'smeserver-unjunkmgr-update'; +#add here the path to your templates needed to expand +#see the /etc/systemd/system-preset/49-koozali.preset should be present for systemd integration on all you yum update event + +foreach my $file (qw( + /etc/systemd/system-preset/49-koozali.preset + /etc/httpd/conf/httpd.conf + /etc/crontab +)) +{ + templates2events( $file, $event ); +} +#action needed in case we have a systemd unit +event_link('systemd-default', $event, '10'); +event_link('systemd-reload', $event, '50'); +#action specific to this package +#event_link('action', $event, '30'); +#services we need to restart +safe_symlink('restart',"root/etc/e-smith/events/$event/services2adjust/httpd-e-smith"); +#and Server Manager panel link +#panel_link('somefunction', 'manager'); + + + +use esmith::Build::CreateLinks qw(:all); + +# Start and stop links + +#-------------------------------------------------- +# functions for manager panel +#-------------------------------------------------- +my $panel = "manager"; + +panel_link( "unjunkmgr", $panel ); + +# Events links + +my $event = 'unjunkmgr-conf'; + +# Templates + +foreach (qw( + /etc/httpd/conf/httpd.conf + /etc/crontab + )) +{ + templates2events( "$_", qw( + unjunkmgr-conf + console-save + bootstrap-console-save + ) + ); +} + +# Actions + +# Services +safe_symlink( "sigusr1", + "root/etc/e-smith/events/$event/services2adjust/httpd-e-smith" ); + diff --git a/root/etc/e-smith/db/configuration/defaults/unjunkmgr/LocalOnly b/root/etc/e-smith/db/configuration/defaults/unjunkmgr/LocalOnly new file mode 100644 index 0000000..7cfab5b --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/unjunkmgr/LocalOnly @@ -0,0 +1 @@ +yes diff --git a/root/etc/e-smith/db/configuration/defaults/unjunkmgr/adminemails b/root/etc/e-smith/db/configuration/defaults/unjunkmgr/adminemails new file mode 100644 index 0000000..7ecb56e --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/unjunkmgr/adminemails @@ -0,0 +1 @@ +no diff --git a/root/etc/e-smith/db/configuration/defaults/unjunkmgr/enabled b/root/etc/e-smith/db/configuration/defaults/unjunkmgr/enabled new file mode 100644 index 0000000..7cfab5b --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/unjunkmgr/enabled @@ -0,0 +1 @@ +yes diff --git a/root/etc/e-smith/db/configuration/defaults/unjunkmgr/statsclient b/root/etc/e-smith/db/configuration/defaults/unjunkmgr/statsclient new file mode 100644 index 0000000..86981e6 --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/unjunkmgr/statsclient @@ -0,0 +1 @@ +enabled diff --git a/root/etc/e-smith/db/configuration/defaults/unjunkmgr/statsclienthost b/root/etc/e-smith/db/configuration/defaults/unjunkmgr/statsclienthost new file mode 100644 index 0000000..3ae541b --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/unjunkmgr/statsclienthost @@ -0,0 +1 @@ +central.swerts-knudsen.dk diff --git a/root/etc/e-smith/db/configuration/defaults/unjunkmgr/statsclientport b/root/etc/e-smith/db/configuration/defaults/unjunkmgr/statsclientport new file mode 100644 index 0000000..ecdb865 --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/unjunkmgr/statsclientport @@ -0,0 +1 @@ +1112 diff --git a/root/etc/e-smith/db/configuration/defaults/unjunkmgr/type b/root/etc/e-smith/db/configuration/defaults/unjunkmgr/type new file mode 100644 index 0000000..24e1098 --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/unjunkmgr/type @@ -0,0 +1 @@ +service diff --git a/root/etc/e-smith/db/configuration/defaults/unjunkmgr/useremails b/root/etc/e-smith/db/configuration/defaults/unjunkmgr/useremails new file mode 100644 index 0000000..7cfab5b --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/unjunkmgr/useremails @@ -0,0 +1 @@ +yes diff --git a/root/etc/e-smith/templates/etc/crontab/unjunk b/root/etc/e-smith/templates/etc/crontab/unjunk new file mode 100644 index 0000000..061b6be --- /dev/null +++ b/root/etc/e-smith/templates/etc/crontab/unjunk @@ -0,0 +1,28 @@ +{ + use esmith::ConfigDB; + + my $dbh = esmith::ConfigDB->open() + || die "Unable to open configuration dbase."; + my %sa_conf = $dbh->get('unjunkmgr')->props; + + while ( my ( $parameter, $value ) = each(%sa_conf) ) { + if ( $parameter eq 'enabled' ) { + $enabled = $value; + } + if ( $parameter eq 'useremails' ) { + $user_emails = $value; + } + } + + $OUT = ""; + if ( uc($enabled) eq 'YES' ) { + $OUT .= "# Schedule the UnJunk every 5 minutes\n"; + $OUT + .= "0-59/5 * * * * root /usr/local/unjunkmgr/spamchanger.pl -file=/tmp/unjunk.file\n"; + $OUT .= "\n"; + $OUT + .= "# Schedule the weekly Blocked Junk Summary to arrive at 1PM Friday\n"; + $OUT .= "0 13 * * 5 root /usr/local/unjunkmgr/spamreminder.pl\n"; + } +} + diff --git a/root/etc/e-smith/templates/etc/crontab/unjunkstats b/root/etc/e-smith/templates/etc/crontab/unjunkstats new file mode 100644 index 0000000..eb9aa3f --- /dev/null +++ b/root/etc/e-smith/templates/etc/crontab/unjunkstats @@ -0,0 +1,33 @@ +{ + use esmith::ConfigDB; + + my $dbh = esmith::ConfigDB->open() + || die "Unable to open configuration dbase."; + my %sa_conf = $dbh->get('unjunkmgr')->props; + + while ( my ( $parameter, $value ) = each(%sa_conf) ) { + if ( $parameter eq 'enabled' ) { + $enabled = $value; + } + } + + $OUT = ""; + if ( uc($enabled) eq 'YES' ) { + + my $random_hour = 5 + int( rand(50) ); + $OUT .= "# Hourly Antivirus stats (random minute)\n"; + $OUT .= $random_hour . " * * * * root /usr/local/unjunkmgr/spamfilter-statsclient.pl viruswebstats hour\n"; + $OUT .= "# The last 24 hour Antivirus stats (random minute)\n"; + $OUT .= $random_hour . " * * * * root /usr/local/unjunkmgr/spamfilter-statsclient.pl viruswebstats day\n"; + $OUT .= "# Hourly Spam stats (random minute)\n"; + $OUT .= $random_hour . " * * * * root /usr/local/unjunkmgr/spamfilter-statsclient.pl spamwebstats hour\n"; + $OUT .= "# The last 24 hour Spam stats (random minute)\n"; + $OUT .= $random_hour . " * * * * root /usr/local/unjunkmgr/spamfilter-statsclient.pl spamwebstats day\n"; + + $OUT .= "\n"; + $OUT .= "# Now get the MRTG stats run\n"; + $OUT .= $random_hour . ' * * * * root /usr/bin/mrtg /etc/mrtg/unjunkstats.cfg >/dev/null 2>&1'; + $OUT .= "\n"; + } +} + diff --git a/root/etc/e-smith/templates/etc/httpd/conf/httpd.conf/98unjunkmgr b/root/etc/e-smith/templates/etc/httpd/conf/httpd.conf/98unjunkmgr new file mode 100644 index 0000000..6924eb4 --- /dev/null +++ b/root/etc/e-smith/templates/etc/httpd/conf/httpd.conf/98unjunkmgr @@ -0,0 +1,46 @@ +{ + use esmith::NetworksDB; + use esmith::ConfigDB; + + my $ndb = esmith::NetworksDB->open_ro(); + + $localAccess = $ndb->local_access_spec(); + $localAccess =~ s#/255\.255\.255\.255##g; + + my $dbh = esmith::ConfigDB->open() || die "Unable to open configuration dbase."; + my %sa_conf = $dbh->get('unjunkmgr')->props; + + while (my ($parameter,$value) = each(%sa_conf)) { + if ($parameter eq 'LocalOnly') { + $local = $value; + } + if ($parameter eq 'enabled') { + $enabled = $value; + } + + } + + $OUT = ""; + if (not (uc($enabled) eq 'YES')) { + return; + } + $OUT .= "# This is the location of the UnJunk web interface\n"; + + $OUT .= "AddHandler cgi-script .pl\n"; + $OUT .= "Alias /unjunkmgr /usr/local/unjunkmgr\n"; + $OUT .= "\n"; + $OUT .= " Options +FollowSymLinks +ExecCGI\n"; + $OUT .= " AllowOverride All\n"; + $OUT .= " \n"; + $OUT .= " SetHandler \"proxy:unix:/var/run/php-fpm/php$version.sock|fcgi://localhost\"\n"; + $OUT .= " \n"; + + if (uc($local) eq 'YES') { + $OUT .= " Require ip $localAccess\n"; + } else { + $OUT .= " Require all granted\n"; + } + + $OUT .= "\n"; +} + diff --git a/root/etc/e-smith/web/functions/unjunkmgr b/root/etc/e-smith/web/functions/unjunkmgr new file mode 100644 index 0000000..1352e83 --- /dev/null +++ b/root/etc/e-smith/web/functions/unjunkmgr @@ -0,0 +1,28 @@ +#!/usr/bin/perl +#---------------------------------------------------------------------- +# heading : Administration +# description : Unjunkmgr +# navigation : 4000 4200 +#---------------------------------------------------------------------- + +use strict; +use CGI ':all'; +use CGI::Carp qw(fatalsToBrowser); + +BEGIN { + $ENV{'PATH'} = '/bin:/usr/bin:/sbin'; + $ENV{'SHELL'} = '/bin/bash'; + delete $ENV{'ENV'}; +} + +my $q = new CGI; +my $content + = "0; url=https://" . $ENV{'HTTP_X_FORWARDED_HOST'} . "/unjunkmgr"; +$q->default_dtd('-//W3C//DTD XHTML 1.0 Transitional//EN'); + +print $q->header('text/html'); +print $q->start_html( + -head => meta( { -http_equiv => 'refresh', -content => $content } ) ); + +print $q->end_html; + diff --git a/root/etc/mrtg/unjunkstats.cfg b/root/etc/mrtg/unjunkstats.cfg new file mode 100644 index 0000000..f2b5d26 --- /dev/null +++ b/root/etc/mrtg/unjunkstats.cfg @@ -0,0 +1,40 @@ +workdir: /usr/local/unjunkmgr/ + +interval: 60 +#--------------------------------------------------------------- + +Target[spam]: `/usr/local/unjunkmgr/spamfilter-statsclient.pl spam hour` +AddHead[spam]: +MaxBytes[spam]: 100000 +Options[spam]: gauge,nopercent,dorelpercent,nobanner,nolegend +Title[spam]: Spam Statistics +PageTop[spam]:

Email Spam Statistics

+WithPeak[spam]: dwmy +YLegend[spam]: messages +ShortLegend[spam]: messages +LegendI[spam]:  Spam: +LegendO[spam]:  Scanned: +Legend1[spam]:  Spam: +Legend2[spam]:  Scanned: +Legend3[spam]: +Legend4[spam]: +Legend5[spam]:  Spam Percent: +Colours[spam]: RED#FF0000,DARK GREEN#23D016,WHITE#FFFFFF,WHITE#FFFFFF,RED#FF0000 + +Target[virus]: `/usr/local/unjunkmgr/spamfilter-statsclient.pl virus hour` +AddHead[virus]: +MaxBytes[virus]: 100000 +Options[virus]: gauge,nopercent,dorelpercent,nobanner,nolegend +Title[virus]: Virus Statistics +PageTop[virus]:

Email Virus Statistics

+WithPeak[virus]: dwmy +YLegend[virus]: messages +ShortLegend[virus]: messages +LegendI[virus]:  Virus: +LegendO[virus]:  Scanned: +Legend1[virus]:  Virus: +Legend2[virus]:  Scanned: +Legend3[virus]: +Legend4[virus]: +Legend5[virus]:  Virus Percent: +Colours[virus]: RED#FF0000,DARK GREEN#23D016,WHITE#FFFFFF,WHITE#FFFFFF,RED#FF0000 diff --git a/root/usr/local/unjunkmgr/index.php b/root/usr/local/unjunkmgr/index.php new file mode 100644 index 0000000..7381804 --- /dev/null +++ b/root/usr/local/unjunkmgr/index.php @@ -0,0 +1,443 @@ + + + + + + + Corporate Virus and Spam Statistics + + + + + + + +
+ + + + + + + + +
+

Corporate Spam and Virus Statistics


+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Hourly Spam Statistics over last 24 hours

+

+
+

Hourly Virus Statistics over last 24 hours

+

+
 
 
+ + + +
 
+ + + +
 
+
+
+ + + +

'); + print ('Statistics database not found'); + print (''); + return; + } + //print ('get data virus_log'); + flock($fd, 1); // get a shared lock + $linecount = 0; + while (!feof($fd)) { + $line = fgets($fd); + if ($line) { + + // printf("line = %s\n",$line); + // $keys = split ("\|", $line) ; + $keys = preg_split("/\|/", $line); + + $list = array(); + foreach ($keys as $item) { + // $key = split ("\=", $item); + $key = preg_split("/\=/", $item); + // printf("key = %s\n",$key); + if ($key[0] == 'SCANNED') { + $scanned = $key[1]; + } + if ($key[0] == 'BAD') { + $bad = $key[1]; + } + if ($key[0] == 'NAME') { + $name = $key[1]; + } elseif ($key[0] == 'COUNT') { + $count = $key[1]; + $list['count'] = $count; + } elseif ($key[0] == 'LATEST') { + $latest = $key[1]; + $list['latest'] = $latest; + } + } + + if ($name) { + $list = array( + 'name' => $name, + 'count' => $count, + 'latest' => $latest + ); + $print_list[$linecount++] = $list; + } + } + } + + flock($fd, 3); // release the lock + fclose($fd); + + // Now print out results if they exist + if (isset($print_list)) { + $sorted_list = msort($print_list, "count", false); + } + + // print_r($sorted_list); + print (''); + // This will always be table - used to have 'oldstyle' + if ($style == 'table') { + print (''); + print (''); + print (''); + print (''); + print (''); + print (''); + print (''); + } + + if (isset($sorted_list)) { + // This will print emails if there are any + if ($linecount > 0) { + $linecount = 0; + + print ('

'); + print (''); + print (''); + print (''); + print (''); + + foreach ($sorted_list as $item) { + + print (''); + print (''); + print (''); + + $linecount++; + + // Only show top 10... + if ($linecount >= 10) { + break; + } + } + } + } // End isset + print ('
'); + print ('

'); + + printf("Virus Threats last %s ", $text); + print ('

'); + printf("Emails Scanned for Virus"); + print ('

'); + printf("%s", number_format($scanned, 0, 0, '.')); + print ('

'); + printf("Infected with Virus"); + print ('

'); + if ($scanned > 0) printf("%s (%.2f %%)", number_format($bad, 0, 2, '.') , number_format(($bad / $scanned) * 100, 2, '.', '')); + print ('

'); + print ('

'); + // Always table ?? + if ($style == 'table') { + print ('Top Blocked Virus Threats'); + } else { + printf("Top Spammed Emails last %s", $text); + } + print ('

'); + // Always table ?? + if ($style == 'table') { + print ('Name'); + } else { + print ('Email address'); + } + print ('
'); + print ('Count'); + print ('
'); + printf("%s", $item['name']); + print ('
'); + printf("%s", $item['count']); + print ('
'); + return; +} + +function read_and_print_spam_log($log_filename, $text, $style) +{ + + if (!file_exists($log_filename) || !is_readable($log_filename) || !$fd = fopen($log_filename, "r")) { + print ('

'); + print ('Statistics database not found'); + print (''); + return; + } + //print ('get data spam_log'); + flock($fd, 1); // get a shared lock + $linecount = 0; + while (!feof($fd)) { + $line = fgets($fd); + if ($line) { + // printf("line = %s\n",$line); + // $keys = split ("\|", $line) ; + $keys = preg_split("/\|/", $line); + $list = array(); + foreach ($keys as $item) { + // $key = split ("\=", $item); + $key = preg_split("/\=/", $item); + // printf("key = %s\n",$key); + if ($key[0] == 'SCANNED') { + $scanned = $key[1]; + } + if ($key[0] == 'REJECT') { + $reject = $key[1]; + } + if ($key[0] == 'TAGGED') { + $tagged = $key[1]; + } + } + } + } + + flock($fd, 3); // release the lock + fclose($fd); + + //if ($style == 'oldstyle') { + // print(''); + // + // print(''); + // print(''); + // print(''); + // + // print(''); + // print(''); + // print(''); + // + // + // print(''); + // print(''); + // + // print(''); + // print(''); + // print('
'); + // print('

'); + // printf("Scanned for Spam"); + // print('

'); + // printf("%s",number_format($scanned,0,0,'.')); + // print('

'); + // printf("Good"); + // print('

'); + // $good_emails = $scanned-($reject+$tagged); + // printf("%s (%.2d %%)",number_format($good_emails,0,0,'.'),($good_emails/$scanned)*100); + // print('

'); + // printf("Rejected as Spam"); + // print('

'); + // printf("%s (%.2d %%)",number_format($reject,0,0,'.'),($reject/$scanned)*100); + // print('

'); + // printf("Accepted but Tagged as Spam"); + // print('

'); + // printf("%s (%.2d %%)",number_format($tagged,0,0,'.'),($tagged/$scanned)*100); + // print('

'); + // + //} else { + $good_emails = $scanned - ($reject + $tagged); + + // what if no emails went through?? + if ($scanned == 0) { + + include "jpgraph/jpgraph_canvas.php"; + include "jpgraph/jpgraph_canvtools.php"; + + $g = new CanvasGraph(450, 260, 'auto'); + $scale = new CanvasScale($g); + $scale->Set(0, 27, 0, 53); + $g->SetMargin(1, 2, 1, 2); + $g->SetColor('white'); + $g->SetMarginColor("black"); + $g->InitFrame(); + + $t = new CanvasRectangleText(); + + $t->SetFillColor(''); + $t->SetFontColor('black'); + $t->SetColor(''); + $t->SetShadow(''); + $t->SetFont(FF_ARIAL, FS_BOLD, 14); + $t->Set('No emails scanned for spam last hour', 8, 1, 8); + $t->Stroke($g->img, $scale); + + $g->Stroke(); + return; + } + + // Some data + $data = array( + $good_emails, + $tagged, + $reject + ); + + // A new pie graph + $graph = new PieGraph(450, 260, 'auto'); + + // Setup title + $graph->title->Set("Spam Statistics " . $text); + $graph->title->SetFont(FF_ARIAL, FS_BOLD, 14); + $graph->title->SetMargin(3); // Add a little bit more margin from the top + $graph->footer->center->Set("Generated " . date('l jS \of F Y h:i:s A')); + + // Create the pie plot + $p1 = new PiePlotC($data); + // $p1->value->SetColor("navy"); + $p1->SetCenter(0.35, 0.50); + + $p1->SetLegends(array( + "Good (" . number_format($good_emails, 0, 0, '.') . ")", + "Tagged (" . number_format($tagged, 0, 0, '.') . ")", + "Rejected (" . number_format($reject, 0, 0, '.') . ")", + )); + + $graph->legend->Pos(0.10, 0.30); + $graph->legend->SetLayout(LEGEND_VERT); + + $p1->SetSliceColors(array( + "green", + "orange", + "red" + )); + + // Create the extra text box to show scanned emails number + // $txt = new Text(); + // $txt->Set("Total scanned " . number_format($scanned,0,0,'.')); + // $txt->ParagraphAlign('right'); + // $txt->Show(); + // $txt->SetFont(FF_VERDANA,FS_NORMAL,8); + // $txt->Pos(0.82,0.5,'center','bottom'); + // $txt->SetBox('gray9','black','gray9',0,2); + // $txt->SetShadow(); + // $graph->AddText($txt); + // $p1->Explode(array(0,15,15,25,15)); + // Set size of pie + $p1->SetSize(0.32); + + // Label font and color setup + $p1->value->SetFont(FF_VERDANA, FS_BOLD, 10); + $p1->value->SetColor('black'); + + // Setup the title on the center circle + $p1->midtitle->Set("Total of\n" . number_format($scanned, 0, 0, '.') . "\nscanned"); + $p1->midtitle->SetFont(FF_VERDANA, FS_NORMAL, 10); + + // Set color for mid circle + $p1->SetMidColor('yellow'); + + // Use percentage values in the legends values (This is also the default) + $p1->SetLabelType(PIE_VALUE_PER); + // $p1->SetLabelPos(0.8); + // Add plot to pie graph + $graph->Add($p1); + + // .. and send the image on it's merry way to the browser + $graph->Stroke(); + + //} + return; +} + +function msort($array, $id = "id", $sort_ascending = true) +{ + $temp_array = array(); + while (count($array) > 0) { + $lowest_id = 0; + $index = 0; + foreach ($array as $item) { + if (isset($item[$id])) { + if ($array[$lowest_id][$id]) { + if ($item[$id] < $array[$lowest_id][$id]) { + $lowest_id = $index; + } + } + } + $index++; + } + $temp_array[] = $array[$lowest_id]; + $array = array_merge(array_slice($array, 0, $lowest_id) , array_slice($array, $lowest_id + 1)); + } + if ($sort_ascending) { + return $temp_array; + } else { + return array_reverse($temp_array); + } +} + +?> + + diff --git a/root/usr/local/unjunkmgr/spamchanger.pl b/root/usr/local/unjunkmgr/spamchanger.pl new file mode 100755 index 0000000..c6afc8a --- /dev/null +++ b/root/usr/local/unjunkmgr/spamchanger.pl @@ -0,0 +1,127 @@ +#!/usr/bin/perl + +############################################################################# +# +# This script provides a weekly overview of email stored in the junkmail +# folder and allows for unjunking. When an email is being unjunked the +# bayesian filter in SpamAssassin is trained as ham. +# +# This script has been developed +# by Jesper Knudsen at http://sme.swerts-knudsen.dk +# +# Revision History: +# +# August 24, 2008: Initial version +############################################################################# + +use Cwd "realpath"; +use File::Basename; +use English; +use strict; +use CGI qw(:standard); +use File::Copy; +use File::Spec; + +## If we are called as Perl Script then we are in "Move files" mode. The commandline then has the path +## to the unjunk file in the option file= + +if ( defined param('-file') ) { + printf( "UnJunk File = %s\n", param('-file') ) + if ( defined( param('-verbose') ) ); + + my $LogFile = param('-file'); + if ( not open( UNJUNK, "+< $LogFile" ) ) { + exit 0; + } + my @logfile = ; + + # Now truncate the file... + truncate( UNJUNK, 0 ); + close(UNJUNK); + + foreach my $email (@logfile) { + my ( $user, $file ) = $email =~ m/^USER:([^:]+):(.*)$/; + if ( defined($user) and defined($email) ) { + printf( "UnJunking file %s for user %s\n", $file, $user ) + if ( defined( param('-verbose') ) ); + + # Now Move the file to /home/e-smith/files/users//Maildir/cur + my ( $name, $path, $suffix ) + = File::Basename::fileparse( $file, '\..*' ); + my $new_location + = sprintf( "/home/e-smith/files/users/%s/Maildir/cur/%s%s", + $user, $name, $suffix ); + + # If UnJunking a file lets learn as ham + my $result = `su - root -c "/usr/bin/sa-learn --ham $file"`; + if ( defined( param('-verbose') ) ) { + printf( "Result of sa-learn: %s\n", $result ); + } + + printf( "New location = %s\n", $new_location ) + if ( defined( param('-verbose') ) ); + + if ( not rename( $file, $new_location ) ) { + printf( "Move was not successfull : %s\n", $! ) + if ( defined( param('-verbose') ) ); + } + } + else { + printf("Incorrect UnJunk file!!\n") + if ( defined( param('-verbose') ) ); + } + } + exit 0; +} + +#################################################################### +## If we end here we are in the web mode and should output HTML data +#################################################################### + +print header; #<-- prints the http header using the CGI module +my $user = param('user'); +my $from = param('from'); +my $subject = param('subject'); +my $email = param('email'); + +# Print header info +print "UnJunk Manager\n"; +printf + "\n"; +print "\n"; + +if ( not defined($user) or not defined($email) ) { + print "

Incorrect UnJunk Link


\n"; + exit 0; +} + +my $email_msg .= sprintf ""; +$email_msg .= sprintf + '", + $user; + +$email_msg .= sprintf + ""; +$email_msg + .= sprintf + "", + $from, $subject; +$email_msg .= sprintf "
'; +$email_msg + .= sprintf + "

The following email has been scheduled for UnJunk

FromSubject
%-30.30s%-40.40s
"; + +printf $email_msg; + +my $LogFile = "/tmp/unjunk.file"; + +if ( not open( WRITE, ">> $LogFile" ) ) { + printf('

Unable to write to the UnJunk schedular file

'); +} +else { + printf WRITE "USER:%s:%s\n", $user, $email; + close(WRITE); +} + +exit 0; + diff --git a/root/usr/local/unjunkmgr/spamfilter-statsclient.pl b/root/usr/local/unjunkmgr/spamfilter-statsclient.pl new file mode 100755 index 0000000..497d98c --- /dev/null +++ b/root/usr/local/unjunkmgr/spamfilter-statsclient.pl @@ -0,0 +1,853 @@ +#!/usr/bin/perl -w +############################################################################# +# +# This script provides daily SpamFilter statistics. +# +# Default configuration is: +# /sbin/e-smith/db config setprop unjunkmgr statsclient enabled +# /sbin/e-smith/db config setprop unjunkmgr statsclientport 1111 +# /sbin/e-smith/db config setprop unjunkmgr statsclienthost central.swerts-knudsen.dk + +# This script has been developed +# by Jesper Knudsen at http://sme.swerts-knudsen.dk +# +# Revision History: +# +# August 15, 2008: Initial version + +############################################################################# +# internal modules (part of core perl distribution) +use Getopt::Long; +use Pod::Usage; +use POSIX qw/strftime floor/; +use Time::Local; +use Date::Manip; +use strict; +use Cwd "realpath"; +use File::Basename; +use English; +use CGI qw(:standard); +use File::Copy; +use File::Spec; +use Data::Dumper; +use IO::Socket::INET; +use Carp; + +use esmith::db; +use esmith::ConfigDB; +use esmith::AccountsDB; + +#Behaving oddly, but seems to work... +if ( ( $#ARGV != 1 ) + or ( $ARGV[0] !~ /spam|virus|webstats/ ) + or ( ( $ARGV[1] !~ /hour|day/ ) ) ) +{ + die "\nUsage: $0 [spam|virus|viruswebstats|spamwebstats] [hour|day]\n"; +} + +my $type = $ARGV[0]; +my $duration = $ARGV[1]; +my $master_host_name; +my $master_host_port; + +# Temp. log location +my $log_location = '/usr/local/unjunkmgr/'; + +# Initialize timezone +my $timezone = `date +%z`; +Date_Init("TZ=$timezone"); + +my ( $checked, $found ) = Corporate_Statistics($duration); + +if ( not( $type eq 'viruswebstats' or $type eq 'spamwebstats' ) ) { + printf( "%s\n%s\n\n", $found, $checked ); +} + +exit 1; + +######################################## +# Process parms # +######################################## +sub parse_arg { + my $startdate = shift; + my $enddate = shift; + + my $secsinday = 86400; + my $time = 0; + + my $start = UnixDate( $startdate, "%s" ); + my $end = UnixDate( $enddate, "%s" ); + + if ( !$start && !$end ) { + $end = time; + $start = $end - $secsinday; + return ( $start, $end ); + } + + if ( !$start ) { + $start = $end - $secsinday; + return ( $start, $end ); + } + + if ( !$end ) { + $end = $start + $secsinday; + return ( $start, $end ); + } + if ( $start > $end ) { + return ( $end, $start ); + } + + return ( $start, $end ); + +} + +sub Corporate_Statistics { + + my $duration = shift; + + my $release_version; + + my $enabled; + my $statsclient; + + my $dbh = esmith::ConfigDB->open() + || die "Unable to open configuration dbase."; + my %sa_conf = $dbh->get('unjunkmgr')->props; + + while ( my ( $parameter, $value ) = each(%sa_conf) ) { + if ( $parameter eq 'enabled' ) { + $enabled = $value; + } + if ( $parameter eq 'statsclient' ) { + $statsclient = $value; + } + if ( $parameter eq 'statsclientport' ) { + $master_host_port = $value; + } + if ( $parameter eq 'statsclienthost' ) { + $master_host_name = $value; + } + } + + if ( not( uc($enabled) eq 'YES' ) ) { + return ( 0, 0 ); + } + + %sa_conf = $dbh->get('sysconfig')->props; + + while ( my ( $parameter, $value ) = each(%sa_conf) ) { + if ( $parameter eq 'ReleaseVersion' ) { + $release_version = $value; + } + } + + my $SME_version; + + # printf("SME Release Version %s\n",$release_version); + #if ( $release_version =~ m/[78]/ ) { + # $SME_version = 7; + #} + #else { + # $SME_version = 6; + #} + + $SME_version = 10; + + my $spam_reject_level; + my $spam_tag_level; + + # Now get the spamassassin configuration + #if ( $SME_version == 6 ) { + # + # my $sa_dbase = '/home/e-smith/spamassassin_V3'; + # my $sa_dbh = esmith::ConfigDB->open($sa_dbase) + # || die "Unable to open spamassassin configuration dbase."; + # my %sa_conf = $sa_dbh->get('conf.global')->props; + # + # my $parameter = ""; + # my $value = ""; + # while ( ( $parameter, $value ) = each(%sa_conf) ) { + # if ( $parameter eq 'required_hits' ) { + # $spam_tag_level = $value; + # } + # if ( $parameter eq 'auto_delete' ) { + # $spam_reject_level = $value; + # } + # if ( $parameter eq 'statsclientport' ) { + # $master_host_port = $value; + # } + # if ( $parameter eq 'statsclienthost' ) { + # $master_host_name = $value; + # } + # if ( $parameter eq 'statsclient' ) { + # if ( not( $value eq 'enabled' ) ) { + # + # # disabled - get out of here. + # return ( 0, 0 ); + # } + # } + # + # } + #} + #elsif ( $SME_version == 7 ) { + $spam_reject_level + = esmith::ConfigDB->open_ro->get('spamassassin')->prop('RejectLevel'); + $spam_tag_level + = esmith::ConfigDB->open_ro->get('spamassassin')->prop('TagLevel'); + + #} + + my $logfile; + my $spamstring; + my $checkstring; + my $virusstring; + my $virusfoundstring; + my $spamclean; + + # efficiency; don't rebuild the (constant) hash every loop iteration + my %month_list = ( + 'Jan' => 0, + 'Feb' => 1, + 'Mar' => 2, + 'Apr' => 3, + 'May' => 4, + 'Jun' => 5, + 'Jul' => 6, + 'Aug' => 7, + 'Sep' => 8, + 'Oct' => 9, + 'Nov' => 10, + 'Dec' => 11 + ); + + #if ( $SME_version == 7 ) { + + # SME 7x + $logfile = '/var/log/qpsmtpd/current'; + $spamstring = 'check_spam: Yes'; + $checkstring = 'check_spam:'; + $virusstring = 'clamscan results'; + + #} + #else { + # if ( $type eq 'virus' or $type eq 'viruswebstats' ) { + # $logfile = '/var/log/amavis-ng/amavis-ng.log'; + # } + # else { + # $logfile = '/var/log/maillog'; + # } + # + # $spamstring = 'identified spam'; + # $spamclean = 'clean message'; + # $checkstring = 'spamd: result:'; + # $virusfoundstring = 'CLAMD found:'; + # $virusstring = 'Starting AMaViS'; + #} + + my $virus_list; + my $virus_count = 0; + my $virus_scan_count = 0; + + my $spam_tagged = 0; + my $spam_rejected = 0; + my $spam_checked = 0; + my $spam_email_list; + + my $YEAR = ( localtime(time) )[5]; # this is years since 1900 + my $start; + my $end; + + if ( $duration eq 'day' ) { + ( $start, $end ) = parse_arg( "yesterday", "" ); + } + else { + ( $start, $end ) = parse_arg( "1 hour ago", "" ); + } + + my $spams_found = 0; + + # Its faster to pipe through grep for the right string.... + if ( $type eq 'spam' or $type eq 'spamwebstats' ) { + + #if ( $SME_version == 7 ) { + +# if (not open(LOG,"/usr/local/bin/tai64nlocal < $logfile | /bin/grep $checkstring|")) { + if ( not open( LOG, "/usr/local/bin/tai64nlocal < $logfile |" ) ) { + printf( + "Error opening logfile (%s) for corporate spam reports - %s\n", + $logfile, $! ); + return; + } + + #} + #else { + # if ( not open( LOG, "/usr/local/bin/tai64nlocal < $logfile|" ) ) { + # printf( + # "Error opening logfile (%s) for corporate spam reports - %s\n", + # $logfile, $! ); + # return; + # } + #} + } + else { + if ( not open( LOG, "/usr/local/bin/tai64nlocal < $logfile |" ) ) { + +# if (not open(LOG,"/usr/local/bin/tai64nlocal < $logfile | /bin/grep -A 1 \'$virusstring\'|")) { + printf( + "Error opening logfile (%s) for corporate spam reports - %s\n", + $logfile, $! ); + return; + } + } + + my $virusmatch = 0; + + foreach my $line () { + + $line =~ s/[ \t\n]*$//; + + # printf("Line = %s\n",$line); + my ( $year, $month, $day, $hour, $min, $sec, $rest ); + my ( $abstime, $abshour ); + + #if ( $SME_version == 7 ) { + + ( $year, $month, $day, $hour, $min, $sec, $rest ) + = $line + =~ m/^([^-]+)-([^-]+)-([^ ]+) ([^:]+):([^:]+):([^.]+).(.*)/; + + if ( not defined($year) ) { + + # printf("Match = %s-%s-%s - %s:%s:%s\n",$year,$month,$day,$hour,$min,$sec); + next; + } + + # Convert to absolute time + $abstime = timelocal( $sec, $min, $hour, $day, $month - 1, $YEAR ); + $abshour = floor( $abstime / 3600 ); # Hours since the epoch + + #} + #else { + # + # # SME 6x format of log + # ( $month, $day, $hour, $min, $sec, $rest ) + # = $line =~ m/^([^ ]+) ([^ ]+) ([^:]+):([^:]+):([^ ]+) (.*)/; + # if ( not defined($month) ) { + # + # # printf("Match = %s-%s - %s:%s:%s\n",$month,$day,$hour,$min,$sec); + # next; + # } + # + # # Convert to absolute time + # $abstime + # = timelocal( $sec, $min, $hour, $day, $month_list{$month}, + # $YEAR ); + # $abshour = floor( $abstime / 3600 ); # Hours since the epoch + #} + + #If date specified, only process lines matching date + next if ( $abstime < $start ); + + # We can assume that logs are chronological + last if ( $abstime > $end ); + + if ($virusmatch) { + my ($virusname) = $line =~ m/\]\: (.*)/; + + # Make sure its found and not OK + if ($virusname) { + $virusname =~ s/[ \t\n]*$//; + $virus_count++; + + $virus_list->{$virusname}{'date'} = $abstime; + $virus_list->{$virusname}{'count'}++; + + # printf("Found Virus = \"%s\"\n",$virusname); + } + $virusmatch = 0; + } + + # Count Virus Results + my $virusname; + + # I am not sure about this - I thkink it can go + #if ( $SME_version == 6 and $line =~ m/$virusfoundstring/ ) { + # + # # Have to get the name from next line - set flag + # # printf("Match...\n"); + # $virusmatch = 1; + # next; + #} + + if ( $line =~ m/$virusstring/ ) { + $virus_scan_count++; + + #if ( $SME_version == 7 ) { + ($virusname) = $line =~ m/$virusstring\: (.*)/; + + # Make sure its found and not OK + if ( defined($virusname) and not $virusname =~ m/: OK/ ) { + + # Lets not count various errors from Clam... + if ( lc($virusname) =~ m/warning/ + or lc($virusname) =~ m/error/ ) + { + next; + } + + # Get rid of trailing spaces... + $virusname =~ s/[ \t\n]*$//; + $virus_count++; + + $virus_list->{$virusname}{'date'} = $abstime; + $virus_list->{$virusname}{'count'}++; + + # printf("Found Virus = \"%s\"\n",$virusname); + } + + #} + } + + # Now count checked Spam + if ( $line =~ m/$checkstring/ ) { + $spam_checked++; + } + + # Find emails hit by spam + if ( $line =~ m/logging::logterse plugin:/ ) { + + # printf("Found logterse entry = %s\n",$line); + + my @logentry = split( /\t/, $line ); + + # printf("Found entry6 = %s\n",$logentry[6]); + # printf("Found entry7 = %s\n",$logentry[7]); + # printf("Found entry8 = %s\n",$logentry[8]); + + # Check if it got to SA scanning + if ( defined( $logentry[8] ) ) { + + # Now get email address if it was spam/rejected + if ( $logentry[8] =~ m/^Yes/ or $logentry[6] =~ m/^90/ ) { + my @emails = split( /,/, $logentry[4] ); + + # printf("Reject Reason = %s - %s\n",$logentry[8],$logentry[4]); + + # Count rejected emails that didn't reach max score but was + # rejected before queuing. + if ( not( $logentry[8] =~ m/^Yes/ ) + and $logentry[6] =~ m/^90/ ) + { + $spam_rejected++; + $spams_found++; + $spam_checked++; + } + + foreach my $email (@emails) { + $email =~ s/[<>]//g; + + # printf("Email = \"%s\"\n",$email); + + $spam_email_list->{$email}++; + } + } + + } + } + + # Match identified Spam + if ( $line =~ m/$spamstring/ ) { + $spams_found++; + + # printf("Line = %s\n",$line); + my ( $score, $taglevel, $rest ); + if ( $SME_version == 6 ) { + ( $score, $taglevel, $rest ) + = $line =~ m/identified spam \(([^\/]+)\/([^\)]+)\)(.*)/; + } + elsif ( $SME_version == 7 ) { + ( $score, $taglevel, $rest ) + = $line =~ m/hits=([^\,]+)\, required=([^\,]+)\,(.*)/; + } + if ( defined($score) ) { + + # printf("Score = %s (%s)\n",$score,$taglevel); + if ( $score > $spam_tag_level + and $score < $spam_reject_level ) + { + $spam_tagged++; + } + elsif ( $score > $spam_reject_level ) { + $spam_rejected++; + } + else { +# this can only happen if configuration were changed in the last period - discard them.. +# printf("Configuration changed in last period - discarding item.....\n"); + } + } + } + + } + +# printf ("Spam Emails found = %s out of %s emails\n",$spams_found,$spam_checks); + close(LOG); + + # printf(Dumper($spam_email_list)); + + if ( $type eq 'viruswebstats' ) { + my $LogFile = sprintf( "%s/spamfilterstats.virus.%s", $log_location, + $duration ); + if ( open( WRITE, "+> $LogFile" ) ) { + + # printf WRITE ("START=%s|",$start); + # printf WRITE ("END=%s|",$end); + printf WRITE ( "SMEVERSION=%s|", $release_version ); + + printf WRITE ( "SCANNED=%s|", $virus_scan_count ); + printf WRITE ( "BAD=%s|", $virus_count ); + + foreach my $virus ( keys %{$virus_list} ) { + + # First write virus name + printf WRITE ( "NAME=%s|", $virus ); + printf WRITE ( "COUNT=%s|", + $virus_list->{$virus}->{'count'} ); + printf WRITE ( "LATEST=%s|", + $virus_list->{$virus}->{'date'} ); + printf WRITE ("\n"); + } + close(WRITE); + } + + # Now send the statistics to the Statistice Server + if ( $duration eq 'hour' and uc($statsclient) eq 'ENABLED' ) { + ConnectAndSend( CreateStatisticsMsg('virus') ); + } + + } + if ( $type eq 'spamwebstats' ) { + my $LogFile = sprintf( "%s/spamfilterstats.spam.%s", $log_location, + $duration ); + if ( open( WRITE, "+> $LogFile" ) ) { + + # printf WRITE ("START=%s|",$start); + # printf WRITE ("END=%s|",$end); + printf WRITE ( "SMEVERSION=%s|", $release_version ); + + printf WRITE ( "SCANNED=%s|", $spam_checked ); + printf WRITE ( "REJECT=%s|", $spam_rejected ); + printf WRITE ( "TAGGED=%s|", $spam_tagged ); + + foreach my $email ( keys %{$spam_email_list} ) { + + # First write virus name + printf WRITE ( "NAME=%s|", $email ); + printf WRITE ( "COUNT=%s|", $spam_email_list->{$email} ); + printf WRITE ("\n"); + } + + close(WRITE); + } + + # Now send the statistics to the Statistice Server + if ( $duration eq 'hour' and uc($statsclient) eq 'ENABLED' ) { + ConnectAndSend( CreateStatisticsMsg('spam') ); + } + + } + + if ( $type eq 'virus' ) { + return ( $virus_scan_count, $virus_count ); + } + elsif ( $type eq 'spam' ) { + return ( $spam_checked, $spams_found ); + } + else { + return ( 0, 0, 0 ); + } +} + +sub CreateStatisticsMsg { + + my $stattype = shift; + + # General + my $duration = 'hour'; + my $encryption = 'NO'; + my $compression = 'NO'; + my $version = 1; + + # Prepare Header + my $msg + = sprintf( + "COMMAND=LOGGING\nTYPE=%s\nCOMPRESSION=%s\nENCRYPTION=%s\nVERSION=%s\n", + uc($stattype), $compression, $encryption, $version ); + + $msg .= 'DATA='; + + # printf("MSGHDR=%s\n",$msg); + + my $LogFile = sprintf( "%s/spamfilterstats.%s.%s", + $log_location, $stattype, $duration ); + + if ( open( LOG, "< $LogFile" ) ) { + foreach my $line () { + $msg .= $line; + } + close(LOG); + } + + # printf("MSG=%s",$msg); + + return $msg; +} + +sub ConnectAndSend() { + + my $command = shift; + + my $return_value = undef; + my $retry_attempts = 3; + + my $socket + = job_start_client( $master_host_name, $master_host_port, 120 ); + + if ( not defined($socket) ) { + return $return_value; + } + +TRY_AGAIN: + +# printf("Initiating Contact with statistics server \"%s\" (%s)\n",$master_host_name,$retry_attempts); + + my $message = sprintf( "ACCOUNT=%s\|\n", GetMacAddress() ); + + # printf("Tx Msg = %s",$message); + job_send_data( $socket, $message ); + + my $msg = <$socket>; + + if ( not defined($msg) ) { + if ( $retry_attempts-- > 0 ) { + + # Wait a little while before trying again if system seems ill.... + sleep( int( rand(10) ) ); + + # debug(5,"Need a retry - %s retries left,",$retry_attempts); + goto TRY_AGAIN; + } + else { + job_stop_client($socket); + + # printf("Timed out connecting to master"); + return $return_value; + } + } + + my $deserialized = deserialize_cmd($msg); + $msg = $deserialized; + + $msg =~ s/[\r\n]*$//; + + # printf("Rx Msg = %s\n",$msg); + + my ( $status, $rest ) = $msg =~ m/STATUS=([^\|]+)\|(.*)/; + + if ( not( $status eq 'OK' ) ) { + job_stop_client($socket); + + # printf("Received not OK message from master (%s)",$msg); + return $return_value; + } + + $message = sprintf( "%s\n", $command ); + + # printf("Message = %s",$message); + job_send_data( $socket, $message ); + + $msg = <$socket>; + + if ( not defined($msg) ) { + job_stop_client($socket); + + # printf("Timed out connecting to master to send command"); + return $return_value; + } + + $deserialized = deserialize_cmd($msg); + $msg = $deserialized; + + $msg =~ s/[\r\n]*$//; + + # printf("Rx Cmd Msg = %s\n",$msg); + + ( $status, $rest ) = $msg =~ m/STATUS=([^\|]+)\|(.*)/; + + if ( not( $status eq 'OK' ) ) { + job_stop_client($socket); + + # printf("Received not OK message from master (%s)\n",$msg); + return $return_value; + } + + job_stop_client($socket); + + # printf("Statistics uploaded successfully\n"); + return 1; + +} + +sub GetMacAddress { + + # This could fail + my $ifconfigin = `/sbin/ifconfig eth0`; + my @ifeth = split( /\n/, $ifconfigin ); + my ( $junk, $macaddr ) = split( / HWaddr /, $ifeth[0] ); + + # remove the ":" and spaces + $macaddr =~ s/[\: ]//g; + + # printf "MAC=\"$macaddr\"\n"; + return $macaddr; +} + +################################################################################ +################################################################################ + +sub job_start_client { + + my $master_host_name = shift; + my $master_host_port = shift; + my $timeout = shift; + my $silent = shift; + + $timeout = 60 if ( not defined($timeout) ); + $silent = 0 if ( not defined($silent) ); + + my $arp_refresh = 1; + if ($arp_refresh) { + + # Suspecting ARP cache timeout and subsequent connect failures as cause for + # failures with "No Route to Host" error message + # Hack it by massaging the ARP into clients ARP table with 3 pings. + +# debug(2,"JOB: Doing ping to host %s to refresh ARP cache...",$master_host_name); + my $system_cmd = sprintf( "ping -c 3 %s > %s 2>&1", + $master_host_name, "/dev/null" ); + my $execute = `$system_cmd`; + if ( $? == -1 ) { + + # debug(2,"ARP ping failed to exec: %s", $!); + } + elsif ( $? >> 8 ) { + + # debug(2,"ARP ping exited with value %d\n", $? >> 8); + } + } + + my $socket = IO::Socket::INET->new( + PeerAddr => $master_host_name, + PeerPort => $master_host_port, + Proto => "tcp", + Type => SOCK_STREAM, + Timeout => $timeout + ); + + if ( not defined($socket) ) { + if ( not $silent ) { + +# printf("Couldn't connect to Job Master = %s (TCP Port=%s) : %s\n",$master_host_name ,$master_host_port,$!); + } + return undef; + } + else { + # Set timeout for socket + $socket->timeout($timeout); + } + return $socket; +} + +################################################################################ +################################################################################ + +sub job_stop_client { + + my $client = shift; + + close($client) if ( defined($client) ); +} + +################################################################################ +# The function serializes a complex datastructure into a safe and compact +# single line string. +################################################################################ + +sub serialize_cmd { + my $cmd = shift; + + # replace all newlines, CR and % with CGI-style encoded sequences + $cmd =~ s/([%\r\n])/sprintf("%%%02X", ord($1))/ge; + + return $cmd; +} + +################################################################################ +# The function deserializes the input string into a complex datastructure. +################################################################################ +sub deserialize_cmd { + my $serialized = shift; + + # convert back escapes to the original chars + $serialized =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/ge; + + return $serialized; + +} + +################################################################################ +################################################################################ + +sub job_send_data { + + my $socket = shift; + my $message = shift; + + my $status = 0; + if ( defined($socket) ) { + my $msg = serialize_cmd($message); + my $r = print $socket "$msg\n"; + + # printf("Socket write error msg \'%s\' - %s",$msg,$!) if (!defined($r)); + $status = 1; + } + return $status; +} + +################################################################################ +################################################################################ + +sub job_receive_data { + + my $socket = shift; + my $timeout = shift; + + $timeout = 1 if ( not defined($timeout) ); + + my $msg = undef; + if ( defined($socket) ) { + + # Set timeout for socket + $socket->timeout($timeout); + + # Now wait timeout time for someone to send something + my $client = $socket->accept(); + if ( defined($client) ) { + $msg = <$client>; + my $deserialized = deserialize_cmd($msg); + return ( $client, $deserialized ); + } + } + return undef; +} + + diff --git a/root/usr/local/unjunkmgr/spamreminder.pl b/root/usr/local/unjunkmgr/spamreminder.pl new file mode 100755 index 0000000..691a5de --- /dev/null +++ b/root/usr/local/unjunkmgr/spamreminder.pl @@ -0,0 +1,396 @@ +#!/usr/bin/perl -w + +############################################################################# +# +# This script provides weekly overview of email stored in the junkmail +# folder and allows for unjunking. When an email is being unjunked the +# baysian filter in SpamAssassin is trained as ham. +# +# This script has been developed +# by Jesper Knudsen at http://sme.swerts-knudsen.dk +# +# Revision History: +# +# August 24, 2008: Initial version +############################################################################# + +# internal modules (part of core perl distribution) +use Getopt::Long; +use Pod::Usage; +use POSIX qw/strftime floor/; +use Time::Local; +use Date::Manip; +use strict; +use MIME::Lite; + +use esmith::db; +use esmith::ConfigDB; +use esmith::AccountsDB; + +############################################################################# +# Configuration +############################################################################# + +# The address which will be copied on the weekly summary emails (default: none) +my $admin_email_addr = ''; + +my $domain_name = esmith::ConfigDB->open()->get('DomainName')->value; +my $unjunkhost = $domain_name; +my $enabled; +my $useremails = 'yes'; + +my $db = esmith::ConfigDB->open() + || die "Unable to open configuration dbase."; +my %db_conf = $db->get('unjunkmgr')->props; + +while ( my ( $parameter, $value ) = each(%db_conf) ) { + if ( $parameter eq 'enabled' ) { + $enabled = $value; + } + if ( $parameter eq 'adminemails' and uc($value) eq 'YES' ) { + $admin_email_addr = 'admin'; + } + if ( $parameter eq 'unjunkhost' ) { + $unjunkhost = $value; + } + if ( $parameter eq 'useremails' ) { + $useremails = $value; + } + +} + +# The address from which the weekly summary comes from +my $spamfilter_addr = 'Admin Junk Summary '; + +# Which stylesheet to use for the summary email +my $css_file = '/usr/local/unjunkmgr/unjunkmgr.css'; + +# Debug enabled? - will send all reports to $admin_email_addr +my $debug = 0; +############################################################################# + +# Parameters for the Junkmail Summary functionality + +my $root_url = sprintf( "https://%s/unjunkmgr", $unjunkhost ); + +my $path = "/home/e-smith/files/users/"; + +my $end_path_cur = "/Maildir/.junkmail/cur"; +my $end_path_new = "/Maildir/.junkmail/new"; + +my $sa_dbase = '/home/e-smith/db/configuration'; +my $dbh = esmith::ConfigDB->open($sa_dbase) + || die "Unable to open spamassassin configuration dbase."; +my %sa_conf = $dbh->get('spamassassin')->props; + +my $disabled = 0; +my $days_to_keep = 5; +my $spam_mark = 7; +my $spam_discard = 10; + +my $parameter = ""; +my $value = ""; +while ( ( $parameter, $value ) = each(%sa_conf) ) { + if ( $parameter eq 'status' and not $value eq 'enabled' ) { + $disabled = 1; + } + if ( $parameter eq 'MessageRetentionTime' ) { + $days_to_keep = $value; + } + if ( $parameter eq 'TagLevel' ) { + $spam_mark = $value; + } + if ( $parameter eq 'RejectLevel' ) { + $spam_discard = $value; + } +} + +#printf("Enabled = %s\n",$disabled); +#printf("Retention = %s\n",$days_to_keep); +#printf("TagLevel = %s\n",$spam_mark); +#printf("RejectLevel = %s\n",$spam_discard); + +if ( uc($useremails) eq 'YES' or lc($admin_email_addr) eq 'admin' ) { + Junkmail_Reminder(); +} + +#All done +exit 0; + +############################################################################# + # Subroutines ############################################################### +############################################################################# + +######################################## + # Process parms # +######################################## +sub parse_arg { + my $startdate = shift; + my $enddate = shift; + my $secsinday = 86400; + my $time = 0; + my $start = UnixDate( $startdate, "%s" ); + my $end = UnixDate( $enddate, "%s" ); + + if ( !$start && !$end ) { + $end = time; + $start = $end - $secsinday; + return ( $start, $end ); + } + + if ( !$start ) { + $start = $end - $secsinday; + return ( $start, $end ); + } + + if ( !$end ) { + $end = $start + $secsinday; + return ( $start, $end ); + } + + if ( $start > $end ) { + return ( $end, $start ); + } + + return ( $start, $end ); + +} + +sub get_email_details { + my $entry = shift; + my $score; + my $spam; + my $spamlimit; + my $spam_string = 'Unknown'; + my $subject = 'Unknown'; + my $from = 'Unknown'; + my $to = 'Unknown'; + + open( ORIGINAL, "$entry" ); #OPEN FILE FOR READING + my @original = ; #READ FILE INTO AN ARRAY + + #PROCESS THE ARRAY + + foreach my $x (@original) { + if ( $x =~ m/^Subject:/ ) { + ($subject) = $x =~ m/^Subject: (.*)$/; + if ( not defined($subject) ) { + $subject = 'Unknown'; + } + else { + $subject =~ s/^[ \t]//g; + } + } + + if ( $x =~ m/^To:/ ) { + ($to) + = $x + =~ m/([a-zA-Z0-9._\%\+\-]+\@[a-zA-Z0-9\.\-]+\.[a-zA-Z]{2,4})/; + if ( not defined($to) ) { + $to = 'Unknown'; + } + } + if ( $x =~ m/^From:/ ) { + ($from) + = $x + =~ m/([a-zA-Z0-9._\%\+\-]+\@[a-zA-Z0-9\.\-]+\.[a-zA-Z]{2,4})/; + if ( not defined($from) ) { + $from = 'Unknown'; + } + } + + if ( $x =~ m/^X-Spam-Status:/ ) { + ( $spam, $score, $spamlimit ) + = $x + =~ m/^X-Spam-Status: ([^\,]+)\, hits=([^\ ]+)\ required=(.*)/; + if ( defined($spamlimit) ) { + if ( $spam eq 'Yes' + and $score > $spamlimit + and $score < $spam_discard ) + { + $spam_string = sprintf( "Likely Spam (%s)", $score ); + } + else { + $spam_string = sprintf( "Spam (%s)", $score ); + } + } + else { + $spam_string = 'Unknown'; + } + } + + } + close(ORIGINAL); + + return ( $subject, $from, $to, $spam_string ); +} + +################################################################################ + # The function serializes a complex datastructure into a safe and compact + # single line string. +################################################################################ + +sub serialize_cmd { + my $cmd = shift; + + my $out = $cmd; + + # replace all newlines, CR and % with CGI-style encoded sequences + $out =~ s/([\;%\r\n])/sprintf("%%%02X", ord($1))/ge; + + return $out; +} + +sub Junkmail_Reminder { + + my $found; + my $entry; + my $subject; + my $to; + my $from; + my $real_name; + my $name; + my $to_email; + my $score; + my $spamlist; + my $spamcount = 0; + + my ( $oneweekago, $noew ) = parse_arg( "last week", "" ); + + my $adb = esmith::AccountsDB->open_ro() + || die "Couldnt' open AccountsDB\n"; + my @accounts; + push @accounts, $adb->users; + + foreach my $account (@accounts) { + + $name = $account->key; + $spamcount = 0; + $spamlist = undef; + $found = 0; + + $real_name = sprintf( "%s %s", + $account->prop('FirstName'), + $account->prop('LastName') ); + if ( $debug == 1 ) { + printf( "User : %s (%s)\n", $real_name, $name ); + } + + my @junkmail_dirs; + push @junkmail_dirs, "$path$name$end_path_new"; + push @junkmail_dirs, "$path$name$end_path_cur"; + + foreach my $junkmail_dir (@junkmail_dirs) { + + # Now get the content list for the directory. + opendir( QDIR, "$junkmail_dir" ) + or die "Couldn't read directory $junkmail_dir"; + + my @sorted_dates = map $_->[1], sort { $b->[0] <=> $a->[0] } + map -f "$junkmail_dir/$_" ? [ ( stat _ )[9], $_ ] : (), + readdir(QDIR); + closedir(QDIR); + + foreach $entry (@sorted_dates) { + next if $entry =~ /^\./; + $entry = $junkmail_dir . '/' . $entry; + my $modifytime = ( stat($entry) )[9]; + + # Now only report new emails.. + if ( -f $entry and ( $modifytime > $oneweekago ) ) { + $found++; + ( $subject, $from, $to, $score ) + = get_email_details($entry); + + # printf("Found Spam email: %s with score %s (%s)",$from, $score,$spamcount); + $spamlist->[$spamcount]{'user'} = $name; + $spamlist->[$spamcount]{'realname'} = $real_name; + $spamlist->[$spamcount]{'file'} = $entry; + $spamlist->[$spamcount]{'subject'} = $subject; + $spamlist->[$spamcount]{'from'} = $from; + $spamlist->[$spamcount]{'to'} = $to; + $spamlist->[ $spamcount++ ]{'score'} = $score; + } + } + } + + if ( $spamcount > 0 and not $disabled ) { + + my $email_msg; + + $email_msg .= "UnJunk Manager"; + + if ( open( CSS, "$css_file" ) ) { + my @css = ; + $email_msg .= ""; + } + + $email_msg .= ""; + $email_msg + .= sprintf "

Junk Emails Blocked for %s: %s


", + $real_name, $spamcount; + $email_msg .= sprintf + "The emails listed below have been placed in your personal Junk Box since your last Junk Box Summary and will be
"; + $email_msg .= sprintf + "deleted after $days_to_keep days. To receive any of these messages, click UnJunk and the message will be delivered to your inbox.
"; + $email_msg + .= sprintf ""; + $email_msg .= sprintf + '", + $real_name; + $email_msg .= sprintf + ""; + + foreach my $email ( @{$spamlist} ) { + my $spamchanger = $root_url . '/spamchanger.pl'; + my $url + = sprintf "%s?user=%s&email=%s&subject=%s&from=%s", + $spamchanger, $email->{'user'}, $email->{'file'}, + $email->{'subject'}, $email->{'from'}; + $email_msg + .= sprintf + "", + serialize_cmd($url); + $email_msg + .= sprintf + "", + $email->{'from'}, $email->{'subject'}, + $email->{'score'}; + } + + $email_msg .= sprintf "
'; + $email_msg + .= sprintf + "

Emails sent to %s

ActionFromSubjectThreat
UnJunk%-40.40s%-50.50s%s
"; + + # create a new MIME Lite based email + my $msg = MIME::Lite->new( + Subject => + sprintf( + "Summary of junk emails blocked - %s Junk Emails Blocked", + $spamcount ), + From => $spamfilter_addr, + To => uc($useremails) eq 'YES' + ? $name + : $admin_email_addr, + + # To => $debug == 1 ? $admin_email_addr : $name, + # No cc email if debug.. + # Cc => $admin_email_addr eq 'admin' ? $admin_email_addr : '', + Cc => ( + $admin_email_addr eq 'admin' + and uc($useremails) eq 'YES' + ) ? $admin_email_addr : '', + Type => 'text/html', + Data => $email_msg + ); + + $msg->send(); + } + } +} + diff --git a/root/usr/local/unjunkmgr/unjunkmgr.css b/root/usr/local/unjunkmgr/unjunkmgr.css new file mode 100644 index 0000000..0b869ab --- /dev/null +++ b/root/usr/local/unjunkmgr/unjunkmgr.css @@ -0,0 +1,242 @@ +BODY{ + background: #e6ecf8; + color: #000000; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 12px; + height: 100%; + margin: 5px; + padding: 0px; + text-align: left; + width: 100%; +} + +a { color: #000000 } + +DIV.menu_bg{ z-index: 0; position: fixed; top: 0px; left: 0px; width: 162px; height: 100%; background-color: #eeeeee; } +DIV.titre{ z-index: 1; position: absolute; top: 0px; left: 0px; width: 100%; height: 68px; background: #cccccc; margin: 0px 0px 1px 0px; } +DIV.t_logo{ z-index: 1; position: absolute; top: 0px; left: 0px; } +DIV.t_barre_orange{ z-index: 1; position: absolute; top: 41px; left: 0px; width: 100%; background: #ffc50a; border-color: #888888; border-style: solid; border-width: 1px 0px 1px 0px; } +DIV.t_email{ z-index: 1; position: absolute; top: 50px; left: 0px; width: 240px; } +DIV.t_liens{ z-index: 1; position: absolute; top: 50px; right: 0px; height: 14px; text-align: right; } +DIV.t_barre_grise{ z-index: 1; position: absolute; top: 64px; left: 0px; width: 100%; background: #888888;} +DIV.menu{ z-index: 1; position: absolute; top: 68px; left: 0px; width: 160px; padding: 0px 0px 0px 2px; } +DIV.page{ z-index: 0; position: absolute; top: 68px; left: 162px; bottom: -1px; } +DIV.contenu{ padding: 0px 20px 5px 5px; } + +DIV.vert{ border-color: #006600; border-width: 1px; border-style: solid; color: #006600; padding: 2px; margin: 0px 20px 0px 20px; } +DIV.rouge{ border-color: #ff0000; border-width: 1px; border-style: solid; color: #ff0000; padding: 2px; margin: 0px 20px 0px 20px; } +DIV.vert a{ color: #006600; font-weight: bold; } +DIV.rouge a{ color: #ff0000; font-weight: bold; } + +FIELDSET{ + background-color: #eeeeee; +} + +FONT.copyleft{ + color: #777777; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 11px; +} + +FORM{ + margin-top: 2px; + margin-bottom: 2px; +} + +H1{ + color: #333333; + font-size: 18px; + margin-bottom: 4px; + margin-top: 12px; + font-family: Verdana, Arial, Helvetica, sans-serif; +} + +H2{ + color: #333333; + font-size: 14px; + margin-bottom: 3px; + margin-top: 12px; + font-family: Verdana, Arial, Helvetica, sans-serif; +} + +H3{ + color: #333333; + font-size: 12px; +/* margin-bottom: 3px; + margin-top: 12px; */ + font-family: Verdana, Arial, Helvetica, sans-serif; +} + +H4{ + color: #333333; + font-size: 12px; + margin-bottom: 3px; + margin-top: 12px; + font-family: Verdana, Arial, Helvetica, sans-serif; +} + +H5{ + color: #333333; + font-size: 10px; +/* margin-bottom: 3px; + margin-top: 12px; */ + font-family: Verdana, Arial, Helvetica, sans-serif; +} + + + +HR{ + color: #666666; + background-color: #666666; + height: 1px; + width: 80%; + border: 0px; +} + +HR.copyleft{ + color: #dddddd; + background-color: #dddddd; + height: 1px; + width: 100%; + border: 0; +} + +OL, UL, LI{ + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 12px; + font-weight: normal; +} + +UL{ + list-style-type: circle; +} + +P{ + margin-bottom: 2px; + margin-top: 8px; +} + +PRE{ + margin: 3px 0px 3px 0px; + padding: 2px; +} + +TABLE{ + border-collapse : collapse; + empty-cells: show; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 12px; + background-color: #e6ecf8; +} + +TABLE.bordure{ + border: 2px solid #cccccc; + margin: 5px 5px 5px 2px; +} + +TD{ + font-size: 11px; + margin: 0px; + padding: 0px; +} + +TD.header{ + border: 1px solid #cccccc; + padding: 3px 2px 3px 2px; +} + +TH{ + font-size: 11px; + vertical-align: middle; +} + +TH.bordure{ + background-color: #dddddd; + border: 1px solid #cccccc; + padding: 3px 2px 3px 2px; + text-align: center; +} + +TH.presse{ + text-align: right; + vertical-align: top; +} + +TH.bouton{ + border: 1px solid #dddddd; + background-color: #eeeeee; + padding: 4px; + text-align: right; +} + +TR{ + vertical-align: top; +} + +.cmdline{ + color: #00ffff; +} + +.xterm{ + background-color: #000000; + border: 3px inset #999999; + color: #ffffff; + font-size: 11px; +} + +.section{ + font-size: 11px; + font-weight: bold; + padding-bottom: 2px; + padding-top: 8px; +} + +a1:link, a1:visited, a1:hover, a1:active{ + border: 1px solid #000000; +# border: 1px solid #cccccc; + color: #000000; +# margin: 0px; +# padding: 0px 0px 0px 10px; + text-decoration: none; +} + +a:hover{ + background: #25bd25; + border-color: #C0C0C0; +} + +a:active{ + background: #000000; + border-color: #000000; + color: #ffffff; +} + +a.choix, a.selection{ + border: 1px solid #eeeeee; + color: #000000; + display: block; +# margin: 0px 0px 0px 0px; +# padding: 0px 10px 2px 10px; + text-align: left; + text-decoration: none; +} + +a.choix:hover, a.selection:hover{ + background: #cccccc; + border-color: #888888; +} + +a.choix:active, a.selection:active{ + background: #000000; + border-color: #000000; + color: #ffffff; +} + +a.selection:link, a.selection:visited, a.selection:active, a.selection:hover{ + border-color: #888888; +} + +a.selection:hover{ + background: #ffffff; + border-color: #888888; +} diff --git a/smeserver-unjunkmgr.spec b/smeserver-unjunkmgr.spec new file mode 100644 index 0000000..8fffcac --- /dev/null +++ b/smeserver-unjunkmgr.spec @@ -0,0 +1,136 @@ +# $Id: smeserver-unjunkmgr.spec,v 1.10 2022/08/02 01:49:38 jpp Exp $ +# Authority: jesperknudsen +# Name: Jesper Knudsen + +Summary: SME Server UnJunk Manager +%define name smeserver-unjunkmgr +%define version 3.1 +%define release 7 +Name: %{name} +Version: %{version} +Release: %{release}%{?dist} +License: GPL +Group: SME Server/addon +Source: %{name}-%{version}.tar.xz +#Patch1: smeserver-unjunkmgr-bzxxxxx-fix-stuff.patch + +BuildRoot: /var/tmp/%{name}-%{version}-%{release}-buildroot +BuildArch: noarch +BuildRequires: e-smith-devtools +Requires: smeserver-release >= 10.0 +Requires: e-smith-apache >= 2.6.0-19 +Requires: perl-MIME-Lite => 3 +Requires: perl-DateManip => 5.40 +Requires: mrtg +Requires: perl-Email-Date-Format +Requires: jpgraph +Obsoletes: sme-unjunkmgr + +%changelog +* Sat Sep 07 2024 cvs2git.sh aka Brian Read 3.1-7.sme +- Roll up patches and move to git repo [SME: 12338] + +* Sat Sep 07 2024 BogusDateBot +- Eliminated rpmbuild "bogus date" warnings due to inconsistent weekday, + by assuming the date is correct and changing the weekday. + +* Mon Aug 01 2022 Jean-Philippe Pialasse 3.1-6.sme +- update to httpd 2.4 access syntax [SME: 12063] + thanks to Vasarhelyi Zsolt + +* Sat Nov 06 2021 Jean-Philippe Pialasse 3.1-5.sme +- fix unjunk links failing [SME: 11737] + +* Tue Jun 01 2021 Jean-Philippe Pialasse 3.1-4.sme +- fix missing curly bracket and indentation [SME: 11178] + +* Thu Apr 01 2021 Brian Read 3.1-3.sme +- Add Update event to createlinks [SME: 11178] + +* Thu Apr 01 2021 BogusDateBot +- Eliminated rpmbuild "bogus date" warnings due to inconsistent weekday, + by assuming the date is correct and changing the weekday. + +* Tue Nov 24 2020 John Crisp 3.1-2.sme +- Add cleaned up and fixed index.php that I forgot + +* Tue Nov 24 2020 John Crisp 3.1-1.sme +- Remove jpgraph to its own rpm +- cleanup of various files +- remove references to old SME versions + +* Wed Nov 18 2020 Brian Read 3.0-2.sme +- Fix SME Version check in perl stats prgram [SME: 11178] + +* Wed Nov 18 2020 John Crisp 3.0-1.sme +- Completely update jpgraph to 4.3.4 +- clean up various files and incorporate patches [SME: 11178] + +* Tue Nov 17 2020 Brian Read 2.1-5.sme +- Update jpgraph and fix php references in httpd.conf [SME: 11178] + +* Mon Nov 16 2020 Brian Read 2.1-4.sme +- Initial import to SME10 tree [SME: 11178] + +* Fri Apr 4 2014 stephane de Labrusse 2.1-3.sme +- added a unjunkmgr menu entry in the server-manager [sme : 8307] + +* Thu Jun 27 2013 JP Pialasse 2.1-2.sme +- Add Update event to createlinks some php deprecated and PHP warning +- patch1 + +* Wed Jun 26 2013 JP Pialasse 2.1-1.sme +- update JpGraph to fix deprecated messages +- added perl-Email-Date-Format requirement for sme8 as per wiki +- patch ttfpatch + +* Tue Jun 25 2013 JP Pialasse 2.0-4.sme +- Add Update event to createlinks db default [SME: 7701] +- patch0 +- added createlink in spec file + +* Wed Jun 02 2010 Shad L. Lords 2.0-1.sme +- Clean up spec for importing into CVS + +* Thu Jun 4 2009 Jesper Knudsen 1.1.3-1 +- Added top spammed email stats in GUI and updated CSS to show nicely in IE +- Fixed problem using unjunkhost confg parameter + +* Fri Apr 3 2009 Jesper Knudsen 1.1.0-2 +- Fixed error in spamreminder.pl line 112 + +* Thu Apr 2 2009 Jesper Knudsen 1.1.0-1 +- Updated to submit statistics more randomly over the hour to avoid congestion on central +- Add pie charts on stats +- Add more configuration options + +* Thu Aug 28 2008 Jesper Knudsen 1.0.1-1 +- Updated spamreminder.pl to allow non-exisitng Subject fields +- Made dependency of MRTG for RPM +- Make sure to cleanup during un-install + +* Fri Aug 22 2008 Jesper Knudsen 1.0.0-1 +- First release + +%description +SME Server UnJunk Manager which scan the individual users junkmail folders and +sends out a weekly summary where they can unjunk the emails + +%prep +%setup +#%patch1 -p1 + +%build +perl createlinks + +%install +rm -rf $RPM_BUILD_ROOT +(cd root ; find . -depth -print | cpio -dump $RPM_BUILD_ROOT) +/sbin/e-smith/genfilelist $RPM_BUILD_ROOT > %{name}-%{version}-filelist +echo "%doc " >> %{name}-%{version}-filelist + +%clean +rm -rf $RPM_BUILD_ROOT + +%files -f %{name}-%{version}-filelist +%defattr(-,root,root)