]>
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 | # | |
ae52237f | 161 | my %opt = ( 'create-prefs' => 1, 'format' => 'detect', pre => [], cf => [] ); |
37ef5775 | 162 | |
ae52237f | 163 | my $doing_welcomelist_operation = 0; |
37ef5775 SI |
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( | |
ae52237f SI |
177 | 'add-addr-to-blocklist=s' => \$opt{'add-addr-to-blocklist'}, |
178 | 'add-addr-to-welcomelist=s' => \$opt{'add-addr-to-welcomelist'}, | |
179 | 'add-addr-to-blacklist=s' => \$opt{'add-addr-to-blocklist'}, # removed in 4.1 | |
180 | 'add-addr-to-whitelist=s' => \$opt{'add-addr-to-welcomelist'}, # removed in 4.1 | |
181 | 'add-to-blocklist' => \$opt{'add-to-blocklist'}, | |
182 | 'add-to-welcomelist|W' => \$opt{'add-to-welcomelist'}, | |
183 | 'add-to-blacklist' => \$opt{'add-to-blocklist'}, # removed in 4.1 | |
184 | 'add-to-whitelist' => \$opt{'add-to-welcomelist'}, # removed in 4.1 | |
f887dfc0 | 185 | 'username|u=s' => \$opt{'username'}, |
37ef5775 SI |
186 | 'configpath|config-file|config-dir|c|C=s' => \$opt{'configpath'}, |
187 | 'create-prefs!' => \$opt{'create-prefs'}, | |
ae52237f | 188 | 'pre=s' => \@{$opt{'pre'}}, |
37ef5775 SI |
189 | 'cf=s' => \@{$opt{'cf'}}, |
190 | 'debug|D:s' => \$opt{'debug'}, | |
191 | 'error-code|exit-code|e:i' => \$opt{'error-code'}, | |
192 | 'help|h|?' => \$opt{'help'}, | |
193 | '4|ipv4only|ipv4-only|ipv4' => sub { $opt{'force_ipv4'} = 1; | |
194 | $opt{'force_ipv6'} = 0; }, | |
195 | '6' => sub { $opt{'force_ipv6'} = 1; | |
196 | $opt{'force_ipv4'} = 0; }, | |
197 | 'lint' => \$opt{'lint'}, | |
ae52237f | 198 | 'net' => \$opt{'net'}, |
37ef5775 SI |
199 | 'local-only|local|L' => \$opt{'local'}, |
200 | 'mbox' => sub { $opt{'format'} = 'mbox'; }, | |
201 | 'mbx' => sub { $opt{'format'} = 'mbx'; }, | |
202 | 'prefspath|prefs-file|p=s' => \$opt{'prefspath'}, | |
ae52237f SI |
203 | 'remove-addr-from-welcomelist=s' => \$opt{'remove-addr-from-welcomelist'}, |
204 | 'remove-from-welcomelist|R' => \$opt{'remove-from-welcomelist'}, | |
205 | 'remove-addr-from-whitelist=s' => \$opt{'remove-addr-from-welcomelist'}, # removed in 4.1 | |
206 | 'remove-from-whitelist' => \$opt{'remove-from-welcomelist'}, # removed in 4.1 | |
37ef5775 SI |
207 | 'remove-markup|despamassassinify|d' => \$opt{'remove-markup'}, |
208 | 'report|r' => \$opt{'report'}, | |
209 | 'revoke|k' => \$opt{'revoke'}, | |
210 | 'siteconfigpath=s' => \$opt{'siteconfigpath'}, | |
211 | 'test-mode|test|t' => \$opt{'test-mode'}, | |
212 | 'progress' => \$opt{'progress'}, | |
213 | 'version|V' => \$opt{'version'}, | |
214 | 'x' => sub { $opt{'create-prefs'} = 0 }, | |
215 | ||
216 | # | |
217 | # NOTE: These are old options. We should ignore (but warn about) | |
218 | # the ones that are now defaults. Everything else gets a die (see note2) | |
219 | # so the user doesn't get us doing something they didn't expect. | |
220 | # | |
221 | # NOTE2: 'die' doesn't actually stop the process, GetOptions() catches | |
222 | # it, then passes the error on, so we'll end up doing a Usage statement. | |
223 | # You can avoid that by doing an explicit exit in the sub. | |
224 | # | |
225 | ||
226 | # last in 2.3 | |
227 | 'pipe|P' => sub { warn "The -P option is deprecated as 'pipe mode' is now the default behavior, ignoring.\n" }, | |
228 | 'F:i' => sub { warn "The -F option has been removed from spamassassin, please remove from your commandline and re-run.\n"; exit 2; }, | |
229 | 'add-from!' => sub { warn "The --add-from option has been removed from spamassassin, please remove from your commandline and re-run.\n"; exit 2; }, | |
230 | ||
231 | # last in 2.4 | |
232 | 'stop-at-threshold|S' => sub { warn "The -S option has been deprecated and is no longer supported, ignoring.\n" }, | |
233 | ||
234 | # last in 2.6 | |
235 | 'log-to-mbox|l:s' => sub { warn "The -l option has been deprecated and is no longer supported, ignoring.\n" }, | |
236 | '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; }, | |
237 | '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; }, | |
238 | ||
239 | ) or print_usage_and_exit(); | |
240 | ||
241 | if ( defined $opt{'help'} ) { | |
242 | print_usage_and_exit("For more information read the spamassassin man page.\n", 'EX_OK'); | |
243 | } | |
244 | if ( defined $opt{'version'} ) { | |
245 | print_version(); | |
246 | exit($resphash{'EX_OK'}); | |
247 | } | |
248 | ||
249 | # set debug areas, if any specified (only useful for command-line tools) | |
250 | if (defined $opt{'debug'}) { | |
251 | $opt{'debug'} ||= 'all'; | |
252 | } | |
253 | ||
254 | if (Mail::SpamAssassin::Util::am_running_on_windows()) { | |
255 | binmode(STDIN) or die "cannot set binmode on STDIN: $!"; # bug 4363 | |
256 | binmode(STDOUT) or die "cannot set binmode on STDOUT: $!"; | |
257 | } | |
258 | ||
259 | # bug 5048: --lint should not cause network accesses | |
ae52237f SI |
260 | # allow --net to override for testing |
261 | if ($opt{'lint'} && !$opt{'net'}) { $opt{'local'} = 1; } | |
37ef5775 SI |
262 | |
263 | # create the tester factory | |
ae52237f | 264 | my $spamtest = Mail::SpamAssassin->new( |
37ef5775 SI |
265 | { |
266 | rules_filename => $opt{'configpath'}, | |
267 | site_rules_filename => $opt{'siteconfigpath'}, | |
268 | userprefs_filename => $opt{'prefspath'}, | |
f887dfc0 | 269 | username => $opt{'username'}, |
37ef5775 SI |
270 | force_ipv4 => $opt{'force_ipv4'}, |
271 | force_ipv6 => $opt{'force_ipv6'}, | |
272 | local_tests_only => $opt{'local'}, | |
273 | debug => $opt{'debug'}, | |
274 | dont_copy_prefs => ( $opt{'create-prefs'} ? 0 : 1 ), | |
ae52237f | 275 | pre_config_text => join("\n", @{$opt{'pre'}})."\n", |
37ef5775 SI |
276 | post_config_text => join("\n", @{$opt{'cf'}})."\n", |
277 | require_rules => 1, | |
278 | PREFIX => $PREFIX, | |
279 | DEF_RULES_DIR => $DEF_RULES_DIR, | |
280 | LOCAL_RULES_DIR => $LOCAL_RULES_DIR, | |
281 | LOCAL_STATE_DIR => $LOCAL_STATE_DIR, | |
282 | } | |
283 | ); | |
284 | ||
285 | if ($opt{'lint'}) { | |
286 | $spamtest->debug_diagnostics(); | |
287 | my $res = $spamtest->lint_rules(); | |
ae52237f | 288 | $spamtest->finish(); |
37ef5775 SI |
289 | warn "lint: $res issues detected, please rerun with debug enabled for more information\n" if ($res and !$opt{'debug'}); |
290 | # make sure we notice any write errors while flushing output buffer | |
291 | close STDOUT or die "error closing STDOUT: $!"; | |
292 | close STDIN or die "error closing STDIN: $!"; | |
f887dfc0 | 293 | exit($res ? 1 : 0); |
37ef5775 SI |
294 | } |
295 | ||
ae52237f SI |
296 | if ($opt{'remove-addr-from-welcomelist'} || |
297 | $opt{'add-addr-to-welcomelist'} || | |
298 | $opt{'add-addr-to-blocklist'}) | |
37ef5775 SI |
299 | { |
300 | $spamtest->init(1); | |
301 | ||
ae52237f SI |
302 | if ( $opt{'add-addr-to-welcomelist'} ) { |
303 | $spamtest->add_address_to_welcomelist($opt{'add-addr-to-welcomelist'}, 1); | |
37ef5775 | 304 | } |
ae52237f SI |
305 | elsif ( $opt{'remove-addr-from-welcomelist'} ) { |
306 | $spamtest->remove_address_from_welcomelist($opt{'remove-addr-from-welcomelist'}, 1); | |
37ef5775 | 307 | } |
ae52237f SI |
308 | elsif ( $opt{'add-addr-to-blocklist'} ) { |
309 | $spamtest->add_address_to_blocklist($opt{'add-addr-to-blocklist'}, 1); | |
37ef5775 SI |
310 | } |
311 | else { | |
ae52237f | 312 | die "spamassassin: oops! unhandled welcomelist operation"; |
37ef5775 SI |
313 | } |
314 | ||
315 | $spamtest->finish(); | |
316 | # make sure we notice any write errors while flushing output buffer | |
317 | close STDOUT or die "error closing STDOUT: $!"; | |
318 | close STDIN or die "error closing STDIN: $!"; | |
319 | exit(0); | |
320 | } | |
321 | ||
ae52237f SI |
322 | # if we're going to do welcome/block-listing, let's prep now... |
323 | if ( $opt{'remove-from-welcomelist'} | |
324 | or $opt{'add-to-welcomelist'} | |
325 | or $opt{'add-to-blocklist'} ) | |
37ef5775 | 326 | { |
ae52237f | 327 | $doing_welcomelist_operation = 1; |
37ef5775 SI |
328 | $spamtest->init(1); |
329 | } | |
330 | ||
331 | # if we're doing things in test mode, force disable long-term memory | |
ae52237f | 332 | # functions like autowelcomelist and bayes autolearn. |
37ef5775 SI |
333 | # XXX - feels like we need a plugin hook here so plugins can be made |
334 | # aware and take appropriate action. | |
335 | if ($opt{'test-mode'}) { | |
ae52237f | 336 | $spamtest->{'conf'}->{'use_auto_welcomelist'} = 0; |
37ef5775 SI |
337 | $spamtest->{'conf'}->{'bayes_auto_learn'} = 0; |
338 | } | |
339 | ||
340 | ########################################################################### | |
341 | # Deal with the target listing, and STDIN -> tempfile | |
342 | ||
343 | my $tempfile; # will be defined if stdin -> tempfile | |
344 | push(@targets, @ARGV); | |
345 | @targets = ('-') unless @targets; | |
346 | ||
347 | for(my $elem = 0; $elem <= $#targets; $elem++) { | |
348 | # ArchiveIterator doesn't really like STDIN, so if "-" is specified | |
349 | # as a target, make it a temp file instead. | |
350 | if ( $targets[$elem] =~ /(?:^|:)-$/ ) { | |
351 | if (defined $tempfile) { | |
352 | # uh-oh, stdin specified multiple times? | |
353 | warn "skipping extra stdin target (".$targets[$elem].")\n"; | |
354 | splice @targets, $elem, 1; | |
355 | $elem--; # go back to this element again | |
356 | next; | |
357 | } | |
358 | else { | |
359 | my $handle; | |
360 | ( $tempfile, $handle ) = Mail::SpamAssassin::Util::secure_tmpfile(); | |
361 | binmode $handle or die "cannot set binmode on file $tempfile: $!"; | |
362 | ||
363 | # avoid slurping the whole file into memory, copy chunk by chunk | |
364 | my($inbuf,$nread,$nwrites); | |
365 | while ( $nread = sysread(STDIN, $inbuf, 32*1024) ) { | |
366 | for (my $ofs = 0; $ofs < length($inbuf); $ofs += $nwrites) { | |
367 | $nwrites = $handle->syswrite($inbuf, length($inbuf)-$ofs, $ofs); | |
368 | defined $nwrites or die "error writing to $tempfile: $!"; | |
369 | } | |
370 | } | |
371 | undef $inbuf; # release storage | |
372 | defined $nread or die "error reading from STDIN: $!"; | |
373 | close $handle or die "cannot close $tempfile: $!"; | |
374 | ||
375 | # re-aim the targets at the tempfile instead of STDIN | |
376 | $targets[$elem] =~ s/-$/$tempfile/; | |
377 | } | |
378 | } | |
379 | ||
380 | # make sure the target list is in the normal AI format | |
381 | if ($targets[$elem] !~ /^[^:]*:[a-z]+:/) { | |
382 | my $format = $opt{'format'} || 'detect'; | |
383 | $targets[$elem] = join ( ":", '', $format, $targets[$elem] ); | |
384 | } | |
385 | } | |
386 | ||
387 | ########################################################################### | |
388 | ||
389 | setup_sig_handlers(); | |
390 | ||
391 | # Everything below here needs ArchiveIterator ... | |
ae52237f | 392 | my $iter = Mail::SpamAssassin::ArchiveIterator->new( |
37ef5775 SI |
393 | { |
394 | 'opt_max_size' => 0, # no limit | |
395 | 'opt_want_date' => 0 | |
396 | } | |
397 | ); | |
398 | ||
399 | $iter->set_functions( \&wanted, \&result ); | |
400 | ||
401 | # Go run the messages! | |
402 | # bug 4930: use a temp variable since "||=" decides whether or not to set the | |
403 | # value before the RHS is actually run, so if the RHS separately sets the LHS | |
404 | # variable, things don't work right. Stupid global variables. ;) | |
405 | my $eval_stat; | |
406 | eval { | |
407 | my $runreturn = !$iter->run(@targets); $exitvalue ||= $runreturn; 1; | |
408 | } or do { | |
409 | $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat; | |
410 | }; | |
411 | ||
412 | $progress->final() if ($opt{progress} && $progress); | |
413 | ||
414 | # If we needed to make a tempfile, go delete it now. | |
415 | if (defined $tempfile) { | |
416 | unlink $tempfile or die "cannot unlink temporary file $tempfile: $!"; | |
417 | undef $tempfile; | |
418 | } | |
419 | ||
420 | # Let folks know how many messages were handled, as long as the handling | |
421 | # didn't produce output (ala: check, test, or remove_markup ...) | |
ae52237f | 422 | if ( $opt{'report'} || $opt{'revoke'} || $doing_welcomelist_operation ) { |
37ef5775 SI |
423 | print "$count message(s) examined.\n" or die "error writing: $!"; |
424 | } | |
425 | ||
426 | # if the eval died from something, report it here and return an error. | |
427 | if (defined $eval_stat) { die $eval_stat; } | |
428 | ||
429 | $spamtest->finish() if $spamtest; | |
430 | ||
431 | # make sure we notice any write errors while flushing output buffer | |
432 | close STDOUT or die "error closing STDOUT: $!"; | |
433 | close STDIN or die "error closing STDIN: $!"; | |
434 | # Ok, exit! | |
435 | exit( $exitvalue || 0 ); | |
436 | ||
437 | ########################################################################### | |
438 | ||
439 | sub init_results { | |
440 | $init_results = 1; | |
441 | ||
442 | return unless $opt{'progress'}; | |
443 | ||
444 | $total_messages = $Mail::SpamAssassin::ArchiveIterator::MESSAGES; | |
445 | ||
446 | $progress = Mail::SpamAssassin::Util::Progress->new({total => $total_messages,}); | |
447 | } | |
448 | ||
449 | ########################################################################### | |
450 | ||
451 | sub result { | |
452 | my ($class, $result, $time) = @_; | |
453 | ||
454 | # don't open results files until we get here to avoid overwriting files | |
455 | &init_results if !$init_results; | |
456 | ||
457 | $progress->update($count) if ($opt{progress} && $progress); | |
458 | } | |
459 | ||
460 | ########################################################################### | |
461 | ||
462 | my $mail; # global, so signal handler can clean it up; bug 5626 | |
463 | ||
464 | # make sure it only returns false values so that result_sub() isn't called... | |
465 | sub wanted { | |
466 | $spamtest->timer_reset; # reset timers for each AI message | |
467 | my $dataref = $_[3]; | |
468 | $mail = $spamtest->parse($dataref); | |
469 | $count++; | |
470 | ||
ae52237f SI |
471 | # This is a short cut -- doing welcome/block-list? Do it and return quickly. |
472 | if ($doing_welcomelist_operation) { | |
473 | if ( $opt{'add-to-welcomelist'} ) { | |
474 | $spamtest->add_all_addresses_to_welcomelist($mail, 1); | |
37ef5775 | 475 | } |
ae52237f SI |
476 | elsif ( $opt{'remove-from-welcomelist'} ) { |
477 | $spamtest->remove_all_addresses_from_welcomelist($mail, 1); | |
37ef5775 | 478 | } |
ae52237f SI |
479 | elsif ( $opt{'add-to-blocklist'} ) { |
480 | $spamtest->add_all_addresses_to_blocklist($mail, 1); | |
37ef5775 SI |
481 | } |
482 | else { | |
ae52237f | 483 | warn "spamassassin: oops! unhandled welcomelist operation"; |
37ef5775 SI |
484 | } |
485 | ||
486 | $mail->finish(); | |
487 | $mail = undef; | |
488 | return 1; | |
489 | } | |
490 | ||
491 | # handle removing reports | |
492 | if ( $opt{'remove-markup'} ) { | |
493 | ||
494 | # If we're not going to retest, just remove the markup and print it out | |
495 | if ( !$opt{'test-mode'} ) { | |
496 | print $spamtest->remove_spamassassin_markup ($mail); | |
497 | $mail->finish(); | |
498 | $mail = undef; | |
499 | return 1; | |
500 | } | |
501 | else { | |
502 | ||
503 | # remove the markup and retest it... a little more tricky ... | |
504 | # go ahead and remove the markup, then fake that the clean version | |
505 | # was what was sent in | |
506 | # | |
507 | my $new_mail = | |
508 | $spamtest->parse( $spamtest->remove_spamassassin_markup($mail) ); | |
509 | ||
510 | $mail->finish(); | |
511 | $mail = $new_mail; | |
512 | } | |
513 | } | |
514 | ||
515 | # handle reporting and revoking | |
516 | if ( $opt{'report'} || $opt{'revoke'} ) { | |
517 | ||
518 | # Make sure the message is clean first ... | |
519 | my $new_mail = | |
520 | $spamtest->parse( $spamtest->remove_spamassassin_markup($mail) ); | |
521 | $mail->finish(); | |
522 | $mail = $new_mail; | |
523 | ||
524 | my $failed; | |
525 | if ( $opt{'report'} && !$spamtest->report_as_spam($mail) ) { | |
526 | $failed = 'report'; | |
527 | } | |
528 | ||
529 | if ( $opt{'revoke'} && !$spamtest->revoke_as_spam($mail) ) { | |
530 | $failed = 'revoke'; | |
531 | } | |
532 | ||
533 | if ($failed) { | |
534 | warn "spamassassin: warning, unable to $failed message\n"; | |
535 | warn "spamassassin: for more information, re-run with -D option to see debug output\n"; | |
536 | } | |
537 | ||
538 | $mail->finish(); | |
539 | $mail = undef; | |
540 | return 1; | |
541 | } | |
542 | ||
543 | # OK, do checks and put out the message. | |
544 | my $status = $spamtest->check($mail); | |
545 | print $status->rewrite_mail() or die "error writing: $!"; | |
546 | ||
547 | if ( $opt{'test-mode'} ) { | |
548 | print $status->get_report() or die "error writing: $!"; | |
549 | } | |
550 | ||
551 | # if this message was spam, set the exit value appropriately. | |
552 | if ( defined $opt{'error-code'} && $status->is_spam() && !defined $exitvalue ) | |
553 | { | |
554 | $exitvalue = $opt{'error-code'} || 5; | |
555 | } | |
556 | ||
557 | # clean up after ourselves | |
558 | $mail->finish(); | |
559 | $mail = undef; | |
560 | ||
561 | $status->finish(); | |
562 | ||
563 | return 1; | |
564 | } | |
565 | ||
566 | ########################################################################### | |
567 | ||
568 | sub setup_sig_handlers { | |
569 | $SIG{HUP} = \&kill_handler; | |
570 | $SIG{INT} = \&kill_handler; | |
571 | $SIG{TERM} = \&kill_handler; | |
572 | # $SIG{PIPE} = \&kill_handler; | |
573 | $SIG{PIPE} = 'IGNORE'; | |
574 | } | |
575 | ||
576 | sub kill_handler { | |
577 | my ($sig) = @_; | |
578 | warn "spamassassin: killed by SIG$sig\n"; | |
579 | if ($mail) { | |
580 | $mail->finish(); # bug 5626: remove temp files etc. | |
581 | $mail = undef; | |
582 | } | |
583 | if (defined $tempfile) { # bug 5557: additional paranoia about tmpfiles | |
584 | unlink $tempfile or warn "cannot unlink temporary file $tempfile: $!"; | |
585 | undef $tempfile; | |
586 | } | |
587 | close STDOUT; close STDIN; # ignoring status | |
588 | exit 15; | |
589 | } | |
590 | ||
591 | __END__ | |
592 | ||
593 | # --------------------------------------------------------------------------- | |
594 | ||
595 | =head1 NAME | |
596 | ||
597 | spamassassin - extensible email filter used to identify spam | |
598 | ||
599 | =head1 DESCRIPTION | |
600 | ||
601 | SpamAssassin is an intelligent email filter which uses a diverse range of | |
602 | tests to identify unsolicited bulk email, more commonly known as "spam". | |
603 | These tests are applied to email headers and content to classify email | |
604 | using advanced statistical methods. In addition, SpamAssassin has a | |
605 | modular architecture that allows other technologies to be quickly wielded | |
606 | against spam and is designed for easy integration into virtually any email | |
607 | system. | |
608 | ||
609 | =head1 SYNOPSIS | |
610 | ||
611 | For ease of access, the SpamAssassin manual has been split up into | |
612 | several sections. If you're intending to read these straight through | |
613 | for the first time, the suggested order will tend to reduce the number | |
614 | of forward references. | |
615 | ||
616 | Extensive additional documentation for SpamAssassin is available, | |
617 | primarily on the SpamAssassin web site and wiki. | |
618 | ||
619 | You should be able to view SpamAssassin's documentation with your man(1) | |
620 | program or perldoc(1). | |
621 | ||
622 | =head2 OVERVIEW | |
623 | ||
624 | spamassassin SpamAssassin overview (this section) | |
625 | ||
626 | =head2 CONFIGURATION | |
627 | ||
628 | Mail::SpamAssassin::Conf SpamAssassin configuration files | |
629 | ||
630 | =head2 USAGE | |
631 | ||
632 | spamassassin-run "spamassassin" front-end filtering script | |
633 | sa-learn train SpamAssassin's Bayesian classifier | |
634 | spamc client for spamd (faster than spamassassin) | |
635 | spamd spamassassin server (needed by spamc) | |
636 | ||
637 | =head2 DEFAULT PLUGINS | |
638 | ||
639 | @@PLUGIN_POD@@ | |
640 | ||
641 | =head1 WEB SITES | |
642 | ||
ae52237f SI |
643 | SpamAssassin web site: https://spamassassin.apache.org/ |
644 | Wiki-based documentation: https://wiki.apache.org/spamassassin/ | |
37ef5775 SI |
645 | |
646 | =head1 USER MAILING LIST | |
647 | ||
648 | A users mailing list exists where other experienced users are often able | |
649 | to help and provide tips and advice. Subscription instructions are | |
650 | located on the SpamAssassin web site. | |
651 | ||
652 | =head1 CONFIGURATION FILES | |
653 | ||
654 | The SpamAssassin rule base, text templates, and rule description text | |
655 | are loaded from configuration files. | |
656 | ||
657 | Default configuration data is loaded from the first existing directory | |
658 | in: | |
659 | ||
660 | =over 4 | |
661 | ||
662 | =item @@LOCAL_STATE_DIR@@/@@VERSION@@ | |
663 | ||
664 | =item @@DEF_RULES_DIR@@ | |
665 | ||
666 | =item @@PREFIX@@/share/spamassassin | |
667 | ||
668 | =item /usr/local/share/spamassassin | |
669 | ||
670 | =item /usr/share/spamassassin | |
671 | ||
672 | =back | |
673 | ||
674 | Site-specific configuration data is used to override any values which had | |
675 | already been set. This is loaded from the first existing directory in: | |
676 | ||
677 | =over 4 | |
678 | ||
679 | =item @@LOCAL_RULES_DIR@@ | |
680 | ||
681 | =item @@PREFIX@@/etc/mail/spamassassin | |
682 | ||
683 | =item @@PREFIX@@/etc/spamassassin | |
684 | ||
685 | =item /usr/local/etc/spamassassin | |
686 | ||
687 | =item /usr/pkg/etc/spamassassin | |
688 | ||
689 | =item /usr/etc/spamassassin | |
690 | ||
691 | =item /etc/mail/spamassassin | |
692 | ||
693 | =item /etc/spamassassin | |
694 | ||
695 | =back | |
696 | ||
697 | From those directories, SpamAssassin will first read files ending in | |
698 | ".pre" in lexical order and then it will read files ending in ".cf" in | |
699 | lexical order (most files begin with two numbers to make the sorting | |
700 | order obvious). | |
701 | ||
702 | In other words, it will read F<init.pre> first, then F<10_default_prefs.cf> before | |
703 | F<50_scores.cf> and F<20_body_tests.cf> before F<20_head_tests.cf>. | |
704 | Options in later files will override earlier files. | |
705 | ||
706 | Individual user preferences are loaded from the location specified on | |
707 | the C<spamassassin>, C<sa-learn>, or C<spamd> command line (see respective | |
708 | manual page for details). If the location is not specified, | |
709 | F<~/.spamassassin/user_prefs> is used if it exists. SpamAssassin will | |
710 | create that file if it does not already exist, using | |
711 | F<user_prefs.template> as a template. That file will be looked for in: | |
712 | ||
713 | =over 4 | |
714 | ||
715 | =item @@LOCAL_RULES_DIR@@ | |
716 | ||
717 | =item @@PREFIX@@/etc/mail/spamassassin | |
718 | ||
719 | =item @@PREFIX@@/share/spamassassin | |
720 | ||
721 | =item /etc/spamassassin | |
722 | ||
723 | =item /etc/mail/spamassassin | |
724 | ||
725 | =item /usr/local/share/spamassassin | |
726 | ||
727 | =item /usr/share/spamassassin | |
728 | ||
729 | =back | |
730 | ||
731 | =head1 TAGGING | |
732 | ||
733 | The following two sections detail the default tagging and markup that | |
734 | takes place for messages when running C<spamassassin> or C<spamc> with | |
735 | C<spamd> in the default configuration. | |
736 | ||
737 | Note: before header modification and addition, all headers beginning | |
738 | with C<X-Spam-> are removed to prevent spammer mischief and also to | |
739 | avoid potential problems caused by prior invocations of SpamAssassin. | |
740 | ||
741 | =head2 TAGGING FOR SPAM MAILS | |
742 | ||
743 | By default, all messages with a calculated score of 5.0 or higher are | |
744 | tagged as spam. | |
745 | ||
746 | If an incoming message is tagged as spam, instead of modifying the | |
747 | original message, SpamAssassin will create a new report message and | |
748 | attach the original message as a message/rfc822 MIME part (ensuring the | |
749 | original message is completely preserved and easier to recover). | |
750 | ||
751 | The new report message inherits the following headers (if they are | |
752 | present) from the original spam message: | |
753 | ||
754 | =over 4 | |
755 | ||
756 | =item From: header | |
757 | ||
758 | =item To: header | |
759 | ||
760 | =item Cc: header | |
761 | ||
762 | =item Subject: header | |
763 | ||
764 | =item Date: header | |
765 | ||
766 | =item Message-ID: header | |
767 | ||
768 | =back | |
769 | ||
770 | The above headers can be modified if the relevant C<rewrite_header> | |
771 | option is given (see C<Mail::SpamAssassin::Conf> for more information). | |
772 | ||
773 | By default these message headers are added to spam: | |
774 | ||
775 | =over 4 | |
776 | ||
777 | =item X-Spam-Flag: header | |
778 | ||
779 | Set to C<YES>. | |
780 | ||
781 | =back | |
782 | ||
783 | The headers that added are fully configurable via the C<add_header> | |
784 | option (see C<Mail::SpamAssassin::Conf> for more information). | |
785 | ||
786 | =over 4 | |
787 | ||
788 | =item spam mail body text | |
789 | ||
790 | The SpamAssassin report is added to top of the mail message body, | |
791 | if the message is marked as spam. | |
792 | ||
793 | =back | |
794 | ||
795 | =head2 DEFAULT TAGGING FOR ALL MAILS | |
796 | ||
797 | These headers are added to all messages, both spam and ham (non-spam). | |
798 | ||
799 | =over 4 | |
800 | ||
801 | =item X-Spam-Checker-Version: header | |
802 | ||
803 | The version and subversion of SpamAssassin and the host where | |
804 | SpamAssassin was run. | |
805 | ||
806 | =item X-Spam-Level: header | |
807 | ||
808 | A series of "*" characters where each one represents a full score point. | |
809 | ||
810 | =item X-Spam-Status: header | |
811 | ||
812 | A string, C<(Yes|No), score=nn required=nn tests=xxx,xxx | |
813 | autolearn=(ham|spam|no|unavailable|failed)> is set in this header to | |
814 | reflect the filter status. For the first word, "Yes" means spam and | |
815 | "No" means ham (non-spam). | |
816 | ||
817 | =back | |
818 | ||
819 | The headers that added are fully configurable via the C<add_header> | |
820 | option (see C<Mail::SpamAssassin::Conf> for more information). | |
821 | ||
822 | =head1 INSTALLATION | |
823 | ||
824 | The B<spamassassin> command is part of the B<Mail::SpamAssassin> Perl module. | |
825 | Install this as a normal Perl module, using C<perl -MCPAN -e shell>, or by | |
826 | hand. | |
827 | ||
828 | Note that it is not possible to use the C<PERL5LIB> environment variable | |
829 | to affect where SpamAssassin finds its perl modules, due to limitations | |
830 | imposed by perl's "taint" security checks. | |
831 | ||
832 | For further details on how to install, please read the C<INSTALL> file | |
833 | from the SpamAssassin distribution. | |
834 | ||
835 | =head1 DEVELOPER DOCUMENTATION | |
836 | ||
837 | Mail::SpamAssassin | |
838 | Spam detector and markup engine | |
839 | ||
840 | Mail::SpamAssassin::ArchiveIterator | |
841 | find and process messages one at a time | |
842 | ||
ae52237f SI |
843 | Mail::SpamAssassin::AutoWelcomelist |
844 | auto-welcomelist handler for SpamAssassin | |
37ef5775 SI |
845 | |
846 | Mail::SpamAssassin::Bayes | |
847 | determine spammishness using a Bayesian classifier | |
848 | ||
849 | Mail::SpamAssassin::BayesStore | |
850 | Bayesian Storage Module | |
851 | ||
852 | Mail::SpamAssassin::BayesStore::SQL | |
853 | SQL Bayesian Storage Module Implementation | |
854 | ||
855 | Mail::SpamAssassin::Conf::LDAP | |
856 | load SpamAssassin scores from LDAP database | |
857 | ||
858 | Mail::SpamAssassin::Conf::Parser | |
859 | parse SpamAssassin configuration | |
860 | ||
861 | Mail::SpamAssassin::Conf::SQL | |
862 | load SpamAssassin scores from SQL database | |
863 | ||
864 | Mail::SpamAssassin::Message | |
865 | decode, render, and hold an RFC-2822 message | |
866 | ||
867 | Mail::SpamAssassin::Message::Metadata | |
868 | extract metadata from a message | |
869 | ||
870 | Mail::SpamAssassin::Message::Node | |
871 | decode, render, and make available MIME message parts | |
872 | ||
873 | Mail::SpamAssassin::PerMsgLearner | |
874 | per-message status (spam or not-spam) | |
875 | ||
876 | Mail::SpamAssassin::PerMsgStatus | |
877 | per-message status (spam or not-spam) | |
878 | ||
879 | Mail::SpamAssassin::PersistentAddrList | |
880 | persistent address list base class | |
881 | ||
882 | Mail::SpamAssassin::Plugin | |
883 | SpamAssassin plugin base class | |
884 | ||
37ef5775 SI |
885 | Mail::SpamAssassin::Plugin::RelayCountry |
886 | add message metadata indicating the country code of each relay | |
887 | ||
888 | Mail::SpamAssassin::Plugin::SPF | |
889 | perform SPF verification tests | |
890 | ||
891 | Mail::SpamAssassin::Plugin::URIDNSBL | |
892 | look up URLs against DNS blocklists | |
893 | ||
894 | Mail::SpamAssassin::SQLBasedAddrList | |
ae52237f | 895 | SpamAssassin SQL Based Auto Welcomelist |
37ef5775 SI |
896 | |
897 | =head1 BUGS | |
898 | ||
f887dfc0 | 899 | See E<lt>https://issues.apache.org/SpamAssassin/E<gt> |
37ef5775 SI |
900 | |
901 | =head1 AUTHORS | |
902 | ||
f887dfc0 | 903 | The SpamAssassin(tm) Project E<lt>https://spamassassin.apache.org/E<gt> |
37ef5775 SI |
904 | |
905 | =head1 COPYRIGHT AND LICENSE | |
906 | ||
907 | SpamAssassin is distributed under the Apache License, Version 2.0, as | |
908 | described in the file C<LICENSE> included with the distribution. | |
909 | ||
910 | Copyright (C) 2015 The Apache Software Foundation | |
911 |