]>
Commit | Line | Data |
---|---|---|
37ef5775 SI |
1 | #!/usr/bin/perl -w |
2 | ||
3 | # <@LICENSE> | |
4 | # Licensed to the Apache Software Foundation (ASF) under one or more | |
5 | # contributor license agreements. See the NOTICE file distributed with | |
6 | # this work for additional information regarding copyright ownership. | |
7 | # The ASF licenses this file to you under the Apache License, Version 2.0 | |
8 | # (the "License"); you may not use this file except in compliance with | |
9 | # the License. You may obtain a copy of the License at: | |
10 | # | |
11 | # http://www.apache.org/licenses/LICENSE-2.0 | |
12 | # | |
13 | # Unless required by applicable law or agreed to in writing, software | |
14 | # distributed under the License is distributed on an "AS IS" BASIS, | |
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
16 | # See the License for the specific language governing permissions and | |
17 | # limitations under the License. | |
18 | # </@LICENSE> | |
19 | ||
20 | use strict; | |
21 | use warnings; | |
22 | use re 'taint'; | |
23 | ||
24 | use File::Spec; | |
25 | ||
26 | my $PREFIX = '@@PREFIX@@'; # substituted at 'make' time | |
27 | my $DEF_RULES_DIR = '@@DEF_RULES_DIR@@'; # substituted at 'make' time | |
28 | my $LOCAL_RULES_DIR = '@@LOCAL_RULES_DIR@@'; # substituted at 'make' time | |
29 | my $LOCAL_STATE_DIR = '@@LOCAL_STATE_DIR@@'; # substituted at 'make' time | |
30 | ||
31 | use lib '@@INSTALLSITELIB@@'; # substituted at 'make' time | |
32 | ||
33 | BEGIN { | |
34 | # Locate locally installed SA libraries *without* using FindBin, which | |
35 | # generates warnings and causes more trouble than its worth. We don't | |
36 | # need to be too smart about this BTW. | |
37 | my @bin = File::Spec->splitpath($0); | |
38 | my $bin = ( | |
39 | $bin[0] | |
40 | ? File::Spec->catpath( @bin[ 0 .. 1 ], '' ) | |
41 | : $bin[1] | |
42 | ) # /home/jm/foo -> /home/jm | |
43 | || File::Spec->curdir; # foo -> . | |
44 | ||
45 | # check to make sure it wasn't just installed in the normal way. | |
46 | # note that ./lib/Mail/SpamAssassin.pm takes precedence, for | |
47 | # building SpamAssassin on a machine where an old version is installed. | |
48 | if (-e $bin.'/lib/Mail/SpamAssassin.pm' | |
49 | || !-e '@@INSTALLSITELIB@@/Mail/SpamAssassin.pm' ) | |
50 | { | |
51 | my $searchrelative; | |
52 | $searchrelative = 1; # disabled during "make install": REMOVEFORINST | |
53 | ||
54 | # Firstly, are we running "make test" in the "t" dir? the test files | |
55 | # *need* to use 'blib', so that 'use bytes' is removed for pre-5.6 perls | |
56 | # beforehand by the preproc. However, ./spamassassin does not, as the | |
57 | # preproc will have stripped out the "use rule files from cwd" code from | |
58 | # Mail::SpamAssassin. So we want to use blib just for the t scripts. | |
59 | # This is disabled during the "make install" process. | |
60 | if ($searchrelative && $bin eq '../' && -e '../blib/lib/Mail/SpamAssassin.pm') | |
61 | { | |
62 | unshift ( @INC, '../blib/lib' ); | |
63 | } else { | |
64 | # These are common paths where the SA libs might be found. | |
65 | foreach ( qw(lib ../lib/site_perl | |
66 | ../lib/spamassassin ../share/spamassassin/lib)) | |
67 | { | |
68 | my $dir = File::Spec->catdir( $bin, split ( '/', $_ ) ); | |
69 | if ( -f File::Spec->catfile( $dir, "Mail", "SpamAssassin.pm" ) ) | |
70 | { unshift ( @INC, $dir ); last; } | |
71 | } | |
72 | } | |
73 | } | |
74 | } | |
75 | ||
76 | use Getopt::Long; | |
77 | use Pod::Usage; | |
78 | use POSIX qw(locale_h setsid sigprocmask _exit); | |
79 | use Mail::SpamAssassin; | |
80 | use Mail::SpamAssassin::ArchiveIterator; | |
81 | use Mail::SpamAssassin::Util::Progress; | |
82 | use Mail::SpamAssassin::Logger qw(log_message); | |
83 | ||
84 | BEGIN { | |
85 | # redirect __WARN__, but NOT until after the | |
86 | # Mail::SpamAssassin::Logger class has been parsed. | |
87 | # do not trap warnings here based on eval scope; evals are very | |
88 | # common throughout. die()s can be trapped in future though. | |
89 | $SIG{__WARN__} = sub { | |
90 | log_message("warn", $_[0]); | |
91 | }; | |
92 | }; | |
93 | ||
94 | POSIX::setlocale(LC_TIME,'C'); | |
95 | ||
96 | my %resphash = ( | |
97 | EX_OK => 0, # no problems | |
98 | EX_USAGE => 64, # command line usage error | |
99 | EX_DATAERR => 65, # data format error | |
100 | EX_NOINPUT => 66, # cannot open input | |
101 | EX_NOUSER => 67, # addressee unknown | |
102 | EX_NOHOST => 68, # host name unknown | |
103 | EX_UNAVAILABLE => 69, # service unavailable | |
104 | EX_SOFTWARE => 70, # internal software error | |
105 | EX_OSERR => 71, # system error (e.g., can't fork) | |
106 | EX_OSFILE => 72, # critical OS file missing | |
107 | EX_CANTCREAT => 73, # can't create (user) output file | |
108 | EX_IOERR => 74, # input/output error | |
109 | EX_TEMPFAIL => 75, # temp failure; user is invited to retry | |
110 | EX_PROTOCOL => 76, # remote error in protocol | |
111 | EX_NOPERM => 77, # permission denied | |
112 | EX_CONFIG => 78, # configuration error | |
113 | ); | |
114 | ||
115 | ||
116 | sub print_version { | |
117 | printf("SpamAssassin version %s\n running on Perl version %s\n", | |
118 | Mail::SpamAssassin::Version(), | |
119 | join(".", map( 0+($_||0), ($] =~ /(\d)\.(\d{3})(\d{3})?/ )))) | |
120 | or die "error writing: $!"; | |
121 | } | |
122 | ||
123 | sub print_usage_and_exit { | |
124 | my ( $message, $respnam ) = @_; | |
125 | $respnam ||= 'EX_USAGE'; | |
126 | ||
127 | if ($respnam eq 'EX_OK' ) { | |
128 | print_version(); | |
129 | print("\n") or die "error writing: $!"; | |
130 | } | |
131 | pod2usage( | |
132 | -verbose => 0, | |
133 | -message => $message, | |
134 | -exitval => $resphash{$respnam}, | |
135 | -input => "spamassassin-run.pod", | |
136 | -pathlist => \@INC, | |
137 | ); | |
138 | } | |
139 | ||
140 | ||
141 | ||
142 | sub usage { | |
143 | my ( $verbose, $message ) = @_; | |
144 | my $ver = Mail::SpamAssassin::Version(); | |
145 | ||
146 | print "SpamAssassin version $ver\n" or die "error writing: $!"; | |
147 | pod2usage( -verbose => $verbose, -message => $message, -exitval => 64, -input => "spamassassin-run.pod", -pathlist => \@INC ); | |
148 | ||
149 | } | |
150 | ||
151 | # Check to make sure the script version and the module version matches. | |
152 | # If not, die here! Also, deal with unchanged VERSION macro. | |
153 | if ($Mail::SpamAssassin::VERSION ne '@@VERSION@@' && '@@VERSION@@' ne "\@\@VERSION\@\@") { | |
154 | die 'spamassassin: spamassassin script is v@@VERSION@@, but using modules v'.$Mail::SpamAssassin::VERSION."\n"; | |
155 | } | |
156 | ||
157 | # by default: | |
158 | # - create user preference files | |
159 | # - have ArchiveIterator detect the input message format (file vs dir) | |
160 | # | |
161 | my %opt = ( 'create-prefs' => 1, 'format' => 'detect', cf => [] ); | |
162 | ||
163 | my $doing_whitelist_operation = 0; | |
164 | my $count = 0; | |
165 | my @targets = (); | |
166 | my $exitvalue; | |
167 | ||
168 | my $init_results = 0; | |
169 | my $progress; | |
170 | my $total_messages = 0; | |
171 | ||
172 | # gnu_getopt is not available in Getopt::Long 2.24, see bug 732 | |
173 | # gnu_compat neither. | |
174 | Getopt::Long::Configure( | |
175 | qw(bundling no_getopt_compat no_auto_abbrev no_ignore_case)); | |
176 | GetOptions( | |
177 | 'add-addr-to-blacklist=s' => \$opt{'add-addr-to-blacklist'}, | |
178 | 'add-addr-to-whitelist=s' => \$opt{'add-addr-to-whitelist'}, | |
179 | 'add-to-blacklist' => \$opt{'add-to-blacklist'}, | |
180 | 'add-to-whitelist|W' => \$opt{'add-to-whitelist'}, | |
181 | 'configpath|config-file|config-dir|c|C=s' => \$opt{'configpath'}, | |
182 | 'create-prefs!' => \$opt{'create-prefs'}, | |
183 | 'cf=s' => \@{$opt{'cf'}}, | |
184 | 'debug|D:s' => \$opt{'debug'}, | |
185 | 'error-code|exit-code|e:i' => \$opt{'error-code'}, | |
186 | 'help|h|?' => \$opt{'help'}, | |
187 | '4|ipv4only|ipv4-only|ipv4' => sub { $opt{'force_ipv4'} = 1; | |
188 | $opt{'force_ipv6'} = 0; }, | |
189 | '6' => sub { $opt{'force_ipv6'} = 1; | |
190 | $opt{'force_ipv4'} = 0; }, | |
191 | 'lint' => \$opt{'lint'}, | |
192 | 'local-only|local|L' => \$opt{'local'}, | |
193 | 'mbox' => sub { $opt{'format'} = 'mbox'; }, | |
194 | 'mbx' => sub { $opt{'format'} = 'mbx'; }, | |
195 | 'prefspath|prefs-file|p=s' => \$opt{'prefspath'}, | |
196 | 'remove-addr-from-whitelist=s' => \$opt{'remove-addr-from-whitelist'}, | |
197 | 'remove-from-whitelist|R' => \$opt{'remove-from-whitelist'}, | |
198 | 'remove-markup|despamassassinify|d' => \$opt{'remove-markup'}, | |
199 | 'report|r' => \$opt{'report'}, | |
200 | 'revoke|k' => \$opt{'revoke'}, | |
201 | 'siteconfigpath=s' => \$opt{'siteconfigpath'}, | |
202 | 'test-mode|test|t' => \$opt{'test-mode'}, | |
203 | 'progress' => \$opt{'progress'}, | |
204 | 'version|V' => \$opt{'version'}, | |
205 | 'x' => sub { $opt{'create-prefs'} = 0 }, | |
206 | ||
207 | # | |
208 | # NOTE: These are old options. We should ignore (but warn about) | |
209 | # the ones that are now defaults. Everything else gets a die (see note2) | |
210 | # so the user doesn't get us doing something they didn't expect. | |
211 | # | |
212 | # NOTE2: 'die' doesn't actually stop the process, GetOptions() catches | |
213 | # it, then passes the error on, so we'll end up doing a Usage statement. | |
214 | # You can avoid that by doing an explicit exit in the sub. | |
215 | # | |
216 | ||
217 | # last in 2.3 | |
218 | 'pipe|P' => sub { warn "The -P option is deprecated as 'pipe mode' is now the default behavior, ignoring.\n" }, | |
219 | 'F:i' => sub { warn "The -F option has been removed from spamassassin, please remove from your commandline and re-run.\n"; exit 2; }, | |
220 | 'add-from!' => sub { warn "The --add-from option has been removed from spamassassin, please remove from your commandline and re-run.\n"; exit 2; }, | |
221 | ||
222 | # last in 2.4 | |
223 | 'stop-at-threshold|S' => sub { warn "The -S option has been deprecated and is no longer supported, ignoring.\n" }, | |
224 | ||
225 | # last in 2.6 | |
226 | 'log-to-mbox|l:s' => sub { warn "The -l option has been deprecated and is no longer supported, ignoring.\n" }, | |
227 | '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; }, | |
228 | '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; }, | |
229 | ||
230 | ) or print_usage_and_exit(); | |
231 | ||
232 | if ( defined $opt{'help'} ) { | |
233 | print_usage_and_exit("For more information read the spamassassin man page.\n", 'EX_OK'); | |
234 | } | |
235 | if ( defined $opt{'version'} ) { | |
236 | print_version(); | |
237 | exit($resphash{'EX_OK'}); | |
238 | } | |
239 | ||
240 | # set debug areas, if any specified (only useful for command-line tools) | |
241 | if (defined $opt{'debug'}) { | |
242 | $opt{'debug'} ||= 'all'; | |
243 | } | |
244 | ||
245 | if (Mail::SpamAssassin::Util::am_running_on_windows()) { | |
246 | binmode(STDIN) or die "cannot set binmode on STDIN: $!"; # bug 4363 | |
247 | binmode(STDOUT) or die "cannot set binmode on STDOUT: $!"; | |
248 | } | |
249 | ||
250 | # bug 5048: --lint should not cause network accesses | |
251 | if ($opt{'lint'}) { $opt{'local'} = 1; } | |
252 | ||
253 | # create the tester factory | |
254 | my $spamtest = new Mail::SpamAssassin( | |
255 | { | |
256 | rules_filename => $opt{'configpath'}, | |
257 | site_rules_filename => $opt{'siteconfigpath'}, | |
258 | userprefs_filename => $opt{'prefspath'}, | |
259 | force_ipv4 => $opt{'force_ipv4'}, | |
260 | force_ipv6 => $opt{'force_ipv6'}, | |
261 | local_tests_only => $opt{'local'}, | |
262 | debug => $opt{'debug'}, | |
263 | dont_copy_prefs => ( $opt{'create-prefs'} ? 0 : 1 ), | |
264 | post_config_text => join("\n", @{$opt{'cf'}})."\n", | |
265 | require_rules => 1, | |
266 | PREFIX => $PREFIX, | |
267 | DEF_RULES_DIR => $DEF_RULES_DIR, | |
268 | LOCAL_RULES_DIR => $LOCAL_RULES_DIR, | |
269 | LOCAL_STATE_DIR => $LOCAL_STATE_DIR, | |
270 | } | |
271 | ); | |
272 | ||
273 | if ($opt{'lint'}) { | |
274 | $spamtest->debug_diagnostics(); | |
275 | my $res = $spamtest->lint_rules(); | |
276 | warn "lint: $res issues detected, please rerun with debug enabled for more information\n" if ($res and !$opt{'debug'}); | |
277 | # make sure we notice any write errors while flushing output buffer | |
278 | close STDOUT or die "error closing STDOUT: $!"; | |
279 | close STDIN or die "error closing STDIN: $!"; | |
280 | exit $res ? 1 : 0; | |
281 | } | |
282 | ||
283 | if ($opt{'remove-addr-from-whitelist'} || | |
284 | $opt{'add-addr-to-whitelist'} || | |
285 | $opt{'add-addr-to-blacklist'}) | |
286 | { | |
287 | $spamtest->init(1); | |
288 | ||
289 | if ( $opt{'add-addr-to-whitelist'} ) { | |
290 | $spamtest->add_address_to_whitelist($opt{'add-addr-to-whitelist'}, 1); | |
291 | } | |
292 | elsif ( $opt{'remove-addr-from-whitelist'} ) { | |
293 | $spamtest->remove_address_from_whitelist($opt{'remove-addr-from-whitelist'}, 1); | |
294 | } | |
295 | elsif ( $opt{'add-addr-to-blacklist'} ) { | |
296 | $spamtest->add_address_to_blacklist($opt{'add-addr-to-blacklist'}, 1); | |
297 | } | |
298 | else { | |
299 | die "spamassassin: oops! unhandled whitelist operation"; | |
300 | } | |
301 | ||
302 | $spamtest->finish(); | |
303 | # make sure we notice any write errors while flushing output buffer | |
304 | close STDOUT or die "error closing STDOUT: $!"; | |
305 | close STDIN or die "error closing STDIN: $!"; | |
306 | exit(0); | |
307 | } | |
308 | ||
309 | # if we're going to do white/black-listing, let's prep now... | |
310 | if ( $opt{'remove-from-whitelist'} | |
311 | or $opt{'add-to-whitelist'} | |
312 | or $opt{'add-to-blacklist'} ) | |
313 | { | |
314 | $doing_whitelist_operation = 1; | |
315 | $spamtest->init(1); | |
316 | } | |
317 | ||
318 | # if we're doing things in test mode, force disable long-term memory | |
319 | # functions like autowhitelist and bayes autolearn. | |
320 | # XXX - feels like we need a plugin hook here so plugins can be made | |
321 | # aware and take appropriate action. | |
322 | if ($opt{'test-mode'}) { | |
323 | $spamtest->{'conf'}->{'use_auto_whitelist'} = 0; | |
324 | $spamtest->{'conf'}->{'bayes_auto_learn'} = 0; | |
325 | } | |
326 | ||
327 | ########################################################################### | |
328 | # Deal with the target listing, and STDIN -> tempfile | |
329 | ||
330 | my $tempfile; # will be defined if stdin -> tempfile | |
331 | push(@targets, @ARGV); | |
332 | @targets = ('-') unless @targets; | |
333 | ||
334 | for(my $elem = 0; $elem <= $#targets; $elem++) { | |
335 | # ArchiveIterator doesn't really like STDIN, so if "-" is specified | |
336 | # as a target, make it a temp file instead. | |
337 | if ( $targets[$elem] =~ /(?:^|:)-$/ ) { | |
338 | if (defined $tempfile) { | |
339 | # uh-oh, stdin specified multiple times? | |
340 | warn "skipping extra stdin target (".$targets[$elem].")\n"; | |
341 | splice @targets, $elem, 1; | |
342 | $elem--; # go back to this element again | |
343 | next; | |
344 | } | |
345 | else { | |
346 | my $handle; | |
347 | ( $tempfile, $handle ) = Mail::SpamAssassin::Util::secure_tmpfile(); | |
348 | binmode $handle or die "cannot set binmode on file $tempfile: $!"; | |
349 | ||
350 | # avoid slurping the whole file into memory, copy chunk by chunk | |
351 | my($inbuf,$nread,$nwrites); | |
352 | while ( $nread = sysread(STDIN, $inbuf, 32*1024) ) { | |
353 | for (my $ofs = 0; $ofs < length($inbuf); $ofs += $nwrites) { | |
354 | $nwrites = $handle->syswrite($inbuf, length($inbuf)-$ofs, $ofs); | |
355 | defined $nwrites or die "error writing to $tempfile: $!"; | |
356 | } | |
357 | } | |
358 | undef $inbuf; # release storage | |
359 | defined $nread or die "error reading from STDIN: $!"; | |
360 | close $handle or die "cannot close $tempfile: $!"; | |
361 | ||
362 | # re-aim the targets at the tempfile instead of STDIN | |
363 | $targets[$elem] =~ s/-$/$tempfile/; | |
364 | } | |
365 | } | |
366 | ||
367 | # make sure the target list is in the normal AI format | |
368 | if ($targets[$elem] !~ /^[^:]*:[a-z]+:/) { | |
369 | my $format = $opt{'format'} || 'detect'; | |
370 | $targets[$elem] = join ( ":", '', $format, $targets[$elem] ); | |
371 | } | |
372 | } | |
373 | ||
374 | ########################################################################### | |
375 | ||
376 | setup_sig_handlers(); | |
377 | ||
378 | # Everything below here needs ArchiveIterator ... | |
379 | my $iter = new Mail::SpamAssassin::ArchiveIterator( | |
380 | { | |
381 | 'opt_max_size' => 0, # no limit | |
382 | 'opt_want_date' => 0 | |
383 | } | |
384 | ); | |
385 | ||
386 | $iter->set_functions( \&wanted, \&result ); | |
387 | ||
388 | # Go run the messages! | |
389 | # bug 4930: use a temp variable since "||=" decides whether or not to set the | |
390 | # value before the RHS is actually run, so if the RHS separately sets the LHS | |
391 | # variable, things don't work right. Stupid global variables. ;) | |
392 | my $eval_stat; | |
393 | eval { | |
394 | my $runreturn = !$iter->run(@targets); $exitvalue ||= $runreturn; 1; | |
395 | } or do { | |
396 | $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat; | |
397 | }; | |
398 | ||
399 | $progress->final() if ($opt{progress} && $progress); | |
400 | ||
401 | # If we needed to make a tempfile, go delete it now. | |
402 | if (defined $tempfile) { | |
403 | unlink $tempfile or die "cannot unlink temporary file $tempfile: $!"; | |
404 | undef $tempfile; | |
405 | } | |
406 | ||
407 | # Let folks know how many messages were handled, as long as the handling | |
408 | # didn't produce output (ala: check, test, or remove_markup ...) | |
409 | if ( $opt{'report'} || $opt{'revoke'} || $doing_whitelist_operation ) { | |
410 | print "$count message(s) examined.\n" or die "error writing: $!"; | |
411 | } | |
412 | ||
413 | # if the eval died from something, report it here and return an error. | |
414 | if (defined $eval_stat) { die $eval_stat; } | |
415 | ||
416 | $spamtest->finish() if $spamtest; | |
417 | ||
418 | # make sure we notice any write errors while flushing output buffer | |
419 | close STDOUT or die "error closing STDOUT: $!"; | |
420 | close STDIN or die "error closing STDIN: $!"; | |
421 | # Ok, exit! | |
422 | exit( $exitvalue || 0 ); | |
423 | ||
424 | ########################################################################### | |
425 | ||
426 | sub init_results { | |
427 | $init_results = 1; | |
428 | ||
429 | return unless $opt{'progress'}; | |
430 | ||
431 | $total_messages = $Mail::SpamAssassin::ArchiveIterator::MESSAGES; | |
432 | ||
433 | $progress = Mail::SpamAssassin::Util::Progress->new({total => $total_messages,}); | |
434 | } | |
435 | ||
436 | ########################################################################### | |
437 | ||
438 | sub result { | |
439 | my ($class, $result, $time) = @_; | |
440 | ||
441 | # don't open results files until we get here to avoid overwriting files | |
442 | &init_results if !$init_results; | |
443 | ||
444 | $progress->update($count) if ($opt{progress} && $progress); | |
445 | } | |
446 | ||
447 | ########################################################################### | |
448 | ||
449 | my $mail; # global, so signal handler can clean it up; bug 5626 | |
450 | ||
451 | # make sure it only returns false values so that result_sub() isn't called... | |
452 | sub wanted { | |
453 | $spamtest->timer_reset; # reset timers for each AI message | |
454 | my $dataref = $_[3]; | |
455 | $mail = $spamtest->parse($dataref); | |
456 | $count++; | |
457 | ||
458 | # This is a short cut -- doing white/black-list? Do it and return quickly. | |
459 | if ($doing_whitelist_operation) { | |
460 | if ( $opt{'add-to-whitelist'} ) { | |
461 | $spamtest->add_all_addresses_to_whitelist($mail, 1); | |
462 | } | |
463 | elsif ( $opt{'remove-from-whitelist'} ) { | |
464 | $spamtest->remove_all_addresses_from_whitelist($mail, 1); | |
465 | } | |
466 | elsif ( $opt{'add-to-blacklist'} ) { | |
467 | $spamtest->add_all_addresses_to_blacklist($mail, 1); | |
468 | } | |
469 | else { | |
470 | warn "spamassassin: oops! unhandled whitelist operation"; | |
471 | } | |
472 | ||
473 | $mail->finish(); | |
474 | $mail = undef; | |
475 | return 1; | |
476 | } | |
477 | ||
478 | # handle removing reports | |
479 | if ( $opt{'remove-markup'} ) { | |
480 | ||
481 | # If we're not going to retest, just remove the markup and print it out | |
482 | if ( !$opt{'test-mode'} ) { | |
483 | print $spamtest->remove_spamassassin_markup ($mail); | |
484 | $mail->finish(); | |
485 | $mail = undef; | |
486 | return 1; | |
487 | } | |
488 | else { | |
489 | ||
490 | # remove the markup and retest it... a little more tricky ... | |
491 | # go ahead and remove the markup, then fake that the clean version | |
492 | # was what was sent in | |
493 | # | |
494 | my $new_mail = | |
495 | $spamtest->parse( $spamtest->remove_spamassassin_markup($mail) ); | |
496 | ||
497 | $mail->finish(); | |
498 | $mail = $new_mail; | |
499 | } | |
500 | } | |
501 | ||
502 | # handle reporting and revoking | |
503 | if ( $opt{'report'} || $opt{'revoke'} ) { | |
504 | ||
505 | # Make sure the message is clean first ... | |
506 | my $new_mail = | |
507 | $spamtest->parse( $spamtest->remove_spamassassin_markup($mail) ); | |
508 | $mail->finish(); | |
509 | $mail = $new_mail; | |
510 | ||
511 | my $failed; | |
512 | if ( $opt{'report'} && !$spamtest->report_as_spam($mail) ) { | |
513 | $failed = 'report'; | |
514 | } | |
515 | ||
516 | if ( $opt{'revoke'} && !$spamtest->revoke_as_spam($mail) ) { | |
517 | $failed = 'revoke'; | |
518 | } | |
519 | ||
520 | if ($failed) { | |
521 | warn "spamassassin: warning, unable to $failed message\n"; | |
522 | warn "spamassassin: for more information, re-run with -D option to see debug output\n"; | |
523 | } | |
524 | ||
525 | $mail->finish(); | |
526 | $mail = undef; | |
527 | return 1; | |
528 | } | |
529 | ||
530 | # OK, do checks and put out the message. | |
531 | my $status = $spamtest->check($mail); | |
532 | print $status->rewrite_mail() or die "error writing: $!"; | |
533 | ||
534 | if ( $opt{'test-mode'} ) { | |
535 | print $status->get_report() or die "error writing: $!"; | |
536 | } | |
537 | ||
538 | # if this message was spam, set the exit value appropriately. | |
539 | if ( defined $opt{'error-code'} && $status->is_spam() && !defined $exitvalue ) | |
540 | { | |
541 | $exitvalue = $opt{'error-code'} || 5; | |
542 | } | |
543 | ||
544 | # clean up after ourselves | |
545 | $mail->finish(); | |
546 | $mail = undef; | |
547 | ||
548 | $status->finish(); | |
549 | ||
550 | return 1; | |
551 | } | |
552 | ||
553 | ########################################################################### | |
554 | ||
555 | sub setup_sig_handlers { | |
556 | $SIG{HUP} = \&kill_handler; | |
557 | $SIG{INT} = \&kill_handler; | |
558 | $SIG{TERM} = \&kill_handler; | |
559 | # $SIG{PIPE} = \&kill_handler; | |
560 | $SIG{PIPE} = 'IGNORE'; | |
561 | } | |
562 | ||
563 | sub kill_handler { | |
564 | my ($sig) = @_; | |
565 | warn "spamassassin: killed by SIG$sig\n"; | |
566 | if ($mail) { | |
567 | $mail->finish(); # bug 5626: remove temp files etc. | |
568 | $mail = undef; | |
569 | } | |
570 | if (defined $tempfile) { # bug 5557: additional paranoia about tmpfiles | |
571 | unlink $tempfile or warn "cannot unlink temporary file $tempfile: $!"; | |
572 | undef $tempfile; | |
573 | } | |
574 | close STDOUT; close STDIN; # ignoring status | |
575 | exit 15; | |
576 | } | |
577 | ||
578 | __END__ | |
579 | ||
580 | # --------------------------------------------------------------------------- | |
581 | ||
582 | =head1 NAME | |
583 | ||
584 | spamassassin - extensible email filter used to identify spam | |
585 | ||
586 | =head1 DESCRIPTION | |
587 | ||
588 | SpamAssassin is an intelligent email filter which uses a diverse range of | |
589 | tests to identify unsolicited bulk email, more commonly known as "spam". | |
590 | These tests are applied to email headers and content to classify email | |
591 | using advanced statistical methods. In addition, SpamAssassin has a | |
592 | modular architecture that allows other technologies to be quickly wielded | |
593 | against spam and is designed for easy integration into virtually any email | |
594 | system. | |
595 | ||
596 | =head1 SYNOPSIS | |
597 | ||
598 | For ease of access, the SpamAssassin manual has been split up into | |
599 | several sections. If you're intending to read these straight through | |
600 | for the first time, the suggested order will tend to reduce the number | |
601 | of forward references. | |
602 | ||
603 | Extensive additional documentation for SpamAssassin is available, | |
604 | primarily on the SpamAssassin web site and wiki. | |
605 | ||
606 | You should be able to view SpamAssassin's documentation with your man(1) | |
607 | program or perldoc(1). | |
608 | ||
609 | =head2 OVERVIEW | |
610 | ||
611 | spamassassin SpamAssassin overview (this section) | |
612 | ||
613 | =head2 CONFIGURATION | |
614 | ||
615 | Mail::SpamAssassin::Conf SpamAssassin configuration files | |
616 | ||
617 | =head2 USAGE | |
618 | ||
619 | spamassassin-run "spamassassin" front-end filtering script | |
620 | sa-learn train SpamAssassin's Bayesian classifier | |
621 | spamc client for spamd (faster than spamassassin) | |
622 | spamd spamassassin server (needed by spamc) | |
623 | ||
624 | =head2 DEFAULT PLUGINS | |
625 | ||
626 | @@PLUGIN_POD@@ | |
627 | ||
628 | =head1 WEB SITES | |
629 | ||
630 | SpamAssassin web site: http://spamassassin.apache.org/ | |
631 | Wiki-based documentation: http://wiki.apache.org/spamassassin/ | |
632 | ||
633 | =head1 USER MAILING LIST | |
634 | ||
635 | A users mailing list exists where other experienced users are often able | |
636 | to help and provide tips and advice. Subscription instructions are | |
637 | located on the SpamAssassin web site. | |
638 | ||
639 | =head1 CONFIGURATION FILES | |
640 | ||
641 | The SpamAssassin rule base, text templates, and rule description text | |
642 | are loaded from configuration files. | |
643 | ||
644 | Default configuration data is loaded from the first existing directory | |
645 | in: | |
646 | ||
647 | =over 4 | |
648 | ||
649 | =item @@LOCAL_STATE_DIR@@/@@VERSION@@ | |
650 | ||
651 | =item @@DEF_RULES_DIR@@ | |
652 | ||
653 | =item @@PREFIX@@/share/spamassassin | |
654 | ||
655 | =item /usr/local/share/spamassassin | |
656 | ||
657 | =item /usr/share/spamassassin | |
658 | ||
659 | =back | |
660 | ||
661 | Site-specific configuration data is used to override any values which had | |
662 | already been set. This is loaded from the first existing directory in: | |
663 | ||
664 | =over 4 | |
665 | ||
666 | =item @@LOCAL_RULES_DIR@@ | |
667 | ||
668 | =item @@PREFIX@@/etc/mail/spamassassin | |
669 | ||
670 | =item @@PREFIX@@/etc/spamassassin | |
671 | ||
672 | =item /usr/local/etc/spamassassin | |
673 | ||
674 | =item /usr/pkg/etc/spamassassin | |
675 | ||
676 | =item /usr/etc/spamassassin | |
677 | ||
678 | =item /etc/mail/spamassassin | |
679 | ||
680 | =item /etc/spamassassin | |
681 | ||
682 | =back | |
683 | ||
684 | From those directories, SpamAssassin will first read files ending in | |
685 | ".pre" in lexical order and then it will read files ending in ".cf" in | |
686 | lexical order (most files begin with two numbers to make the sorting | |
687 | order obvious). | |
688 | ||
689 | In other words, it will read F<init.pre> first, then F<10_default_prefs.cf> before | |
690 | F<50_scores.cf> and F<20_body_tests.cf> before F<20_head_tests.cf>. | |
691 | Options in later files will override earlier files. | |
692 | ||
693 | Individual user preferences are loaded from the location specified on | |
694 | the C<spamassassin>, C<sa-learn>, or C<spamd> command line (see respective | |
695 | manual page for details). If the location is not specified, | |
696 | F<~/.spamassassin/user_prefs> is used if it exists. SpamAssassin will | |
697 | create that file if it does not already exist, using | |
698 | F<user_prefs.template> as a template. That file will be looked for in: | |
699 | ||
700 | =over 4 | |
701 | ||
702 | =item @@LOCAL_RULES_DIR@@ | |
703 | ||
704 | =item @@PREFIX@@/etc/mail/spamassassin | |
705 | ||
706 | =item @@PREFIX@@/share/spamassassin | |
707 | ||
708 | =item /etc/spamassassin | |
709 | ||
710 | =item /etc/mail/spamassassin | |
711 | ||
712 | =item /usr/local/share/spamassassin | |
713 | ||
714 | =item /usr/share/spamassassin | |
715 | ||
716 | =back | |
717 | ||
718 | =head1 TAGGING | |
719 | ||
720 | The following two sections detail the default tagging and markup that | |
721 | takes place for messages when running C<spamassassin> or C<spamc> with | |
722 | C<spamd> in the default configuration. | |
723 | ||
724 | Note: before header modification and addition, all headers beginning | |
725 | with C<X-Spam-> are removed to prevent spammer mischief and also to | |
726 | avoid potential problems caused by prior invocations of SpamAssassin. | |
727 | ||
728 | =head2 TAGGING FOR SPAM MAILS | |
729 | ||
730 | By default, all messages with a calculated score of 5.0 or higher are | |
731 | tagged as spam. | |
732 | ||
733 | If an incoming message is tagged as spam, instead of modifying the | |
734 | original message, SpamAssassin will create a new report message and | |
735 | attach the original message as a message/rfc822 MIME part (ensuring the | |
736 | original message is completely preserved and easier to recover). | |
737 | ||
738 | The new report message inherits the following headers (if they are | |
739 | present) from the original spam message: | |
740 | ||
741 | =over 4 | |
742 | ||
743 | =item From: header | |
744 | ||
745 | =item To: header | |
746 | ||
747 | =item Cc: header | |
748 | ||
749 | =item Subject: header | |
750 | ||
751 | =item Date: header | |
752 | ||
753 | =item Message-ID: header | |
754 | ||
755 | =back | |
756 | ||
757 | The above headers can be modified if the relevant C<rewrite_header> | |
758 | option is given (see C<Mail::SpamAssassin::Conf> for more information). | |
759 | ||
760 | By default these message headers are added to spam: | |
761 | ||
762 | =over 4 | |
763 | ||
764 | =item X-Spam-Flag: header | |
765 | ||
766 | Set to C<YES>. | |
767 | ||
768 | =back | |
769 | ||
770 | The headers that added are fully configurable via the C<add_header> | |
771 | option (see C<Mail::SpamAssassin::Conf> for more information). | |
772 | ||
773 | =over 4 | |
774 | ||
775 | =item spam mail body text | |
776 | ||
777 | The SpamAssassin report is added to top of the mail message body, | |
778 | if the message is marked as spam. | |
779 | ||
780 | =back | |
781 | ||
782 | =head2 DEFAULT TAGGING FOR ALL MAILS | |
783 | ||
784 | These headers are added to all messages, both spam and ham (non-spam). | |
785 | ||
786 | =over 4 | |
787 | ||
788 | =item X-Spam-Checker-Version: header | |
789 | ||
790 | The version and subversion of SpamAssassin and the host where | |
791 | SpamAssassin was run. | |
792 | ||
793 | =item X-Spam-Level: header | |
794 | ||
795 | A series of "*" characters where each one represents a full score point. | |
796 | ||
797 | =item X-Spam-Status: header | |
798 | ||
799 | A string, C<(Yes|No), score=nn required=nn tests=xxx,xxx | |
800 | autolearn=(ham|spam|no|unavailable|failed)> is set in this header to | |
801 | reflect the filter status. For the first word, "Yes" means spam and | |
802 | "No" means ham (non-spam). | |
803 | ||
804 | =back | |
805 | ||
806 | The headers that added are fully configurable via the C<add_header> | |
807 | option (see C<Mail::SpamAssassin::Conf> for more information). | |
808 | ||
809 | =head1 INSTALLATION | |
810 | ||
811 | The B<spamassassin> command is part of the B<Mail::SpamAssassin> Perl module. | |
812 | Install this as a normal Perl module, using C<perl -MCPAN -e shell>, or by | |
813 | hand. | |
814 | ||
815 | Note that it is not possible to use the C<PERL5LIB> environment variable | |
816 | to affect where SpamAssassin finds its perl modules, due to limitations | |
817 | imposed by perl's "taint" security checks. | |
818 | ||
819 | For further details on how to install, please read the C<INSTALL> file | |
820 | from the SpamAssassin distribution. | |
821 | ||
822 | =head1 DEVELOPER DOCUMENTATION | |
823 | ||
824 | Mail::SpamAssassin | |
825 | Spam detector and markup engine | |
826 | ||
827 | Mail::SpamAssassin::ArchiveIterator | |
828 | find and process messages one at a time | |
829 | ||
830 | Mail::SpamAssassin::AutoWhitelist | |
831 | auto-whitelist handler for SpamAssassin | |
832 | ||
833 | Mail::SpamAssassin::Bayes | |
834 | determine spammishness using a Bayesian classifier | |
835 | ||
836 | Mail::SpamAssassin::BayesStore | |
837 | Bayesian Storage Module | |
838 | ||
839 | Mail::SpamAssassin::BayesStore::SQL | |
840 | SQL Bayesian Storage Module Implementation | |
841 | ||
842 | Mail::SpamAssassin::Conf::LDAP | |
843 | load SpamAssassin scores from LDAP database | |
844 | ||
845 | Mail::SpamAssassin::Conf::Parser | |
846 | parse SpamAssassin configuration | |
847 | ||
848 | Mail::SpamAssassin::Conf::SQL | |
849 | load SpamAssassin scores from SQL database | |
850 | ||
851 | Mail::SpamAssassin::Message | |
852 | decode, render, and hold an RFC-2822 message | |
853 | ||
854 | Mail::SpamAssassin::Message::Metadata | |
855 | extract metadata from a message | |
856 | ||
857 | Mail::SpamAssassin::Message::Node | |
858 | decode, render, and make available MIME message parts | |
859 | ||
860 | Mail::SpamAssassin::PerMsgLearner | |
861 | per-message status (spam or not-spam) | |
862 | ||
863 | Mail::SpamAssassin::PerMsgStatus | |
864 | per-message status (spam or not-spam) | |
865 | ||
866 | Mail::SpamAssassin::PersistentAddrList | |
867 | persistent address list base class | |
868 | ||
869 | Mail::SpamAssassin::Plugin | |
870 | SpamAssassin plugin base class | |
871 | ||
872 | Mail::SpamAssassin::Plugin::Hashcash | |
873 | perform hashcash verification tests | |
874 | ||
875 | Mail::SpamAssassin::Plugin::RelayCountry | |
876 | add message metadata indicating the country code of each relay | |
877 | ||
878 | Mail::SpamAssassin::Plugin::SPF | |
879 | perform SPF verification tests | |
880 | ||
881 | Mail::SpamAssassin::Plugin::URIDNSBL | |
882 | look up URLs against DNS blocklists | |
883 | ||
884 | Mail::SpamAssassin::SQLBasedAddrList | |
885 | SpamAssassin SQL Based Auto Whitelist | |
886 | ||
887 | =head1 BUGS | |
888 | ||
889 | See <http://issues.apache.org/SpamAssassin/> | |
890 | ||
891 | =head1 AUTHORS | |
892 | ||
893 | The SpamAssassin(tm) Project <http://spamassassin.apache.org/> | |
894 | ||
895 | =head1 COPYRIGHT AND LICENSE | |
896 | ||
897 | SpamAssassin is distributed under the Apache License, Version 2.0, as | |
898 | described in the file C<LICENSE> included with the distribution. | |
899 | ||
900 | Copyright (C) 2015 The Apache Software Foundation | |
901 |