#
#   MailScanner - SMTP E-Mail Virus Scanner
#   Copyright (C) 2002  Julian Field
#
#   $Id: MCPMessage.pm,v 1.1.2.24 2005/06/03 10:46:15 jkf Exp $
#
#   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 2 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, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#   The author, Julian Field, can be contacted by email at
#      Jules@JulianField.net
#   or by paper mail at
#      Julian Field
#      Dept of Electronics & Computer Science
#      University of Southampton
#      Southampton
#      SO17 1BJ
#      United Kingdom
#

package MailScanner::Message;

use strict 'vars';
use strict 'refs';
no  strict 'subs'; # Allow bare words for parameter %'s

use DirHandle;
use Time::localtime qw/ctime/;
use MIME::Parser;
use MIME::Decoder::UU;
use MIME::WordDecoder;
use POSIX qw(setsid);
use HTML::TokeParser;

# Install an extra MIME decoder for badly-header uue messages.
install MIME::Decoder::UU 'uuencode';

use vars qw($VERSION);

### The package version, both in 1.23 style *and* usable by MakeMaker:
$VERSION = substr q$Revision: 1.1.2.24 $, 10;

# Is this message spam? Try to build the spam report and store it in
# the message.
sub IsMCP {
  my $this = shift;
  my($includesaheader, $iswhitelisted);

  my $spamheader    = "";
  my $rblspamheader = "";
  my $saspamheader  = "";
  my $RBLsaysspam   = 0;
  my $rblcounter    = 0;
  my $LogSpam = MailScanner::Config::Value('logmcp');
  my $LocalSpamText = MailScanner::Config::LanguageValue($this, 'mcp');

  # Construct a pretty list of all the unique domain names for logging
  my(%todomain, $todomain);
  foreach $todomain (@{$this->{todomain}}) {
    $todomain{$todomain} = 1;
  }
  $todomain = join(',', keys %todomain);

  $this->{mcpwhitelisted} = 0;
  $this->{ismcp} = 0;
  $this->{ishighmcp} = 0;
  $this->{mcpreport} = "";
  $this->{mcpsascore} = 0;

  ## If it's a blacklisted address, don't bother doing any checks at all
  #if (MailScanner::Config::Value('spamblacklist', $this)) {
  #  $this->{isspam} = 1;
  #  $this->{spamreport} = 'spam (blacklisted)';
  #  MailScanner::Log::InfoLog("Message %s from %s (%s) " .
  #                            " is spam (blacklisted)",
  #                            $this->{id}, $this->{clientip},
  #                            $this->{from});
  #  return 1;
  #}

  # Work out if they always want the SA header
  $includesaheader = MailScanner::Config::Value('includemcpheader', $this);

  # Do the whitelist check before the blacklist check.
  # If anyone whitelists it, then everyone gets the message.
  # If no-one has whitelisted it, then consider the blacklist.
  $iswhitelisted = 0;
  if (MailScanner::Config::Value('mcpwhitelist', $this)) {
    # Whitelisted, so get out unless they want SA header
    #print STDERR "Message is whitelisted\n";
    $iswhitelisted = 1;
    $this->{mcpwhitelisted} = 1;
    # whitelisted and doesn't want SA header so get out
    return 0 unless $includesaheader;
  }

  # If it's a blacklisted address, don't bother doing any checks at all
  if (MailScanner::Config::Value('mcpblacklist', $this)) {
    $this->{ismcp} = 1;
    $this->{ishighmcp} = 1
      if MailScanner::Config::Value('mcpblacklistedishigh', $this);
    $this->{mcpreport} = $LocalSpamText . ' (' .
                   MailScanner::Config::LanguageValue($this, 'mcpblacklisted') .
                   ')';
    MailScanner::Log::InfoLog("Message %s from %s (%s) to %s" .
                              " is banned (MCP blacklisted)",
                              $this->{id}, $this->{clientip},
                              $this->{from}, $todomain)
      if $LogSpam;
    return 1;
  }

  #if (!$iswhitelisted) {
  #  # Not whitelisted, so do the RBL checks
  #  #$rblspamheader     = MailScanner::RBLs::Checks($this);
  #  ($rblcounter, $rblspamheader) = MailScanner::RBLs::Checks($this);
  #  $RBLsaysspam       = 1 if $rblcounter;
  #  #$RBLsaysspam       = 1 if $rblspamheader;
  #  # Add leading "spam, " if RBL says it is spam. This will be at the
  #  # front of the spam report.
  #  $rblspamheader     = $LocalSpamText . ', ' . $rblspamheader if $rblcounter;
  #  $this->{isspam}    = 1 if $rblcounter;
  #  $this->{isrblspam} = 1 if $rblcounter;
  #  $this->{ishigh}    = 1 if $rblcounter >= MailScanner::Config::Value(
  #                                           'highrbls', $this);
  #  #print STDERR "RBL report is \"$rblspamheader\"\n";
  #  #print STDERR "RBLCounter = $rblcounter\n";
  #  #print STDERR "HighRBLs   = " .
  #  #             MailScanner::Config::Value('highrbls', $this) . "\n";
  #}

  # Don't do the SA checks if they have said no.
  #unless (MailScanner::Config::Value('mcpusespamassassin', $this)) {
  #  $this->{mcpwhitelisted} = $iswhitelisted;
  #  $this->{mcpreport}      = $rblspamheader;
  #  MailScanner::Log::InfoLog("Message %s from %s (%s) to %s is %s",
  #                            $this->{id}, $this->{clientip},
  #                            $this->{from}, $todomain, $rblspamheader)
  #    if $RBLsaysspam && $LogSpam;
  #  return $RBLsaysspam;
  #}

  # If it's spam and they dont want to check SA as well
  #if ($this->{isspam} &&
  #    !MailScanner::Config::Value('checksaifonspamlist', $this)) {
  #  $this->{spamwhitelisted} = $iswhitelisted;
  #  $this->{spamreport}      = $rblspamheader;
  #  MailScanner::Log::InfoLog("Message %s from %s (%s) to %s is %s",
  #                            $this->{id}, $this->{clientip},
  #                            $this->{from}, $todomain, $rblspamheader)
  #    if $RBLsaysspam && $LogSpam;
  #  return $RBLsaysspam;
  #}

  # They must want the SA checks doing.

  my $SAsaysspam = 0;
  my $SAHighScoring = 0;
  my $saheader = "";
  my $sascore  = 0;
  ($SAsaysspam, $SAHighScoring, $saheader, $sascore)
    = MailScanner::MCP::Checks($this);
  $this->{mcpsascore} = $sascore; # Save the actual figure for use later...

  # Fix the return values
  $SAsaysspam = 0 unless $saheader;    # Solve bug with empty SAreports
  $saheader =~ s/\s+$//g if $saheader; # Solve bug with trailing space

  #print STDERR "SA report is \"$saheader\"\n";
  #print STDERR "SAsaysspam = $SAsaysspam\n";
  $saheader = MailScanner::Config::LanguageValue($this, 'mcpspamassassin') .
              " ($saheader)" if $saheader;

  # The message really is spam if SA says so (unless it's been whitelisted)
  unless ($iswhitelisted) {
    $this->{ismcp} |= $SAsaysspam;
    $this->{issamcp} = $SAsaysspam;
  }

  # If it's spam...
  if ($this->{ismcp}) {
    #print STDERR "It is spam\nInclude SA = $includesaheader\n";
    #print STDERR "SAHeader = $saheader\n";
    $spamheader = $rblspamheader;
    # If it's SA spam as well, or they always want the SA header
    if ($SAsaysspam || $includesaheader) {
      #print STDERR "Spam or Add SA Header\n";
      $spamheader = $LocalSpamText unless $spamheader;
      $spamheader .= ', ' if $spamheader && $saheader;
      $spamheader .= $saheader;
      $this->{ishighmcp} = 1 if $SAHighScoring;
    }
  } else {
    # It's not spam...
    #print STDERR "It's not spam\n";
    #print STDERR "SAHeader = $saheader\n";
    $spamheader = MailScanner::Config::LanguageValue($this, 'mcpnotspam');
    if ($iswhitelisted) {
      $spamheader .= ' (' .
                    MailScanner::Config::LanguageValue($this, 'mcpwhitelisted') .
                    ')';
    }
    # so RBL report must be blank as you can't force inclusion of that.
    # So just include SA report.
    $spamheader .= ", $saheader";
  }

  # Now just reflow and log the results
  if ($spamheader ne "") {
    $spamheader = $this->ReflowHeader(
                  MailScanner::Config::Value('mcpheader',$this), $spamheader);
    $this->{mcpreport} = $spamheader;
  }

  # Do the spam logging here so we can log high-scoring spam too
  if ($LogSpam && $this->{ismcp}) {
    my $ReportText = $spamheader;
    $ReportText =~ s/\s+/ /sg;
    MailScanner::Log::InfoLog("Message %s from %s (%s) to %s is %s",
                              $this->{id}, $this->{clientip},
                              $this->{from}, $todomain, $ReportText);
  }

  return $this->{ismcp};
}
    

