397 lines
13 KiB
Perl
397 lines
13 KiB
Perl
|
#!/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 <UnJunkManager>';
|
||
|
|
||
|
# 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 = <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 .= "<HTML><HEAD><TITLE>UnJunk Manager</TITLE>";
|
||
|
|
||
|
if ( open( CSS, "$css_file" ) ) {
|
||
|
my @css = <CSS>;
|
||
|
$email_msg .= "<style type=\"text/css\">";
|
||
|
foreach my $cssline (@css) {
|
||
|
$email_msg .= $cssline;
|
||
|
}
|
||
|
$email_msg .= "</style>";
|
||
|
}
|
||
|
|
||
|
$email_msg .= "</HEAD>";
|
||
|
$email_msg
|
||
|
.= sprintf "<H1>Junk Emails Blocked for %s: %s</H1><br>",
|
||
|
$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<br>";
|
||
|
$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.<br>";
|
||
|
$email_msg
|
||
|
.= sprintf "<table border=\"1\" width=\"900\"><tr>";
|
||
|
$email_msg .= sprintf
|
||
|
'<td width="600" align="left" valign="top" bgcolor="#C0C0C0" colspan=4>';
|
||
|
$email_msg
|
||
|
.= sprintf
|
||
|
"<b><font face=\"Verdana\"><H2>Emails sent to %s</H2></font></b></td></tr>",
|
||
|
$real_name;
|
||
|
$email_msg .= sprintf
|
||
|
"<td bgcolor=\"#C0C0C0\"><font size=\"2\">Action</td><td bgcolor=\"#C0C0C0\"><font size=\"2\">From</td><td bgcolor=\"#C0C0C0\"><font size=\"2\">Subject</td><td bgcolor=\"#C0C0C0\"><font size=\"2\">Threat</td></tr>";
|
||
|
|
||
|
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
|
||
|
"<td><font size=\"2\"><a href=\"%s\">UnJunk</a></td>",
|
||
|
serialize_cmd($url);
|
||
|
$email_msg
|
||
|
.= sprintf
|
||
|
"<td><font size=\"2\">%-40.40s</td><td><font size=\"2\">%-50.50s</td><td><font size=\"2\">%s</td></tr>",
|
||
|
$email->{'from'}, $email->{'subject'},
|
||
|
$email->{'score'};
|
||
|
}
|
||
|
|
||
|
$email_msg .= sprintf "</table>";
|
||
|
|
||
|
# 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();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|