SUSPECTED SPAM NOTIFICATIONS do not get sent to Recipient when Sender is blank or <>
Posted: 22 Sep 2016 00:00
While trying to test SUSPECTED SPAM NOTIFICATIONS actions as per viewtopic.php?f=14&t=1541 I noticed that SSNs do not get sent to Recipients when the Sender is blank or <>.
The problem is in /usr/lib/MailScanner/MailScanner/CustomFunctions/CustomAction.pm:
Since we are about to send a message to the RECIPIENT, these lines should not be here:
Full modified code for CustomAction.pm:
The problem is in /usr/lib/MailScanner/MailScanner/CustomFunctions/CustomAction.pm:
Code: Select all
sub HandleSpamNotify {
my($message) = @_;
my($from,$to,$subject,$date,$spamreport,$hostname,$day,$month,$year);
my($emailmsg, $line, $messagefh, $filename, $localpostmaster, $id);
my($postmastername);
my($token);
$from = $message->{from};
# Don't ever send a message to "" or "<>"
return if $from eq "" || $from eq "<>";
# Do we want to send the sender a warning at all?
# If nosenderprecedence is set to non-blank and contains this
# message precedence header, then just return.
my(@preclist, $prec, $precedence, $header);
@preclist = split(" ",
lc(MailScanner::Config::Value('nosenderprecedence', $message)));
$precedence = "";
foreach $header (@{$message->{headers}}) {
$precedence = lc($1) if $header =~ /^precedence:\s+(\S+)/i;
}
if (@preclist && $precedence ne "") {
foreach $prec (@preclist) {
if ($precedence eq $prec) {
MailScanner::Log::InfoLog("Skipping sender of precedence %s",
$precedence);
return;
}
}
}
Code: Select all
# Don't ever send a message to "" or "<>"
return if $from eq "" || $from eq "<>";
# Do we want to send the sender a warning at all?
# If nosenderprecedence is set to non-blank and contains this
# message precedence header, then just return.
my(@preclist, $prec, $precedence, $header);
@preclist = split(" ",
lc(MailScanner::Config::Value('nosenderprecedence', $message)));
$precedence = "";
foreach $header (@{$message->{headers}}) {
$precedence = lc($1) if $header =~ /^precedence:\s+(\S+)/i;
}
if (@preclist && $precedence ne "") {
foreach $prec (@preclist) {
if ($precedence eq $prec) {
MailScanner::Log::InfoLog("Skipping sender of precedence %s",
$precedence);
return;
}
}
Code: Select all
#
# CustomAction.pm
# Version 20140309
# +--------------------------------------------------------------------+
# Copyright (C) 2012~2013 http://www.efa-project.org
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# +--------------------------------------------------------------------+
package MailScanner::CustomConfig;
use strict 'vars';
use strict 'refs';
no strict 'subs'; # Allow bare words for parameter %'s
use vars qw($VERSION);
# The package version, both in 1.23 style *and* usable by MakeMaker:
$VERSION = substr q$Revision: 1.0 $, 10;
use DBI;
#
# CustomAction for EFA Token Generation
#
sub CustomAction {
my($message,$yes_or_no,$param) = @_;
if ($param =~ /^spam$/ ) {
return EFASpamNotify($message);
}
if ($param =~ /^nonspam$/ ) {
return EFANonSpam($message);
}
}
#
# EFA Non Spam Modification Token Generation,
#
sub EFANonSpam {
my($message) = @_;
my($token);
my($file);
my($spamwhitelisted);
# Generate Token/Sign unless message is originates from localhost or is inbound and whitelisted
my($clientip) = $message->{clientip};
if ($clientip =~ /^127/) {
return $message;
} else {
$message->MailScanner::Message::IsAReply();
# Is inbound or outbound signature being applied?
$file = MailScanner::Config::Value("inlinetextsig", $message);
$spamwhitelisted = $message->{spamwhitelisted};
if($message->{isreply}) {
# Message is a reply, do not sign
return $message;
} elsif ($file =~ /inline.sig.in.txt/ && $spamwhitelisted eq "1") {
# Message is inbound and whitelisted, do not sign
return $message;
} else {
$token = EFACreateToken();
$message->{token} = $token;
return SignNonSpam($message);
}
}
}
#
# EFA Token Creation
#
sub EFACreateToken {
my($dbh, $sth, $sql);
my($db_name) = 'efa';
my($db_host) = 'localhost';
my($db_user) = 'efa';
my($fh);
my($pw_config) = '/etc/EFA-Config';
open($fh, "<", $pw_config);
if(!$fh) {
MailScanner::Log::WarnLog("Unable to open %s to retrieve password", $pw_config);
return;
}
my($db_pass) = grep(/^EFASQLPWD/,<$fh>);
$db_pass =~ s/EFASQLPWD://;
$db_pass =~ s/\n//;
close($fh);
# Connect to the database
$dbh = DBI->connect("DBI:mysql:database=$db_name;host=$db_host",
$db_user, $db_pass,
{PrintError => 0});
# Check if connection was successfull - if it isn't
# then generate a warning and continue processing.
if (!$dbh) {
MailScanner::Log::WarnLog("Unable to initialise database connection: %s", $DBI::errstr);
return;
}
# Generate a new token
my($token) = randomtoken();
$sql = "INSERT INTO tokens (token, datestamp) VALUES ('$token', NOW())";
$sth = $dbh->prepare($sql);
$sth->execute;
# Close connections
$sth->finish();
$dbh->disconnect();
# Return $token
return $token;
}
#
# EFA Spam Token Generation and Notification
#
sub EFASpamNotify {
my($message) = @_;
my($token);
$token = EFACreateToken();
#if (!$token) { return; }
$message->{token} = $token;
# Notify user
HandleSpamNotify($message);
return 0;
}
# +---------------------------------------------------+
# # Function to create a pseudorandom 32 char token
# # +---------------------------------------------------+
sub randomtoken {
my($token) = `date | md5sum | tr -cd '[:alnum:]'`;
return $token;
}
# +---------------------------------------------------+
# Code borrowed from Messages.pm and modified for use with EFA
#
# We want to send a message to the recipient saying that their spam
# mail has not been delivered.
# Send a message to the recipients which has the local postmaster as
# the sender.
sub HandleSpamNotify {
my($message) = @_;
my($from,$to,$subject,$date,$spamreport,$hostname,$day,$month,$year);
my($emailmsg, $line, $messagefh, $filename, $localpostmaster, $id);
my($postmastername);
my($token);
$from = $message->{from};
# Setup other variables they can use in the message template
$id = $message->{id};
$localpostmaster = MailScanner::Config::Value('localpostmaster', $message);
$postmastername = MailScanner::Config::LanguageValue($message, 'mailscanner');
$hostname = MailScanner::Config::Value('hostname', $message);
$subject = $message->{subject};
$date = $message->{datestring}; # scalar localtime;
$spamreport = $message->{spamreport};
$token = $message->{token};
# And let them put the date number in there too
#($day, $month, $year) = (localtime)[3,4,5];
#$month++;
#$year += 1900;
#my $datenumber = sprintf("%04d%02d%02d", $year, $month, $day);
my $datenumber = $message->{datenumber};
my($to, %tolist);
foreach $to (@{$message->{to}}) {
$tolist{$to} = 1;
}
$to = join(', ', sort keys %tolist);
# Delete everything in brackets after the SA report, if it exists
$spamreport =~ s/(spamassassin)[^(]*\([^)]*\)/$1/i;
# Work out which of the 3 spam reports to send them.
$filename = MailScanner::Config::Value('recipientspamreport', $message);
MailScanner::Log::NoticeLog("Spam Actions: Notify %s", $to)
if MailScanner::Config::Value('logspam');
$messagefh = new FileHandle;
$messagefh->open($filename)
or MailScanner::Log::WarnLog("Cannot open message file %s, %s",
$filename, $!);
$emailmsg = "";
while(<$messagefh>) {
chomp;
s#"#\\"#g;
s#@#\\@#g;
# Boring untainting again...
/(.*)/;
$line = eval "\"$1\"";
$emailmsg .= MailScanner::Config::DoPercentVars($line) . "\n";
}
$messagefh->close();
# Send the message to the spam sender, but ensure the envelope
# sender address is "<>" so that it can't be bounced.
$global::MS->{mta}->SendMessageString($message, $emailmsg, $localpostmaster)
or MailScanner::Log::WarnLog("Could not send sender spam notify, %s", $!);
}
#
# Code borrowed from Message.pm modified for use with EFA
#
sub SignNonSpam {
my($message) = @_;
my($from);
return if $message->{infected}; # Double-check!
$from = $message->{from};
# Don't ever send a message to "" or "<>"
return if $from eq "" || $from eq "<>";
my($entity, $scannerheader, $clientip);
# Use the presence of an X-MailScanner: header to decide if the
# message will have already been signed by another MailScanner server.
$scannerheader = MailScanner::Config::Value('mailheader', $message);
$scannerheader =~ tr/://d;
# EFA
# Change Signing Behavior do not sign messages from localhost
$clientip = $message->{clientip};
if ($clientip =~ /^127/) { return $message; }
# Want to sign the bottom of the highest-level MIME entity
$entity = $message->{entity};
if (MailScanner::Config::Value('signalreadyscanned', $message) ||
(defined($entity) && !$entity->head->count($scannerheader))) {
AppendSignCleanEntity($message, $entity, 0);
if ($entity && $entity->head) {
$entity->head->add('MIME-Version', '1.0')
unless $entity->head->get('mime-version');
}
$message->{bodymodified} = 1;
}
return $message;
}
#
# Code borrowed from Messages.pm modified for use with EFA
# Code is identical execpt message is passed as parameter instead of
# being called from method
#
sub AppendSignCleanEntity {
my($message, $top, $parent) = @_;
my($MimeType, $signature, @signature);
return unless $top;
#print STDERR "In AppendSignCleanEntity, signing $top\n";
# If multipart, try to sign our first part
if ($top->is_multipart) {
my $sigcounter = 0;
# JKF Signed and encrypted multiparts must not be touched.
# JKF Instead put the sig in the epilogue. Breaks the RFC
# JKF but in a harmless way.
if ($top->effective_type =~ /multipart\/(signed|encrypted)/i) {
# Read the sig and put it in the epilogue, which may be ignored
$signature = ReadVirusWarning($message,'inlinetextsig');
@signature = map { "$_\n" } split(/\n/, $signature);
unshift @signature, "\n";
$top->epilogue(\@signature);
return 1;
}
# If the ASCE(0) returned -1 then we found something we could sign but
# chose not to, so set $sigcounter so we won't try to sign anything else.
my $result0 = AppendSignCleanEntity($message, $top->parts(0), $top);
if ($result0 >= 0) {
$sigcounter += $result0;
} else {
$sigcounter = -1;
}
# If the ASCE(1) returned -1 then we found something we could sign but
# chose not to, so set $sigcounter so we won't try to sign anything else.
if ($top->head and $top->effective_type =~ /multipart\/alternative/i) {
my $result1 = AppendSignCleanEntity($message, $top->parts(1), $top);
if ($result1 >= 0) {
$sigcounter += $result1;
} else {
$sigcounter = -1;
}
}
if ($sigcounter == 0) {
# If we haven't signed anything by now, it must be a multipart
# message containing only things we can't sign. So add a text/plain
# section on the front and sign that.
my $text = ReadVirusWarning($message, 'inlinetextsig') . "\n\n";
my $newpart = build MIME::Entity
Type => 'text/plain',
Charset =>
MailScanner::Config::Value('attachmentcharset', $message),
Disposition => 'inline',
Data => $text,
Encoding => 'quoted-printable',
Top => 0;
$top->add_part($newpart, 0);
$sigcounter = 1;
}
return $sigcounter;
}
$MimeType = $top->head->mime_type if $top->head;
return 0 unless $MimeType =~ m{text/(html|plain)}i; # Won't sign non-text message.
# Won't sign attachments.
return 0 if $top->head->mime_attr('content-disposition') =~ /attachment/i;
# Won't sign HTML parts when we already have a sig and don't allow duplicates
# Or we are a reply and we don't sign replies.
# We return -1 as a special token indicating that there was something we
# could sign but chose not to. If I pick up a -1 when called then don't
# try to sign anything else.
return -1 if ($message->{sigimagepresent} &&
$MimeType =~ /text\/html/i &&
MailScanner::Config::Value('allowmultsigs', $message) !~ /1/) ||
($message->{isreply} &&
MailScanner::Config::Value('isareply', $message));
# Get body data as array of newline-terminated lines
$top->bodyhandle or return undef;
my @body = $top->bodyhandle->as_lines;
# Output original data back into body, followed by message
my($line, $io, $FoundHTMLEnd, $FoundBodyEnd, $FoundSigMark, $html);
$FoundHTMLEnd = 0; # If there is no </html> tag, still append the signature
$FoundBodyEnd = 0; # If there is no </body> tag, still append the signature
$FoundSigMark = 0; # Try to replace _SIGNATURE_ with the sig if it's there
$html = 0;
$io = $top->open("w");
if ($MimeType =~ /text\/html/i) {
$signature = ReadVirusWarning($message, 'inlinehtmlsig');
foreach $line (@body) {
# Try to insert the signature where they want it.
$FoundSigMark = 1 if $line =~ s/_SIGNATURE_/$signature/;
$FoundBodyEnd = 1 if !$FoundSigMark && $line =~ s/\<\/body\>/$signature$&/i;
$FoundHTMLEnd = 1 if !$FoundSigMark && !$FoundBodyEnd && $line =~ s/\<\/x?html\>/$signature$&/i;
$io->print($line);
}
$io->print($signature . "\n")
unless $FoundBodyEnd || $FoundHTMLEnd || $FoundSigMark;
(($body[-1]||'') =~ /\n\Z/) or $io->print("\n"); # Ensure final newline
$html = 1;
} else {
$signature = ReadVirusWarning($message, 'inlinetextsig');
foreach $line (@body) {
# Replace _SIGNATURE_ with the inline sig, if it's present.
$FoundSigMark = 1 if $line =~ s/_SIGNATURE_/$signature/;
$io->print($line); # Original body data
}
# Else just tack the sig on the end.
$io->print("\n$signature\n") unless $FoundSigMark;
}
$io->close;
# Add Image Attachment from Mail Scanner, unless there already is one
if (MailScanner::Config::Value('attachimage', $message) =~ /1/ && !$message->{sigimagepresent}) {
#print STDERR "Adding image signature\n";
my $attach = MailScanner::Config::Value('attachimagetohtmlonly', $message);
if (($html && $attach =~ /1/) || $attach =~ /0/) {
my $filename = MailScanner::Config::Value('attachimagename', $message);
my $ext = 'unknown';
$ext = $1 if $filename =~ /\.([a-z]{3,4})$/;
$ext = 'jpeg' if $ext =~ /jpg/i;
my $internalname = MailScanner::Config::Value('attachimageinternalname', $message);
if (length($filename) && -f $filename) {
my $newentity = MIME::Entity->build(Path => $filename,
Top => 0,
Type => "image/$ext",
Encoding => "base64",
Filename => $internalname,
Disposition => "inline",
'Content-Id:' => '<' . $internalname . '>');
if ($parent && $parent->effective_type =~ /multipart\/related/i) {
# It's already been signed once, so don't nest the MIME structure more
$parent->add_part($newentity);
} else {
# It's a first-time sig, so next it into a multipart/related
$top->make_multipart('related');
$top->add_part($newentity);
}
}
}
}
# We signed something
return $message;
}
# Code borrowed from Messages.pm modified for use with EFA
# Modified to add token to signature line
#
# Read the appropriate warning message to sign the top of cleaned messages.
# Passed in the name of the config variable that points to the filename.
# This is also used to read the inline signature added to the bottom of
# clean messages.
# Substitutions allowed in the message are
# $viruswarningfilename -- by default VirusWarning.txt
# $from
# $subject
# and $filename -- comma-separated list of infected attachments
sub ReadVirusWarning {
my($message, $option) = @_;
my $file = MailScanner::Config::Value($option, $message);
my $viruswarningname = MailScanner::Config::Value('attachmentwarningfilename',
$message);
my($line);
#print STDERR "Reading virus warning message from $filename\n";
my $fh = new FileHandle;
$fh->open($file)
or (MailScanner::Log::WarnLog("Could not open inline file %s, %s",
$file, $!),
return undef);
# Work out the list of all the infected attachments, including
# reports applying to the whole message
my($typedattach, $attach, $text, %infected, $filename, $from, $subject, $id, $token);
while (($typedattach, $text) = each %{$message->{allreports}}) {
# It affects the entire message if the entity of this file matches
# the entity of the entire message.
$attach = substr($typedattach,1);
my $entity = $message->{file2entity}{"$attach"};
#if ($attach eq "") {
if ($message->{entity} eq $entity) {
$infected{MailScanner::Config::LanguageValue($message, "theentiremessage")}
= 1;
} else {
$infected{"$attach"} = 1;
}
}
# And don't forget the external bodies which are just entity reports
while (($typedattach, $text) = each %{$message->{entityreports}}) {
$infected{MailScanner::Config::LanguageValue($message, 'notnamed')} = 1;
}
$attach = substr($typedattach,1);
$filename = join(', ', keys %infected);
$id = $message->{id};
$from = $message->{from};
$subject = $message->{subject};
my($token) = $message->{token};
my($hostname) = MailScanner::Config::Value('hostname', $message);
my $result = "";
while (<$fh>) {
chomp;
s#"#\\"#g;
s#@#\\@#g;
# Boring untainting again...
/(.*)/;
$line = eval "\"$1\"";
$result .= MailScanner::Config::DoPercentVars($line) . "\n";
}
$fh->close();
$result;
}