# Do whatever is necessary with this message to deal with spam.
# We can assume the message passed is indeed spam (isspam==true).
# Call it with either 'spam' or 'nonspam'. Don't use 'ham'!
sub HandleMCP {
  my($this, $HamSpam) = @_;

  my($actions, $action, @actions, %actions);
  my(@extraheaders, $actionscopy, $actionkey);

  # Get a space-separated list of all the actions
  if ($HamSpam eq 'nonmcp') {
    $actions = MailScanner::Config::Value('nonmcpactions', $this);
    # Fast bail-out if it's just the simple "deliver" case that 99% of
    # people will use
    return if $actions eq 'deliver';
  } else {
    # It must be spam as it's not ham
    if ($this->{ishighmcp}) {
      $actions = MailScanner::Config::Value('highscoremcpactions', $this);
    } else {
      $actions = MailScanner::Config::Value('mcpactions', $this);
    }
  }

  # Find all the bits in quotes, with their spaces
  $actionscopy = $actions;
  #print STDERR "Actions = \'$actions\'\n";
  while ($actions =~ s/\"([^\"]+)\"//) {
    $actionkey = $1;
    push @extraheaders, $actionkey;
    MailScanner::Log::WarnLog("Syntax error in \"header\" action in MCP " .
                              "actions, missing \":\" in %s", $actionkey)
      unless $actionkey =~ /:/;
  }
  @{$this->{extramcpheaders}} = @extraheaders;
  $actions = lc($actions);
  $actions =~ s/^\s*//;
  $actions =~ s/\s*$//;
  $actions =~ s/\s+/ /g;
  #print STDERR "Actions after = \'$actions\'\n";
  #print STDERR "Extra headers are \"" . join(',',@extraheaders) . "\"\n";

  MailScanner::Log::WarnLog('Syntax error: missing " in MCP actions %s',
                            $actionscopy) if $actions =~ /\"/;

  $actions =~ tr/,//d; # Remove all commas in case they put any in
  @actions = split(" ", $actions);

  # The default action if they haven't specified anything is to
  # deliver spam like normal mail.
  return unless @actions;

  #print STDERR "Message: HandleHamSpam has actions " . join(',',@actions) .
  #             "\n";

  foreach $action (@actions) {
    $actions{$action} = 1;
    #print STDERR "Message: HandleSpam action is $action\n";
    if ($action =~ /\@/) {
      #print STDERR "Message " . $this->{id} . " : HandleSpam() adding " .
      #             "$action to archiveplaces\n";
      push @{$this->{archiveplaces}}, $action;
      $actions{'forward'} = 1;
    }
  }

  # Now we are left with deliver, bounce, delete, store and striphtml.
  #print STDERR "Archive places are " . join(',', keys %actions) . "\n";

  # Split this job into 2.
  # 1) The message is being delivered to at least 1 address,
  # 2) The message is not being delivered to anyone.
  # The extra addresses for forward it to have already been added.
  if ($actions{'deliver'} || $actions{'forward'}) {
    #
    # Message is going to original recipient and/or extra recipients
    #

    MailScanner::Log::InfoLog("MCP Actions: message %s actions are %s",
                              $this->{id}, join(',', keys %actions))
      if $HamSpam eq 'mcp' && MailScanner::Config::Value('logmcp');

    # Delete action is over-ridden as we are sending it somewhere
    delete $actions{'delete'};

    # Delete the original recipient if they are only forwarding it
    $this->{mcpdelivering} = 1;
    if (!$actions{'deliver'}) {
      $global::MS->{mta}->DeleteRecipients($this);
      $this->{mcpdelivering} = 0;
    }
    # Message still exists, so it will be delivered to its new recipients
  } else {
    #
    # Message is not going to be delivered anywhere
    #

    MailScanner::Log::InfoLog("MCP Actions: message %s actions are %s",
                              $this->{id}, join(',', keys %actions))
      if $HamSpam eq 'mcp' && MailScanner::Config::Value('logmcp');

    # Mark the message as deleted, so it won't get delivered
    #$this->{deleted} = 1;
    $this->{dontdeliver} = 1; # Don't clean or deliver this message, just drop
    # Mark this message as not being delivered by MCP, for later spam filtering
    $this->{mcpdelivering} = 0;
  }

  # All delivery will now happen correctly.

  # Bounce a message back to the sender if they want that
  if ($actions{'bounce'}) {
    if ($HamSpam eq 'nonmcp') {
      MailScanner::Log::WarnLog("Does not make sense to bounce non-mcp");
    } else {
      $this->HandleMCPBounce();
    }
  }

  # Notify the recipient if they want that
  if ($actions{'notify'}) {
    if ($HamSpam eq 'nonmcp') {
      MailScanner::Log::WarnLog("Does not make sense to notify recipient about non-mcp");
    } else {
      $this->HandleMCPNotify();
    }
  }

  
  # Store it if they want that
  if ($actions{'store'}) {
    my($dir, $dir2, $spamdir, $uid, $gid, $changeowner);
    $dir = MailScanner::Config::Value('quarantinedir', $this);
    #$dir2 = $dir . '/' .  MailScanner::Quarantine::TodayDir();
    $dir2 = $dir . '/' .  $this->{datenumber};
    $spamdir = $dir2 . '/' . $HamSpam;
    $uid = $global::MS->{quar}->{uid};
    $gid = $global::MS->{quar}->{gid};
    $changeowner = $global::MS->{quar}->{changeowner};
    umask $global::MS->{quar}->{dirumask};
    unless (-d $dir) {
      mkdir $dir, 0777;
      chown $uid, $gid, $dir if $changeowner;
    }
    unless (-d $dir2) {
      mkdir $dir2, 0777;
      chown $uid, $gid, $dir2 if $changeowner;
    }
    unless (-d $spamdir) {
      mkdir $spamdir, 0777;
      chown $uid, $gid, $spamdir if $changeowner;
    }
    #print STDERR "Storing spam to $spamdir/" . $this->{id} . "\n";
    umask $global::MS->{quar}->{fileumask};
    my @paths = $this->{store}->CopyEntireMessage($this, $spamdir, $this->{id});
    # Remember where we have stored the mcp in an archive, so we never
    # archive infected messages.
    push @{$this->{spamarchive}}, @paths;
    chown $uid, $gid, "$spamdir/" . $this->{id}; # Harmless if this fails
  }
  umask 0077; # Safety net

  # If they want to strip the HTML tags out of it,
  # then just tag it as we can only do this later.
  $this->{needsstripping} = 1 if $actions{'striphtml'};

  # If they want to encapsulate the message in an RFC822 part,
  # then tag it so we can do this later.
  $this->{needsencapsulating} = 1 if $actions{'attachment'};
}


