#!/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(); } } }