');
+}
+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 "