| #!/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_whitelist_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-blacklist=s'                 => \$opt{'add-addr-to-blacklist'}, | 
 |   'add-addr-to-whitelist=s'                 => \$opt{'add-addr-to-whitelist'}, | 
 |   'add-to-blacklist'                        => \$opt{'add-to-blacklist'}, | 
 |   'add-to-whitelist|W'                      => \$opt{'add-to-whitelist'}, | 
 |   '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-whitelist=s'            => \$opt{'remove-addr-from-whitelist'}, | 
 |   'remove-from-whitelist|R'                 => \$opt{'remove-from-whitelist'}, | 
 |   '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'}, | 
 |     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(); | 
 |   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-whitelist'} || | 
 |     $opt{'add-addr-to-whitelist'} || | 
 |     $opt{'add-addr-to-blacklist'}) | 
 | { | 
 |   $spamtest->init(1); | 
 |  | 
 |   if ( $opt{'add-addr-to-whitelist'} ) { | 
 |     $spamtest->add_address_to_whitelist($opt{'add-addr-to-whitelist'}, 1); | 
 |   } | 
 |   elsif ( $opt{'remove-addr-from-whitelist'} ) { | 
 |     $spamtest->remove_address_from_whitelist($opt{'remove-addr-from-whitelist'}, 1); | 
 |   } | 
 |   elsif ( $opt{'add-addr-to-blacklist'} ) { | 
 |     $spamtest->add_address_to_blacklist($opt{'add-addr-to-blacklist'}, 1); | 
 |   } | 
 |   else { | 
 |     die "spamassassin: oops! unhandled whitelist 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 white/black-listing, let's prep now... | 
 | if ( $opt{'remove-from-whitelist'} | 
 |   or $opt{'add-to-whitelist'} | 
 |   or $opt{'add-to-blacklist'} ) | 
 | { | 
 |   $doing_whitelist_operation = 1; | 
 |   $spamtest->init(1); | 
 | } | 
 |  | 
 | # if we're doing things in test mode, force disable long-term memory | 
 | # functions like autowhitelist 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_whitelist'} = 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_whitelist_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 white/black-list?  Do it and return quickly. | 
 |   if ($doing_whitelist_operation) { | 
 |     if ( $opt{'add-to-whitelist'} ) { | 
 |       $spamtest->add_all_addresses_to_whitelist($mail, 1); | 
 |     } | 
 |     elsif ( $opt{'remove-from-whitelist'} ) { | 
 |       $spamtest->remove_all_addresses_from_whitelist($mail, 1); | 
 |     } | 
 |     elsif ( $opt{'add-to-blacklist'} ) { | 
 |       $spamtest->add_all_addresses_to_blacklist($mail, 1); | 
 |     } | 
 |     else { | 
 |       warn "spamassassin: oops! unhandled whitelist 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::AutoWhitelist | 
 | 	auto-whitelist 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 Whitelist | 
 |  | 
 | =head1 BUGS | 
 |  | 
 | See <https://issues.apache.org/SpamAssassin/> | 
 |  | 
 | =head1 AUTHORS | 
 |  | 
 | The SpamAssassin(tm) Project <https://spamassassin.apache.org/> | 
 |  | 
 | =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 | 
 |  |