mirror of
https://git.proxmox.com/git/proxmox-spamassassin
synced 2025-04-28 12:19:37 +00:00
912 lines
30 KiB
Plaintext
Executable File
912 lines
30 KiB
Plaintext
Executable File
#!/usr/bin/perl -w
|
|
|
|
# <@LICENSE>
|
|
# Licensed to the Apache Software Foundation (ASF) under one or more
|
|
# contributor license agreements. See the NOTICE file distributed with
|
|
# this work for additional information regarding copyright ownership.
|
|
# The ASF licenses this file to you under the Apache License, Version 2.0
|
|
# (the "License"); you may not use this file except in compliance with
|
|
# the License. You may obtain a copy of the License at:
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# </@LICENSE>
|
|
|
|
use strict;
|
|
use warnings;
|
|
use re 'taint';
|
|
|
|
use File::Spec;
|
|
|
|
my $PREFIX = '@@PREFIX@@'; # substituted at 'make' time
|
|
my $DEF_RULES_DIR = '@@DEF_RULES_DIR@@'; # substituted at 'make' time
|
|
my $LOCAL_RULES_DIR = '@@LOCAL_RULES_DIR@@'; # substituted at 'make' time
|
|
my $LOCAL_STATE_DIR = '@@LOCAL_STATE_DIR@@'; # substituted at 'make' time
|
|
|
|
use lib '@@INSTALLSITELIB@@'; # substituted at 'make' time
|
|
|
|
BEGIN {
|
|
# Locate locally installed SA libraries *without* using FindBin, which
|
|
# generates warnings and causes more trouble than its worth. We don't
|
|
# need to be too smart about this BTW.
|
|
my @bin = File::Spec->splitpath($0);
|
|
my $bin = (
|
|
$bin[0]
|
|
? File::Spec->catpath( @bin[ 0 .. 1 ], '' )
|
|
: $bin[1]
|
|
) # /home/jm/foo -> /home/jm
|
|
|| File::Spec->curdir; # foo -> .
|
|
|
|
# check to make sure it wasn't just installed in the normal way.
|
|
# note that ./lib/Mail/SpamAssassin.pm takes precedence, for
|
|
# building SpamAssassin on a machine where an old version is installed.
|
|
if (-e $bin.'/lib/Mail/SpamAssassin.pm'
|
|
|| !-e '@@INSTALLSITELIB@@/Mail/SpamAssassin.pm' )
|
|
{
|
|
my $searchrelative;
|
|
$searchrelative = 1; # disabled during "make install": REMOVEFORINST
|
|
|
|
# Firstly, are we running "make test" in the "t" dir? the test files
|
|
# *need* to use 'blib', so that 'use bytes' is removed for pre-5.6 perls
|
|
# beforehand by the preproc. However, ./spamassassin does not, as the
|
|
# preproc will have stripped out the "use rule files from cwd" code from
|
|
# Mail::SpamAssassin. So we want to use blib just for the t scripts.
|
|
# This is disabled during the "make install" process.
|
|
if ($searchrelative && $bin eq '../' && -e '../blib/lib/Mail/SpamAssassin.pm')
|
|
{
|
|
unshift ( @INC, '../blib/lib' );
|
|
} else {
|
|
# These are common paths where the SA libs might be found.
|
|
foreach ( qw(lib ../lib/site_perl
|
|
../lib/spamassassin ../share/spamassassin/lib))
|
|
{
|
|
my $dir = File::Spec->catdir( $bin, split ( '/', $_ ) );
|
|
if ( -f File::Spec->catfile( $dir, "Mail", "SpamAssassin.pm" ) )
|
|
{ unshift ( @INC, $dir ); last; }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
use Getopt::Long;
|
|
use Pod::Usage;
|
|
use POSIX qw(locale_h setsid sigprocmask _exit);
|
|
use Mail::SpamAssassin;
|
|
use Mail::SpamAssassin::ArchiveIterator;
|
|
use Mail::SpamAssassin::Util::Progress;
|
|
use Mail::SpamAssassin::Logger qw(log_message);
|
|
|
|
BEGIN {
|
|
# redirect __WARN__, but NOT until after the
|
|
# Mail::SpamAssassin::Logger class has been parsed.
|
|
# do not trap warnings here based on eval scope; evals are very
|
|
# common throughout. die()s can be trapped in future though.
|
|
$SIG{__WARN__} = sub {
|
|
log_message("warn", $_[0]);
|
|
};
|
|
};
|
|
|
|
POSIX::setlocale(LC_TIME,'C');
|
|
|
|
my %resphash = (
|
|
EX_OK => 0, # no problems
|
|
EX_USAGE => 64, # command line usage error
|
|
EX_DATAERR => 65, # data format error
|
|
EX_NOINPUT => 66, # cannot open input
|
|
EX_NOUSER => 67, # addressee unknown
|
|
EX_NOHOST => 68, # host name unknown
|
|
EX_UNAVAILABLE => 69, # service unavailable
|
|
EX_SOFTWARE => 70, # internal software error
|
|
EX_OSERR => 71, # system error (e.g., can't fork)
|
|
EX_OSFILE => 72, # critical OS file missing
|
|
EX_CANTCREAT => 73, # can't create (user) output file
|
|
EX_IOERR => 74, # input/output error
|
|
EX_TEMPFAIL => 75, # temp failure; user is invited to retry
|
|
EX_PROTOCOL => 76, # remote error in protocol
|
|
EX_NOPERM => 77, # permission denied
|
|
EX_CONFIG => 78, # configuration error
|
|
);
|
|
|
|
|
|
sub print_version {
|
|
printf("SpamAssassin version %s\n running on Perl version %s\n",
|
|
Mail::SpamAssassin::Version(),
|
|
join(".", map( 0+($_||0), ($] =~ /(\d)\.(\d{3})(\d{3})?/ ))))
|
|
or die "error writing: $!";
|
|
}
|
|
|
|
sub print_usage_and_exit {
|
|
my ( $message, $respnam ) = @_;
|
|
$respnam ||= 'EX_USAGE';
|
|
|
|
if ($respnam eq 'EX_OK' ) {
|
|
print_version();
|
|
print("\n") or die "error writing: $!";
|
|
}
|
|
pod2usage(
|
|
-verbose => 0,
|
|
-message => $message,
|
|
-exitval => $resphash{$respnam},
|
|
-input => "spamassassin-run.pod",
|
|
-pathlist => \@INC,
|
|
);
|
|
}
|
|
|
|
|
|
|
|
sub usage {
|
|
my ( $verbose, $message ) = @_;
|
|
my $ver = Mail::SpamAssassin::Version();
|
|
|
|
print "SpamAssassin version $ver\n" or die "error writing: $!";
|
|
pod2usage( -verbose => $verbose, -message => $message, -exitval => 64, -input => "spamassassin-run.pod", -pathlist => \@INC );
|
|
|
|
}
|
|
|
|
# Check to make sure the script version and the module version matches.
|
|
# If not, die here! Also, deal with unchanged VERSION macro.
|
|
if ($Mail::SpamAssassin::VERSION ne '@@VERSION@@' && '@@VERSION@@' ne "\@\@VERSION\@\@") {
|
|
die 'spamassassin: spamassassin script is v@@VERSION@@, but using modules v'.$Mail::SpamAssassin::VERSION."\n";
|
|
}
|
|
|
|
# by default:
|
|
# - create user preference files
|
|
# - have ArchiveIterator detect the input message format (file vs dir)
|
|
#
|
|
my %opt = ( 'create-prefs' => 1, 'format' => 'detect', pre => [], cf => [] );
|
|
|
|
my $doing_welcomelist_operation = 0;
|
|
my $count = 0;
|
|
my @targets = ();
|
|
my $exitvalue;
|
|
|
|
my $init_results = 0;
|
|
my $progress;
|
|
my $total_messages = 0;
|
|
|
|
# gnu_getopt is not available in Getopt::Long 2.24, see bug 732
|
|
# gnu_compat neither.
|
|
Getopt::Long::Configure(
|
|
qw(bundling no_getopt_compat no_auto_abbrev no_ignore_case));
|
|
GetOptions(
|
|
'add-addr-to-blocklist=s' => \$opt{'add-addr-to-blocklist'},
|
|
'add-addr-to-welcomelist=s' => \$opt{'add-addr-to-welcomelist'},
|
|
'add-addr-to-blacklist=s' => \$opt{'add-addr-to-blocklist'}, # removed in 4.1
|
|
'add-addr-to-whitelist=s' => \$opt{'add-addr-to-welcomelist'}, # removed in 4.1
|
|
'add-to-blocklist' => \$opt{'add-to-blocklist'},
|
|
'add-to-welcomelist|W' => \$opt{'add-to-welcomelist'},
|
|
'add-to-blacklist' => \$opt{'add-to-blocklist'}, # removed in 4.1
|
|
'add-to-whitelist' => \$opt{'add-to-welcomelist'}, # removed in 4.1
|
|
'username|u=s' => \$opt{'username'},
|
|
'configpath|config-file|config-dir|c|C=s' => \$opt{'configpath'},
|
|
'create-prefs!' => \$opt{'create-prefs'},
|
|
'pre=s' => \@{$opt{'pre'}},
|
|
'cf=s' => \@{$opt{'cf'}},
|
|
'debug|D:s' => \$opt{'debug'},
|
|
'error-code|exit-code|e:i' => \$opt{'error-code'},
|
|
'help|h|?' => \$opt{'help'},
|
|
'4|ipv4only|ipv4-only|ipv4' => sub { $opt{'force_ipv4'} = 1;
|
|
$opt{'force_ipv6'} = 0; },
|
|
'6' => sub { $opt{'force_ipv6'} = 1;
|
|
$opt{'force_ipv4'} = 0; },
|
|
'lint' => \$opt{'lint'},
|
|
'net' => \$opt{'net'},
|
|
'local-only|local|L' => \$opt{'local'},
|
|
'mbox' => sub { $opt{'format'} = 'mbox'; },
|
|
'mbx' => sub { $opt{'format'} = 'mbx'; },
|
|
'prefspath|prefs-file|p=s' => \$opt{'prefspath'},
|
|
'remove-addr-from-welcomelist=s' => \$opt{'remove-addr-from-welcomelist'},
|
|
'remove-from-welcomelist|R' => \$opt{'remove-from-welcomelist'},
|
|
'remove-addr-from-whitelist=s' => \$opt{'remove-addr-from-welcomelist'}, # removed in 4.1
|
|
'remove-from-whitelist' => \$opt{'remove-from-welcomelist'}, # removed in 4.1
|
|
'remove-markup|despamassassinify|d' => \$opt{'remove-markup'},
|
|
'report|r' => \$opt{'report'},
|
|
'revoke|k' => \$opt{'revoke'},
|
|
'siteconfigpath=s' => \$opt{'siteconfigpath'},
|
|
'test-mode|test|t' => \$opt{'test-mode'},
|
|
'progress' => \$opt{'progress'},
|
|
'version|V' => \$opt{'version'},
|
|
'x' => sub { $opt{'create-prefs'} = 0 },
|
|
|
|
#
|
|
# NOTE: These are old options. We should ignore (but warn about)
|
|
# the ones that are now defaults. Everything else gets a die (see note2)
|
|
# so the user doesn't get us doing something they didn't expect.
|
|
#
|
|
# NOTE2: 'die' doesn't actually stop the process, GetOptions() catches
|
|
# it, then passes the error on, so we'll end up doing a Usage statement.
|
|
# You can avoid that by doing an explicit exit in the sub.
|
|
#
|
|
|
|
# last in 2.3
|
|
'pipe|P' => sub { warn "The -P option is deprecated as 'pipe mode' is now the default behavior, ignoring.\n" },
|
|
'F:i' => sub { warn "The -F option has been removed from spamassassin, please remove from your commandline and re-run.\n"; exit 2; },
|
|
'add-from!' => sub { warn "The --add-from option has been removed from spamassassin, please remove from your commandline and re-run.\n"; exit 2; },
|
|
|
|
# last in 2.4
|
|
'stop-at-threshold|S' => sub { warn "The -S option has been deprecated and is no longer supported, ignoring.\n" },
|
|
|
|
# last in 2.6
|
|
'log-to-mbox|l:s' => sub { warn "The -l option has been deprecated and is no longer supported, ignoring.\n" },
|
|
'warning-from|w:s' => sub { warn "The -w option has been removed from spamassassin, please remove from your commandline and re-run.\n"; exit 2; },
|
|
'whitelist-factory|M:s' => sub { warn "The -M option has been removed from spamassassin, please remove from your commandline and re-run.\n"; exit 2; },
|
|
|
|
) or print_usage_and_exit();
|
|
|
|
if ( defined $opt{'help'} ) {
|
|
print_usage_and_exit("For more information read the spamassassin man page.\n", 'EX_OK');
|
|
}
|
|
if ( defined $opt{'version'} ) {
|
|
print_version();
|
|
exit($resphash{'EX_OK'});
|
|
}
|
|
|
|
# set debug areas, if any specified (only useful for command-line tools)
|
|
if (defined $opt{'debug'}) {
|
|
$opt{'debug'} ||= 'all';
|
|
}
|
|
|
|
if (Mail::SpamAssassin::Util::am_running_on_windows()) {
|
|
binmode(STDIN) or die "cannot set binmode on STDIN: $!"; # bug 4363
|
|
binmode(STDOUT) or die "cannot set binmode on STDOUT: $!";
|
|
}
|
|
|
|
# bug 5048: --lint should not cause network accesses
|
|
# allow --net to override for testing
|
|
if ($opt{'lint'} && !$opt{'net'}) { $opt{'local'} = 1; }
|
|
|
|
# create the tester factory
|
|
my $spamtest = Mail::SpamAssassin->new(
|
|
{
|
|
rules_filename => $opt{'configpath'},
|
|
site_rules_filename => $opt{'siteconfigpath'},
|
|
userprefs_filename => $opt{'prefspath'},
|
|
username => $opt{'username'},
|
|
force_ipv4 => $opt{'force_ipv4'},
|
|
force_ipv6 => $opt{'force_ipv6'},
|
|
local_tests_only => $opt{'local'},
|
|
debug => $opt{'debug'},
|
|
dont_copy_prefs => ( $opt{'create-prefs'} ? 0 : 1 ),
|
|
pre_config_text => join("\n", @{$opt{'pre'}})."\n",
|
|
post_config_text => join("\n", @{$opt{'cf'}})."\n",
|
|
require_rules => 1,
|
|
PREFIX => $PREFIX,
|
|
DEF_RULES_DIR => $DEF_RULES_DIR,
|
|
LOCAL_RULES_DIR => $LOCAL_RULES_DIR,
|
|
LOCAL_STATE_DIR => $LOCAL_STATE_DIR,
|
|
}
|
|
);
|
|
|
|
if ($opt{'lint'}) {
|
|
$spamtest->debug_diagnostics();
|
|
my $res = $spamtest->lint_rules();
|
|
$spamtest->finish();
|
|
warn "lint: $res issues detected, please rerun with debug enabled for more information\n" if ($res and !$opt{'debug'});
|
|
# make sure we notice any write errors while flushing output buffer
|
|
close STDOUT or die "error closing STDOUT: $!";
|
|
close STDIN or die "error closing STDIN: $!";
|
|
exit($res ? 1 : 0);
|
|
}
|
|
|
|
if ($opt{'remove-addr-from-welcomelist'} ||
|
|
$opt{'add-addr-to-welcomelist'} ||
|
|
$opt{'add-addr-to-blocklist'})
|
|
{
|
|
$spamtest->init(1);
|
|
|
|
if ( $opt{'add-addr-to-welcomelist'} ) {
|
|
$spamtest->add_address_to_welcomelist($opt{'add-addr-to-welcomelist'}, 1);
|
|
}
|
|
elsif ( $opt{'remove-addr-from-welcomelist'} ) {
|
|
$spamtest->remove_address_from_welcomelist($opt{'remove-addr-from-welcomelist'}, 1);
|
|
}
|
|
elsif ( $opt{'add-addr-to-blocklist'} ) {
|
|
$spamtest->add_address_to_blocklist($opt{'add-addr-to-blocklist'}, 1);
|
|
}
|
|
else {
|
|
die "spamassassin: oops! unhandled welcomelist operation";
|
|
}
|
|
|
|
$spamtest->finish();
|
|
# make sure we notice any write errors while flushing output buffer
|
|
close STDOUT or die "error closing STDOUT: $!";
|
|
close STDIN or die "error closing STDIN: $!";
|
|
exit(0);
|
|
}
|
|
|
|
# if we're going to do welcome/block-listing, let's prep now...
|
|
if ( $opt{'remove-from-welcomelist'}
|
|
or $opt{'add-to-welcomelist'}
|
|
or $opt{'add-to-blocklist'} )
|
|
{
|
|
$doing_welcomelist_operation = 1;
|
|
$spamtest->init(1);
|
|
}
|
|
|
|
# if we're doing things in test mode, force disable long-term memory
|
|
# functions like autowelcomelist and bayes autolearn.
|
|
# XXX - feels like we need a plugin hook here so plugins can be made
|
|
# aware and take appropriate action.
|
|
if ($opt{'test-mode'}) {
|
|
$spamtest->{'conf'}->{'use_auto_welcomelist'} = 0;
|
|
$spamtest->{'conf'}->{'bayes_auto_learn'} = 0;
|
|
}
|
|
|
|
###########################################################################
|
|
# Deal with the target listing, and STDIN -> tempfile
|
|
|
|
my $tempfile; # will be defined if stdin -> tempfile
|
|
push(@targets, @ARGV);
|
|
@targets = ('-') unless @targets;
|
|
|
|
for(my $elem = 0; $elem <= $#targets; $elem++) {
|
|
# ArchiveIterator doesn't really like STDIN, so if "-" is specified
|
|
# as a target, make it a temp file instead.
|
|
if ( $targets[$elem] =~ /(?:^|:)-$/ ) {
|
|
if (defined $tempfile) {
|
|
# uh-oh, stdin specified multiple times?
|
|
warn "skipping extra stdin target (".$targets[$elem].")\n";
|
|
splice @targets, $elem, 1;
|
|
$elem--; # go back to this element again
|
|
next;
|
|
}
|
|
else {
|
|
my $handle;
|
|
( $tempfile, $handle ) = Mail::SpamAssassin::Util::secure_tmpfile();
|
|
binmode $handle or die "cannot set binmode on file $tempfile: $!";
|
|
|
|
# avoid slurping the whole file into memory, copy chunk by chunk
|
|
my($inbuf,$nread,$nwrites);
|
|
while ( $nread = sysread(STDIN, $inbuf, 32*1024) ) {
|
|
for (my $ofs = 0; $ofs < length($inbuf); $ofs += $nwrites) {
|
|
$nwrites = $handle->syswrite($inbuf, length($inbuf)-$ofs, $ofs);
|
|
defined $nwrites or die "error writing to $tempfile: $!";
|
|
}
|
|
}
|
|
undef $inbuf; # release storage
|
|
defined $nread or die "error reading from STDIN: $!";
|
|
close $handle or die "cannot close $tempfile: $!";
|
|
|
|
# re-aim the targets at the tempfile instead of STDIN
|
|
$targets[$elem] =~ s/-$/$tempfile/;
|
|
}
|
|
}
|
|
|
|
# make sure the target list is in the normal AI format
|
|
if ($targets[$elem] !~ /^[^:]*:[a-z]+:/) {
|
|
my $format = $opt{'format'} || 'detect';
|
|
$targets[$elem] = join ( ":", '', $format, $targets[$elem] );
|
|
}
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
setup_sig_handlers();
|
|
|
|
# Everything below here needs ArchiveIterator ...
|
|
my $iter = Mail::SpamAssassin::ArchiveIterator->new(
|
|
{
|
|
'opt_max_size' => 0, # no limit
|
|
'opt_want_date' => 0
|
|
}
|
|
);
|
|
|
|
$iter->set_functions( \&wanted, \&result );
|
|
|
|
# Go run the messages!
|
|
# bug 4930: use a temp variable since "||=" decides whether or not to set the
|
|
# value before the RHS is actually run, so if the RHS separately sets the LHS
|
|
# variable, things don't work right. Stupid global variables. ;)
|
|
my $eval_stat;
|
|
eval {
|
|
my $runreturn = !$iter->run(@targets); $exitvalue ||= $runreturn; 1;
|
|
} or do {
|
|
$eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
|
|
};
|
|
|
|
$progress->final() if ($opt{progress} && $progress);
|
|
|
|
# If we needed to make a tempfile, go delete it now.
|
|
if (defined $tempfile) {
|
|
unlink $tempfile or die "cannot unlink temporary file $tempfile: $!";
|
|
undef $tempfile;
|
|
}
|
|
|
|
# Let folks know how many messages were handled, as long as the handling
|
|
# didn't produce output (ala: check, test, or remove_markup ...)
|
|
if ( $opt{'report'} || $opt{'revoke'} || $doing_welcomelist_operation ) {
|
|
print "$count message(s) examined.\n" or die "error writing: $!";
|
|
}
|
|
|
|
# if the eval died from something, report it here and return an error.
|
|
if (defined $eval_stat) { die $eval_stat; }
|
|
|
|
$spamtest->finish() if $spamtest;
|
|
|
|
# make sure we notice any write errors while flushing output buffer
|
|
close STDOUT or die "error closing STDOUT: $!";
|
|
close STDIN or die "error closing STDIN: $!";
|
|
# Ok, exit!
|
|
exit( $exitvalue || 0 );
|
|
|
|
###########################################################################
|
|
|
|
sub init_results {
|
|
$init_results = 1;
|
|
|
|
return unless $opt{'progress'};
|
|
|
|
$total_messages = $Mail::SpamAssassin::ArchiveIterator::MESSAGES;
|
|
|
|
$progress = Mail::SpamAssassin::Util::Progress->new({total => $total_messages,});
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
sub result {
|
|
my ($class, $result, $time) = @_;
|
|
|
|
# don't open results files until we get here to avoid overwriting files
|
|
&init_results if !$init_results;
|
|
|
|
$progress->update($count) if ($opt{progress} && $progress);
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
my $mail; # global, so signal handler can clean it up; bug 5626
|
|
|
|
# make sure it only returns false values so that result_sub() isn't called...
|
|
sub wanted {
|
|
$spamtest->timer_reset; # reset timers for each AI message
|
|
my $dataref = $_[3];
|
|
$mail = $spamtest->parse($dataref);
|
|
$count++;
|
|
|
|
# This is a short cut -- doing welcome/block-list? Do it and return quickly.
|
|
if ($doing_welcomelist_operation) {
|
|
if ( $opt{'add-to-welcomelist'} ) {
|
|
$spamtest->add_all_addresses_to_welcomelist($mail, 1);
|
|
}
|
|
elsif ( $opt{'remove-from-welcomelist'} ) {
|
|
$spamtest->remove_all_addresses_from_welcomelist($mail, 1);
|
|
}
|
|
elsif ( $opt{'add-to-blocklist'} ) {
|
|
$spamtest->add_all_addresses_to_blocklist($mail, 1);
|
|
}
|
|
else {
|
|
warn "spamassassin: oops! unhandled welcomelist operation";
|
|
}
|
|
|
|
$mail->finish();
|
|
$mail = undef;
|
|
return 1;
|
|
}
|
|
|
|
# handle removing reports
|
|
if ( $opt{'remove-markup'} ) {
|
|
|
|
# If we're not going to retest, just remove the markup and print it out
|
|
if ( !$opt{'test-mode'} ) {
|
|
print $spamtest->remove_spamassassin_markup ($mail);
|
|
$mail->finish();
|
|
$mail = undef;
|
|
return 1;
|
|
}
|
|
else {
|
|
|
|
# remove the markup and retest it... a little more tricky ...
|
|
# go ahead and remove the markup, then fake that the clean version
|
|
# was what was sent in
|
|
#
|
|
my $new_mail =
|
|
$spamtest->parse( $spamtest->remove_spamassassin_markup($mail) );
|
|
|
|
$mail->finish();
|
|
$mail = $new_mail;
|
|
}
|
|
}
|
|
|
|
# handle reporting and revoking
|
|
if ( $opt{'report'} || $opt{'revoke'} ) {
|
|
|
|
# Make sure the message is clean first ...
|
|
my $new_mail =
|
|
$spamtest->parse( $spamtest->remove_spamassassin_markup($mail) );
|
|
$mail->finish();
|
|
$mail = $new_mail;
|
|
|
|
my $failed;
|
|
if ( $opt{'report'} && !$spamtest->report_as_spam($mail) ) {
|
|
$failed = 'report';
|
|
}
|
|
|
|
if ( $opt{'revoke'} && !$spamtest->revoke_as_spam($mail) ) {
|
|
$failed = 'revoke';
|
|
}
|
|
|
|
if ($failed) {
|
|
warn "spamassassin: warning, unable to $failed message\n";
|
|
warn "spamassassin: for more information, re-run with -D option to see debug output\n";
|
|
}
|
|
|
|
$mail->finish();
|
|
$mail = undef;
|
|
return 1;
|
|
}
|
|
|
|
# OK, do checks and put out the message.
|
|
my $status = $spamtest->check($mail);
|
|
print $status->rewrite_mail() or die "error writing: $!";
|
|
|
|
if ( $opt{'test-mode'} ) {
|
|
print $status->get_report() or die "error writing: $!";
|
|
}
|
|
|
|
# if this message was spam, set the exit value appropriately.
|
|
if ( defined $opt{'error-code'} && $status->is_spam() && !defined $exitvalue )
|
|
{
|
|
$exitvalue = $opt{'error-code'} || 5;
|
|
}
|
|
|
|
# clean up after ourselves
|
|
$mail->finish();
|
|
$mail = undef;
|
|
|
|
$status->finish();
|
|
|
|
return 1;
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
sub setup_sig_handlers {
|
|
$SIG{HUP} = \&kill_handler;
|
|
$SIG{INT} = \&kill_handler;
|
|
$SIG{TERM} = \&kill_handler;
|
|
# $SIG{PIPE} = \&kill_handler;
|
|
$SIG{PIPE} = 'IGNORE';
|
|
}
|
|
|
|
sub kill_handler {
|
|
my ($sig) = @_;
|
|
warn "spamassassin: killed by SIG$sig\n";
|
|
if ($mail) {
|
|
$mail->finish(); # bug 5626: remove temp files etc.
|
|
$mail = undef;
|
|
}
|
|
if (defined $tempfile) { # bug 5557: additional paranoia about tmpfiles
|
|
unlink $tempfile or warn "cannot unlink temporary file $tempfile: $!";
|
|
undef $tempfile;
|
|
}
|
|
close STDOUT; close STDIN; # ignoring status
|
|
exit 15;
|
|
}
|
|
|
|
__END__
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
=head1 NAME
|
|
|
|
spamassassin - extensible email filter used to identify spam
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
SpamAssassin is an intelligent email filter which uses a diverse range of
|
|
tests to identify unsolicited bulk email, more commonly known as "spam".
|
|
These tests are applied to email headers and content to classify email
|
|
using advanced statistical methods. In addition, SpamAssassin has a
|
|
modular architecture that allows other technologies to be quickly wielded
|
|
against spam and is designed for easy integration into virtually any email
|
|
system.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
For ease of access, the SpamAssassin manual has been split up into
|
|
several sections. If you're intending to read these straight through
|
|
for the first time, the suggested order will tend to reduce the number
|
|
of forward references.
|
|
|
|
Extensive additional documentation for SpamAssassin is available,
|
|
primarily on the SpamAssassin web site and wiki.
|
|
|
|
You should be able to view SpamAssassin's documentation with your man(1)
|
|
program or perldoc(1).
|
|
|
|
=head2 OVERVIEW
|
|
|
|
spamassassin SpamAssassin overview (this section)
|
|
|
|
=head2 CONFIGURATION
|
|
|
|
Mail::SpamAssassin::Conf SpamAssassin configuration files
|
|
|
|
=head2 USAGE
|
|
|
|
spamassassin-run "spamassassin" front-end filtering script
|
|
sa-learn train SpamAssassin's Bayesian classifier
|
|
spamc client for spamd (faster than spamassassin)
|
|
spamd spamassassin server (needed by spamc)
|
|
|
|
=head2 DEFAULT PLUGINS
|
|
|
|
@@PLUGIN_POD@@
|
|
|
|
=head1 WEB SITES
|
|
|
|
SpamAssassin web site: https://spamassassin.apache.org/
|
|
Wiki-based documentation: https://wiki.apache.org/spamassassin/
|
|
|
|
=head1 USER MAILING LIST
|
|
|
|
A users mailing list exists where other experienced users are often able
|
|
to help and provide tips and advice. Subscription instructions are
|
|
located on the SpamAssassin web site.
|
|
|
|
=head1 CONFIGURATION FILES
|
|
|
|
The SpamAssassin rule base, text templates, and rule description text
|
|
are loaded from configuration files.
|
|
|
|
Default configuration data is loaded from the first existing directory
|
|
in:
|
|
|
|
=over 4
|
|
|
|
=item @@LOCAL_STATE_DIR@@/@@VERSION@@
|
|
|
|
=item @@DEF_RULES_DIR@@
|
|
|
|
=item @@PREFIX@@/share/spamassassin
|
|
|
|
=item /usr/local/share/spamassassin
|
|
|
|
=item /usr/share/spamassassin
|
|
|
|
=back
|
|
|
|
Site-specific configuration data is used to override any values which had
|
|
already been set. This is loaded from the first existing directory in:
|
|
|
|
=over 4
|
|
|
|
=item @@LOCAL_RULES_DIR@@
|
|
|
|
=item @@PREFIX@@/etc/mail/spamassassin
|
|
|
|
=item @@PREFIX@@/etc/spamassassin
|
|
|
|
=item /usr/local/etc/spamassassin
|
|
|
|
=item /usr/pkg/etc/spamassassin
|
|
|
|
=item /usr/etc/spamassassin
|
|
|
|
=item /etc/mail/spamassassin
|
|
|
|
=item /etc/spamassassin
|
|
|
|
=back
|
|
|
|
From those directories, SpamAssassin will first read files ending in
|
|
".pre" in lexical order and then it will read files ending in ".cf" in
|
|
lexical order (most files begin with two numbers to make the sorting
|
|
order obvious).
|
|
|
|
In other words, it will read F<init.pre> first, then F<10_default_prefs.cf> before
|
|
F<50_scores.cf> and F<20_body_tests.cf> before F<20_head_tests.cf>.
|
|
Options in later files will override earlier files.
|
|
|
|
Individual user preferences are loaded from the location specified on
|
|
the C<spamassassin>, C<sa-learn>, or C<spamd> command line (see respective
|
|
manual page for details). If the location is not specified,
|
|
F<~/.spamassassin/user_prefs> is used if it exists. SpamAssassin will
|
|
create that file if it does not already exist, using
|
|
F<user_prefs.template> as a template. That file will be looked for in:
|
|
|
|
=over 4
|
|
|
|
=item @@LOCAL_RULES_DIR@@
|
|
|
|
=item @@PREFIX@@/etc/mail/spamassassin
|
|
|
|
=item @@PREFIX@@/share/spamassassin
|
|
|
|
=item /etc/spamassassin
|
|
|
|
=item /etc/mail/spamassassin
|
|
|
|
=item /usr/local/share/spamassassin
|
|
|
|
=item /usr/share/spamassassin
|
|
|
|
=back
|
|
|
|
=head1 TAGGING
|
|
|
|
The following two sections detail the default tagging and markup that
|
|
takes place for messages when running C<spamassassin> or C<spamc> with
|
|
C<spamd> in the default configuration.
|
|
|
|
Note: before header modification and addition, all headers beginning
|
|
with C<X-Spam-> are removed to prevent spammer mischief and also to
|
|
avoid potential problems caused by prior invocations of SpamAssassin.
|
|
|
|
=head2 TAGGING FOR SPAM MAILS
|
|
|
|
By default, all messages with a calculated score of 5.0 or higher are
|
|
tagged as spam.
|
|
|
|
If an incoming message is tagged as spam, instead of modifying the
|
|
original message, SpamAssassin will create a new report message and
|
|
attach the original message as a message/rfc822 MIME part (ensuring the
|
|
original message is completely preserved and easier to recover).
|
|
|
|
The new report message inherits the following headers (if they are
|
|
present) from the original spam message:
|
|
|
|
=over 4
|
|
|
|
=item From: header
|
|
|
|
=item To: header
|
|
|
|
=item Cc: header
|
|
|
|
=item Subject: header
|
|
|
|
=item Date: header
|
|
|
|
=item Message-ID: header
|
|
|
|
=back
|
|
|
|
The above headers can be modified if the relevant C<rewrite_header>
|
|
option is given (see C<Mail::SpamAssassin::Conf> for more information).
|
|
|
|
By default these message headers are added to spam:
|
|
|
|
=over 4
|
|
|
|
=item X-Spam-Flag: header
|
|
|
|
Set to C<YES>.
|
|
|
|
=back
|
|
|
|
The headers that added are fully configurable via the C<add_header>
|
|
option (see C<Mail::SpamAssassin::Conf> for more information).
|
|
|
|
=over 4
|
|
|
|
=item spam mail body text
|
|
|
|
The SpamAssassin report is added to top of the mail message body,
|
|
if the message is marked as spam.
|
|
|
|
=back
|
|
|
|
=head2 DEFAULT TAGGING FOR ALL MAILS
|
|
|
|
These headers are added to all messages, both spam and ham (non-spam).
|
|
|
|
=over 4
|
|
|
|
=item X-Spam-Checker-Version: header
|
|
|
|
The version and subversion of SpamAssassin and the host where
|
|
SpamAssassin was run.
|
|
|
|
=item X-Spam-Level: header
|
|
|
|
A series of "*" characters where each one represents a full score point.
|
|
|
|
=item X-Spam-Status: header
|
|
|
|
A string, C<(Yes|No), score=nn required=nn tests=xxx,xxx
|
|
autolearn=(ham|spam|no|unavailable|failed)> is set in this header to
|
|
reflect the filter status. For the first word, "Yes" means spam and
|
|
"No" means ham (non-spam).
|
|
|
|
=back
|
|
|
|
The headers that added are fully configurable via the C<add_header>
|
|
option (see C<Mail::SpamAssassin::Conf> for more information).
|
|
|
|
=head1 INSTALLATION
|
|
|
|
The B<spamassassin> command is part of the B<Mail::SpamAssassin> Perl module.
|
|
Install this as a normal Perl module, using C<perl -MCPAN -e shell>, or by
|
|
hand.
|
|
|
|
Note that it is not possible to use the C<PERL5LIB> environment variable
|
|
to affect where SpamAssassin finds its perl modules, due to limitations
|
|
imposed by perl's "taint" security checks.
|
|
|
|
For further details on how to install, please read the C<INSTALL> file
|
|
from the SpamAssassin distribution.
|
|
|
|
=head1 DEVELOPER DOCUMENTATION
|
|
|
|
Mail::SpamAssassin
|
|
Spam detector and markup engine
|
|
|
|
Mail::SpamAssassin::ArchiveIterator
|
|
find and process messages one at a time
|
|
|
|
Mail::SpamAssassin::AutoWelcomelist
|
|
auto-welcomelist handler for SpamAssassin
|
|
|
|
Mail::SpamAssassin::Bayes
|
|
determine spammishness using a Bayesian classifier
|
|
|
|
Mail::SpamAssassin::BayesStore
|
|
Bayesian Storage Module
|
|
|
|
Mail::SpamAssassin::BayesStore::SQL
|
|
SQL Bayesian Storage Module Implementation
|
|
|
|
Mail::SpamAssassin::Conf::LDAP
|
|
load SpamAssassin scores from LDAP database
|
|
|
|
Mail::SpamAssassin::Conf::Parser
|
|
parse SpamAssassin configuration
|
|
|
|
Mail::SpamAssassin::Conf::SQL
|
|
load SpamAssassin scores from SQL database
|
|
|
|
Mail::SpamAssassin::Message
|
|
decode, render, and hold an RFC-2822 message
|
|
|
|
Mail::SpamAssassin::Message::Metadata
|
|
extract metadata from a message
|
|
|
|
Mail::SpamAssassin::Message::Node
|
|
decode, render, and make available MIME message parts
|
|
|
|
Mail::SpamAssassin::PerMsgLearner
|
|
per-message status (spam or not-spam)
|
|
|
|
Mail::SpamAssassin::PerMsgStatus
|
|
per-message status (spam or not-spam)
|
|
|
|
Mail::SpamAssassin::PersistentAddrList
|
|
persistent address list base class
|
|
|
|
Mail::SpamAssassin::Plugin
|
|
SpamAssassin plugin base class
|
|
|
|
Mail::SpamAssassin::Plugin::RelayCountry
|
|
add message metadata indicating the country code of each relay
|
|
|
|
Mail::SpamAssassin::Plugin::SPF
|
|
perform SPF verification tests
|
|
|
|
Mail::SpamAssassin::Plugin::URIDNSBL
|
|
look up URLs against DNS blocklists
|
|
|
|
Mail::SpamAssassin::SQLBasedAddrList
|
|
SpamAssassin SQL Based Auto Welcomelist
|
|
|
|
=head1 BUGS
|
|
|
|
See E<lt>https://issues.apache.org/SpamAssassin/E<gt>
|
|
|
|
=head1 AUTHORS
|
|
|
|
The SpamAssassin(tm) Project E<lt>https://spamassassin.apache.org/E<gt>
|
|
|
|
=head1 COPYRIGHT AND LICENSE
|
|
|
|
SpamAssassin is distributed under the Apache License, Version 2.0, as
|
|
described in the file C<LICENSE> included with the distribution.
|
|
|
|
Copyright (C) 2015 The Apache Software Foundation
|
|
|