File Coverage

blib/lib/Mail/SpamAssassin/Plugin/AWL.pm
Criterion Covered Total %
statement 101 198 51.0
branch 23 96 23.9
condition 9 35 25.7
subroutine 11 17 64.7
pod 4 6 66.6
total 148 352 42.0


line stmt bran cond sub pod time code
1             # <@LICENSE>
2             # Licensed to the Apache Software Foundation (ASF) under one or more
3             # contributor license agreements. See the NOTICE file distributed with
4             # this work for additional information regarding copyright ownership.
5             # The ASF licenses this file to you under the Apache License, Version 2.0
6             # (the "License"); you may not use this file except in compliance with
7             # the License. You may obtain a copy of the License at:
8             #
9             # http://www.apache.org/licenses/LICENSE-2.0
10             #
11             # Unless required by applicable law or agreed to in writing, software
12             # distributed under the License is distributed on an "AS IS" BASIS,
13             # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14             # See the License for the specific language governing permissions and
15             # limitations under the License.
16             # </@LICENSE>
17              
18             =head1 NAME
19              
20             Mail::SpamAssassin::Plugin::AWL - Normalize scores via auto-whitelist
21              
22             =head1 SYNOPSIS
23              
24             To try this out, add this or uncomment this line in init.pre:
25              
26             loadplugin Mail::SpamAssassin::Plugin::AWL
27              
28             Use the supplied 60_awl.cf file (ie you don't have to do anything) or
29             add these lines to a .cf file:
30              
31             header AWL eval:check_from_in_auto_whitelist()
32             describe AWL From: address is in the auto white-list
33             tflags AWL userconf noautolearn
34             priority AWL 1000
35              
36             =head1 DESCRIPTION
37              
38             This plugin module provides support for the auto-whitelist. It keeps
39             track of the average SpamAssassin score for senders. Senders are
40             tracked using a combination of their From: address and their IP address.
41             It then uses that average score to reduce the variability in scoring
42             from message to message and modifies the final score by pushing the
43             result towards the historical average. This improves the accuracy of
44             filtering for most email.
45              
46             =head1 TEMPLATE TAGS
47              
48             This plugin module adds the following C<tags> that can be used as
49             placeholders in certain options. See C<Mail::SpamAssassin::Conf>
50             for more information on TEMPLATE TAGS.
51              
52             _AWL_ AWL modifier
53             _AWLMEAN_ Mean score on which AWL modification is based
54             _AWLCOUNT_ Number of messages on which AWL modification is based
55             _AWLPRESCORE_ Score before AWL
56              
57             =cut
58              
59             package Mail::SpamAssassin::Plugin::AWL;
60              
61 19     19   144 use strict;
  19         47  
  19         675  
62 19     19   128 use warnings;
  19         37  
  19         643  
63             # use bytes;
64 19     19   123 use re 'taint';
  19         44  
  19         647  
65 19     19   138 use Mail::SpamAssassin::Plugin;
  19         40  
  19         541  
66 19     19   6733 use Mail::SpamAssassin::AutoWhitelist;
  19         67  
  19         643  
67 19     19   138 use Mail::SpamAssassin::Util qw(untaint_var);
  19         42  
  19         841  
68 19     19   119 use Mail::SpamAssassin::Logger;
  19         50  
  19         47768  
69              
70             our @ISA = qw(Mail::SpamAssassin::Plugin);
71              
72             # constructor: register the eval rule
73             sub new {
74 60     60 1 207 my $class = shift;
75 60         166 my $mailsaobject = shift;
76              
77             # some boilerplate...
78 60   33     456 $class = ref($class) || $class;
79 60         313 my $self = $class->SUPER::new($mailsaobject);
80 60         195 bless ($self, $class);
81              
82             # the important bit!
83 60         319 $self->register_eval_rule("check_from_in_auto_whitelist");
84              
85 60         397 $self->set_config($mailsaobject->{conf});
86              
87 60         656 return $self;
88             }
89              
90             sub set_config {
91 60     60 0 170 my($self, $conf) = @_;
92 60         141 my @cmds;
93              
94             =head1 USER PREFERENCES
95              
96             The following options can be used in both site-wide (C<local.cf>) and
97             user-specific (C<user_prefs>) configuration files to customize how
98             SpamAssassin handles incoming email messages.
99              
100             =over 4
101              
102             =item use_auto_whitelist ( 0 | 1 ) (default: 1)
103              
104             Whether to use auto-whitelists. Auto-whitelists track the long-term
105             average score for each sender and then shift the score of new messages
106             toward that long-term average. This can increase or decrease the score
107             for messages, depending on the long-term behavior of the particular
108             correspondent.
109              
110             For more information about the auto-whitelist system, please look
111             at the the C<Automatic Whitelist System> section of the README file.
112             The auto-whitelist is not intended as a general-purpose replacement
113             for static whitelist entries added to your config files.
114              
115             Note that certain tests are ignored when determining the final
116             message score:
117              
118             - rules with tflags set to 'noautolearn'
119              
120             =cut
121              
122 60         334 push (@cmds, {
123             setting => 'use_auto_whitelist',
124             default => 1,
125             type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL
126             });
127              
128             =item auto_whitelist_factor n (default: 0.5, range [0..1])
129              
130             How much towards the long-term mean for the sender to regress a message.
131             Basically, the algorithm is to track the long-term mean score of messages for
132             the sender (C<mean>), and then once we have otherwise fully calculated the
133             score for this message (C<score>), we calculate the final score for the
134             message as:
135              
136             C<finalscore> = C<score> + (C<mean> - C<score>) * C<factor>
137              
138             So if C<factor> = 0.5, then we'll move to half way between the calculated
139             score and the mean. If C<factor> = 0.3, then we'll move about 1/3 of the way
140             from the score toward the mean. C<factor> = 1 means just use the long-term
141             mean; C<factor> = 0 mean just use the calculated score.
142              
143             =cut
144              
145 60         309 push (@cmds, {
146             setting => 'auto_whitelist_factor',
147             default => 0.5,
148             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
149             });
150              
151             =item auto_whitelist_ipv4_mask_len n (default: 16, range [0..32])
152              
153             The AWL database keeps only the specified number of most-significant bits
154             of an IPv4 address in its fields, so that different individual IP addresses
155             within a subnet belonging to the same owner are managed under a single
156             database record. As we have no information available on the allocated
157             address ranges of senders, this CIDR mask length is only an approximation.
158             The default is 16 bits, corresponding to a former class B. Increase the
159             number if a finer granularity is desired, e.g. to 24 (class C) or 32.
160             A value 0 is allowed but is not particularly useful, as it would treat the
161             whole internet as a single organization. The number need not be a multiple
162             of 8, any split is allowed.
163              
164             =cut
165              
166             push (@cmds, {
167             setting => 'auto_whitelist_ipv4_mask_len',
168             default => 16,
169             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
170             code => sub {
171 0     0   0 my ($self, $key, $value, $line) = @_;
172 0 0 0     0 if (!defined $value || $value eq '') {
    0 0        
      0        
173 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
174             } elsif ($value !~ /^\d+$/ || $value < 0 || $value > 32) {
175 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
176             }
177 0         0 $self->{auto_whitelist_ipv4_mask_len} = $value;
178             }
179 60         555 });
180              
181             =item auto_whitelist_ipv6_mask_len n (default: 48, range [0..128])
182              
183             The AWL database keeps only the specified number of most-significant bits
184             of an IPv6 address in its fields, so that different individual IP addresses
185             within a subnet belonging to the same owner are managed under a single
186             database record. As we have no information available on the allocated address
187             ranges of senders, this CIDR mask length is only an approximation. The default
188             is 48 bits, corresponding to an address range commonly allocated to individual
189             (smaller) organizations. Increase the number for a finer granularity, e.g.
190             to 64 or 96 or 128, or decrease for wider ranges, e.g. 32. A value 0 is
191             allowed but is not particularly useful, as it would treat the whole internet
192             as a single organization. The number need not be a multiple of 4, any split
193             is allowed.
194              
195             =cut
196              
197             push (@cmds, {
198             setting => 'auto_whitelist_ipv6_mask_len',
199             default => 48,
200             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
201             code => sub {
202 0     0   0 my ($self, $key, $value, $line) = @_;
203 0 0 0     0 if (!defined $value || $value eq '') {
    0 0        
      0        
204 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
205             } elsif ($value !~ /^\d+$/ || $value < 0 || $value > 128) {
206 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
207             }
208 0         0 $self->{auto_whitelist_ipv6_mask_len} = $value;
209             }
210 60         496 });
211              
212             =item user_awl_sql_override_username
213              
214             Used by the SQLBasedAddrList storage implementation.
215              
216             If this option is set the SQLBasedAddrList module will override the set
217             username with the value given. This can be useful for implementing global
218             or group based auto-whitelist databases.
219              
220             =cut
221              
222 60         336 push (@cmds, {
223             setting => 'user_awl_sql_override_username',
224             default => '',
225             type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
226             });
227              
228             =item auto_whitelist_distinguish_signed
229              
230             Used by the SQLBasedAddrList storage implementation.
231              
232             If this option is set the SQLBasedAddrList module will keep separate
233             database entries for DKIM-validated e-mail addresses and for non-validated
234             ones. A pre-requisite when setting this option is that a field awl.signedby
235             exists in a SQL table, otherwise SQL operations will fail (which is why we
236             need this option at all - for compatibility with pre-3.3.0 database schema).
237             A plugin DKIM should also be enabled, as otherwise there is no benefit from
238             turning on this option.
239              
240             =cut
241              
242 60         251 push (@cmds, {
243             setting => 'auto_whitelist_distinguish_signed',
244             default => 0,
245             type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL
246             });
247              
248             =back
249              
250             =head1 ADMINISTRATOR SETTINGS
251              
252             These settings differ from the ones above, in that they are considered 'more
253             privileged' -- even more than the ones in the B<PRIVILEGED SETTINGS> section.
254             No matter what C<allow_user_rules> is set to, these can never be set from a
255             user's C<user_prefs> file.
256              
257             =over 4
258              
259             =item auto_whitelist_factory module (default: Mail::SpamAssassin::DBBasedAddrList)
260              
261             Select alternative whitelist factory module.
262              
263             =cut
264              
265 60         285 push (@cmds, {
266             setting => 'auto_whitelist_factory',
267             is_admin => 1,
268             default => 'Mail::SpamAssassin::DBBasedAddrList',
269             type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
270             });
271              
272             =item auto_whitelist_path /path/filename (default: ~/.spamassassin/auto-whitelist)
273              
274             This is the automatic-whitelist directory and filename. By default, each user
275             has their own whitelist database in their C<~/.spamassassin> directory with
276             mode 0700. For system-wide SpamAssassin use, you may want to share this
277             across all users, although that is not recommended.
278              
279             =cut
280              
281             push (@cmds, {
282             setting => 'auto_whitelist_path',
283             is_admin => 1,
284             default => '__userstate__/auto-whitelist',
285             type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
286             code => sub {
287 60     60   334 my ($self, $key, $value, $line) = @_;
288 60 50 33     722 unless (defined $value && $value !~ /^$/) {
289 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
290             }
291 60 50       1082 if (-d $value) {
292 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
293             }
294 60         555 $self->{auto_whitelist_path} = $value;
295             }
296 60         484 });
297              
298             =item auto_whitelist_db_modules Module ... (default: see below)
299              
300             What database modules should be used for the auto-whitelist storage database
301             file. The first named module that can be loaded from the perl include path
302             will be used. The format is:
303              
304             PreferredModuleName SecondBest ThirdBest ...
305              
306             ie. a space-separated list of perl module names. The default is:
307              
308             DB_File GDBM_File SDBM_File
309              
310             NDBM_File is no longer supported, since it appears to have bugs that
311             preclude its use for the AWL (see SpamAssassin bug 4353).
312              
313             =cut
314              
315 60         309 push (@cmds, {
316             setting => 'auto_whitelist_db_modules',
317             is_admin => 1,
318             default => 'DB_File GDBM_File SDBM_File',
319             type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
320             });
321              
322             =item auto_whitelist_file_mode (default: 0700)
323              
324             The file mode bits used for the automatic-whitelist directory or file.
325              
326             Make sure you specify this using the 'x' mode bits set, as it may also be used
327             to create directories. However, if a file is created, the resulting file will
328             not have any execute bits set (the umask is set to 0111).
329              
330             =cut
331              
332             push (@cmds, {
333             setting => 'auto_whitelist_file_mode',
334             is_admin => 1,
335             default => '0700',
336             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
337             code => sub {
338 0     0   0 my ($self, $key, $value, $line) = @_;
339 0 0       0 if ($value !~ /^0?[0-7]{3}$/) {
340 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
341             }
342 0         0 $self->{auto_whitelist_file_mode} = untaint_var($value);
343             }
344 60         434 });
345              
346             =item user_awl_dsn DBI:databasetype:databasename:hostname:port
347              
348             Used by the SQLBasedAddrList storage implementation.
349              
350             This will set the DSN used to connect. Example:
351             C<DBI:mysql:spamassassin:localhost>
352              
353             =cut
354              
355 60         255 push (@cmds, {
356             setting => 'user_awl_dsn',
357             is_admin => 1,
358             type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
359             });
360              
361             =item user_awl_sql_username username
362              
363             Used by the SQLBasedAddrList storage implementation.
364              
365             The authorized username to connect to the above DSN.
366              
367             =cut
368              
369 60         6080 push (@cmds, {
370             setting => 'user_awl_sql_username',
371             is_admin => 1,
372             type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
373             });
374              
375             =item user_awl_sql_password password
376              
377             Used by the SQLBasedAddrList storage implementation.
378              
379             The password for the database username, for the above DSN.
380              
381             =cut
382              
383 60         265 push (@cmds, {
384             setting => 'user_awl_sql_password',
385             is_admin => 1,
386             type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
387             });
388              
389             =item user_awl_sql_table tablename
390              
391             Used by the SQLBasedAddrList storage implementation.
392              
393             The table user auto-whitelists are stored in, for the above DSN.
394              
395             =cut
396              
397 60         294 push (@cmds, {
398             setting => 'user_awl_sql_table',
399             is_admin => 1,
400             default => 'awl',
401             type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
402             });
403              
404 60         304 $conf->{parser}->register_commands(\@cmds);
405             }
406              
407             sub check_from_in_auto_whitelist {
408 81     81 0 251 my ($self, $pms) = @_;
409              
410 81 100       1240 return 0 unless ($pms->{conf}->{use_auto_whitelist});
411              
412 17         76 my $timer = $self->{main}->time_method("total_awl");
413              
414 17         87 my $from = lc $pms->get('From:addr');
415             # dbg("auto-whitelist: From: $from");
416 17 100       221 return 0 unless $from =~ /\S/;
417              
418             # find the earliest usable "originating IP". ignore private nets
419 10         24 my $origip;
420 10         25 foreach my $rly (reverse (@{$pms->{relays_trusted}}, @{$pms->{relays_untrusted}}))
  10         33  
  10         42  
421             {
422 1 50       5 next if ($rly->{ip_private});
423 1 50       5 if ($rly->{ip}) {
424 1         3 $origip = $rly->{ip}; last;
  1         3  
425             }
426             }
427              
428 10         28 my $scores = $pms->{conf}->{scores};
429 10         28 my $tflags = $pms->{conf}->{tflags};
430 10         23 my $points = 0;
431 10         51 my $signedby = $pms->get_tag('DKIMDOMAIN');
432 10 50 33     80 undef $signedby if defined $signedby && $signedby eq '';
433              
434 10         25 foreach my $test (@{$pms->{test_names_hit}}) {
  10         34  
435             # ignore tests with 0 score in this scoreset,
436             # or if the test is marked as "noautolearn"
437 50 50       139 next if !$scores->{$test};
438 50 50 66     188 next if exists $tflags->{$test} && $tflags->{$test} =~ /\bnoautolearn\b/;
439 50 50 66     160 return 0 if exists $tflags->{$test} && $tflags->{$test} =~ /\bnoawl\b/;
440 50         119 $points += $scores->{$test};
441             }
442              
443 10         155 my $awlpoints = (sprintf "%0.3f", $points) + 0;
444              
445             # Create the AWL object
446 10         28 my $whitelist;
447             eval {
448 10         111 $whitelist = Mail::SpamAssassin::AutoWhitelist->new($pms->{main});
449              
450 10         33 my $meanscore;
451             { # check
452 10         30 my $timer = $self->{main}->time_method("check_awl");
  10         49  
453 10         73 $meanscore = $whitelist->check_address($from, $origip, $signedby);
454             }
455 10         30 my $delta = 0;
456              
457             dbg("auto-whitelist: AWL active, pre-score: %s, autolearn score: %s, ".
458             "mean: %s, IP: %s, address: %s %s",
459 10 100 100     168 $pms->{score}, $awlpoints,
    50          
460             !defined $meanscore ? 'undef' : sprintf("%.3f",$meanscore),
461             $origip || 'undef',
462             $from, $signedby ? "signed by $signedby" : '(not signed)');
463              
464 10 100       48 if (defined $meanscore) {
465 3         11 $delta = $meanscore - $awlpoints;
466 3         15 $delta *= $pms->{main}->{conf}->{auto_whitelist_factor};
467            
468 3         30 $pms->set_tag('AWL', sprintf("%2.1f",$delta));
469 3 50       11 if (defined $meanscore) {
470 3         25 $pms->set_tag('AWLMEAN', sprintf("%2.1f", $meanscore));
471             }
472 3         18 $pms->set_tag('AWLCOUNT', sprintf("%2.1f", $whitelist->count()));
473 3         27 $pms->set_tag('AWLPRESCORE', sprintf("%2.1f", $pms->{score}));
474             }
475              
476             # Update the AWL *before* adding the new score, otherwise
477             # early high-scoring messages are reinforced compared to
478             # later ones. http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=159704
479 10 100       62 if (!$pms->{disable_auto_learning}) {
480 8         43 my $timer = $self->{main}->time_method("update_awl");
481 8         41 $whitelist->add_score($awlpoints);
482             }
483              
484             # now redundant, got_hit() takes care of it
485             # for my $set (0..3) { # current AWL score changes with each hit
486             # $pms->{conf}->{scoreset}->[$set]->{"AWL"} = sprintf("%0.3f", $delta);
487             # }
488              
489 10 100       70 if ($delta != 0) {
490 2         27 $pms->got_hit("AWL", "AWL: ", ruletype => 'eval',
491             score => sprintf("%0.3f", $delta));
492             }
493              
494 10         65 $whitelist->finish();
495 10         54 1;
496 10 50       30 } or do {
497 0 0       0 my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
  0         0  
498 0         0 warn("auto-whitelist: open of auto-whitelist file failed: $eval_stat\n");
499             # try an unlock, in case we got that far
500 0 0       0 eval { $whitelist->finish(); } if $whitelist;
  0         0  
501 0         0 return 0;
502             };
503              
504 10         46 dbg("auto-whitelist: post auto-whitelist score: %.3f", $pms->{score});
505              
506             # test hit is above
507 10         386 return 0;
508             }
509              
510             sub blacklist_address {
511 0     0 1   my ($self, $args) = @_;
512              
513 0 0         return 0 unless ($self->{main}->{conf}->{use_auto_whitelist});
514              
515 0 0         unless ($args->{address}) {
516 0 0         print "SpamAssassin auto-whitelist: failed to add address to blacklist\n" if ($args->{cli_p});
517 0           dbg("auto-whitelist: failed to add address to blacklist");
518 0           return;
519             }
520            
521 0           my $whitelist;
522             my $status;
523              
524             eval {
525 0           $whitelist = Mail::SpamAssassin::AutoWhitelist->new($self->{main});
526              
527 0 0         if ($whitelist->add_known_bad_address($args->{address}, $args->{signedby})) {
528 0 0         print "SpamAssassin auto-whitelist: adding address to blacklist: " . $args->{address} . "\n" if ($args->{cli_p});
529 0           dbg("auto-whitelist: adding address to blacklist: " . $args->{address});
530 0           $status = 0;
531             }
532             else {
533 0 0         print "SpamAssassin auto-whitelist: error adding address to blacklist\n" if ($args->{cli_p});
534 0           dbg("auto-whitelist: error adding address to blacklist");
535 0           $status = 1;
536             }
537 0           $whitelist->finish();
538 0           1;
539 0 0         } or do {
540 0 0         my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
  0            
541 0           warn("auto-whitelist: open of auto-whitelist file failed: $eval_stat\n");
542 0           eval { $whitelist->finish(); };
  0            
543 0           return 0;
544             };
545              
546 0           return $status;
547             }
548              
549             sub whitelist_address {
550 0     0 1   my ($self, $args) = @_;
551              
552 0 0         return 0 unless ($self->{main}->{conf}->{use_auto_whitelist});
553              
554 0 0         unless ($args->{address}) {
555 0 0         print "SpamAssassin auto-whitelist: failed to add address to whitelist\n" if ($args->{cli_p});
556 0           dbg("auto-whitelist: failed to add address to whitelist");
557 0           return 0;
558             }
559              
560 0           my $whitelist;
561             my $status;
562              
563             eval {
564 0           $whitelist = Mail::SpamAssassin::AutoWhitelist->new($self->{main});
565              
566 0 0         if ($whitelist->add_known_good_address($args->{address}, $args->{signedby})) {
567 0 0         print "SpamAssassin auto-whitelist: adding address to whitelist: " . $args->{address} . "\n" if ($args->{cli_p});
568 0           dbg("auto-whitelist: adding address to whitelist: " . $args->{address});
569 0           $status = 1;
570             }
571             else {
572 0 0         print "SpamAssassin auto-whitelist: error adding address to whitelist\n" if ($args->{cli_p});
573 0           dbg("auto-whitelist: error adding address to whitelist");
574 0           $status = 0;
575             }
576              
577 0           $whitelist->finish();
578 0           1;
579 0 0         } or do {
580 0 0         my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
  0            
581 0           warn("auto-whitelist: open of auto-whitelist file failed: $eval_stat\n");
582 0           eval { $whitelist->finish(); };
  0            
583 0           return 0;
584             };
585              
586 0           return $status;
587             }
588              
589             sub remove_address {
590 0     0 1   my ($self, $args) = @_;
591              
592 0 0         return 0 unless ($self->{main}->{conf}->{use_auto_whitelist});
593              
594 0 0         unless ($args->{address}) {
595 0 0         print "SpamAssassin auto-whitelist: failed to remove address\n" if ($args->{cli_p});
596 0           dbg("auto-whitelist: failed to remove address");
597 0           return 0;
598             }
599              
600 0           my $whitelist;
601             my $status;
602              
603             eval {
604 0           $whitelist = Mail::SpamAssassin::AutoWhitelist->new($self->{main});
605              
606 0 0         if ($whitelist->remove_address($args->{address}, $args->{signedby})) {
607 0 0         print "SpamAssassin auto-whitelist: removing address: " . $args->{address} . "\n" if ($args->{cli_p});
608 0           dbg("auto-whitelist: removing address: " . $args->{address});
609 0           $status = 1;
610             }
611             else {
612 0 0         print "SpamAssassin auto-whitelist: error removing address\n" if ($args->{cli_p});
613 0           dbg("auto-whitelist: error removing address");
614 0           $status = 0;
615             }
616            
617 0           $whitelist->finish();
618 0           1;
619 0 0         } or do {
620 0 0         my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
  0            
621 0           warn("auto-whitelist: open of auto-whitelist file failed: $eval_stat\n");
622 0           eval { $whitelist->finish(); };
  0            
623 0           return 0;
624             };
625              
626 0           return $status;
627             }
628              
629             1;
630              
631             =back
632              
633             =cut