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