# We want to send a message back to the sender saying that their junk
# email has been rejected by our site.
# Send a message back to the sender which has the local postmaster as
# the header sender, but <> as the envelope sender. This means it
# cannot bounce.
# Now have 3 different message file settings:
# 1. Is spam according to RBL's
# 2. Is spam according to SpamAssassin
# 3. Is spam according to both
sub HandleMCPBounce {
  my $this = shift;

  my($from,$to,$subject,$date,$spamreport,$hostname);
  my($emailmsg, $line, $messagefh, $filename, $localpostmaster, $id);
  my($postmastername);

  $from = $this->{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', $this)));
  $precedence = "";
  foreach $header (@{$this->{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;
      }
    }
  }

  # Setup other variables they can use in the message template
  $id = $this->{id};
  $to = join(', ', @{$this->{to}});
  $localpostmaster = MailScanner::Config::Value('localpostmaster', $this);
  $postmastername  = MailScanner::Config::LanguageValue($this, 'mailscanner');
  $hostname = MailScanner::Config::Value('hostname', $this);
  $subject = $this->{subject};
  $date = $this->{datestring}; # scalar localtime;
  $spamreport = $this->{mcpreport};

  # 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 = "";
  #if ($this->{isrblspam} && !$this->{issaspam}) {
  #  $filename = MailScanner::Config::Value('senderrblspamreport', $this);
  #  MailScanner::Log::InfoLog("Spam Actions: (RBL) Bounce to %s", $from)
  #    if MailScanner::Config::Value('logspam');
  #} elsif ($this->{issaspam} && !$this->{isrblspam}) {
  if ($this->{issamcp}) {
    $filename = MailScanner::Config::Value('sendersamcpreport', $this);
    MailScanner::Log::InfoLog("MCP Actions: (SpamAssassin) Bounce to %s",
                              $from)
      if MailScanner::Config::Value('logmcp');
  }
  #if ($filename eq "") {
  #  $filename = MailScanner::Config::Value('senderbothmcpreport', $this);
  #  MailScanner::Log::InfoLog("Spam Actions: (RBL,SpamAssassin) Bounce to %s",
  #                            $from)
  #    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();

  if (MailScanner::Config::Value('bouncemcpasattachment', $this)) {
    $this->HandleMCPBounceAttachment($emailmsg);
  } else {
    # 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($this, $emailmsg, '<>')
      or MailScanner::Log::WarnLog("Could not send sender MCP bounce, %s", $!);
  }
}


# Like encapsulating and sending a message to the recipient, take the
# passed text as the text and headers of an email message and attach
# the original message as an rfc/822 attachment.
sub HandleMCPBounceAttachment {
  my($this, $plaintext) = @_;

  my $parser = MIME::Parser->new;
  my $explodeinto = $global::MS->{work}->{dir} . '/' . $this->{id};
  #print STDERR "Extracting MCP bounce message into $explodeinto\n";
  my $filer  = MIME::Parser::FileInto->new($explodeinto);
  $parser->filer($filer);

  my $bounce = eval { $parser->parse_data(\$plaintext) };
  if (!$bounce) {
    MailScanner::Log::WarnLog("Cannot parse MCP bounce report, %s", $!);
    return;
  }
  #print STDERR "Successfully parsed bounce report\n";

  # Now make it multipart and push the report into a child
  $bounce->make_multipart('report');

  # Now turn the original message into a string and attach it
  my(@original);
  #my $original = $this->{entity}->stringify;
  @original = $global::MS->{mta}->OriginalMsgHeaders($this, "\n");
  push(@original, "\n");
  $this->{store}->ReadBody(\@original, MailScanner::Config::Value(
                                          'maxspamassassinsize'));

  $bounce->add_part(MIME::Entity->build(Type        => 'message/rfc822',
                                        Disposition => 'attachment',
                                        Top         => 0,
                                        'X-Mailer'  => undef,
                                        Data        => \@original));

  # Stringify the message and send it -- this could be VERY large!
  # Prune all the dead branches off the tree
  PruneEntityTree($bounce);
  my $bouncetext = $bounce->stringify;
  #print STDERR "Spam bounce message is this:\n$bouncetext";
  if ($bouncetext) {
    $global::MS->{mta}->SendMessageString($this, $bouncetext, '<>')
      or MailScanner::Log::WarnLog(
           "Could not send sender MCP bounce attachment, %s", $!);
  } else {
    MailScanner::Log::WarnLog(
      "Failed to create sender MCP bounce attachment, %s", $!);
  }
}


# 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 HandleMCPNotify {
  my $this = shift;

  my($from,$to,$subject,$date,$spamreport,$hostname,$day,$month,$year);
  my($emailmsg, $line, $messagefh, $filename, $localpostmaster, $id);
  my($postmastername);

  $from = $this->{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', $this)));
  $precedence = "";
  foreach $header (@{$this->{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;
      }
    }
  }

  # Setup other variables they can use in the message template
  $id = $this->{id};
  $localpostmaster = MailScanner::Config::Value('localpostmaster', $this);
  $postmastername  = MailScanner::Config::LanguageValue($this, 'mailscanner');
  $hostname = MailScanner::Config::Value('hostname', $this);
  $subject = $this->{subject};
  $date = $this->{datestring}; # scalar localtime;
  $spamreport = $this->{mcpreport};
  # 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 = $this->{datenumber};


  my($to, %tolist);
  foreach $to (@{$this->{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('recipientmcpreport', $this);
  MailScanner::Log::InfoLog("MCP Actions: Notify %s", $to)
    if MailScanner::Config::Value('logmcp');

  $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($this, $emailmsg, $localpostmaster)
    or MailScanner::Log::WarnLog("Could not send recipient mcp notify, %s", $!);
}


1;

