File Coverage

blib/lib/Mail/SpamAssassin/DBBasedAddrList.pm
Criterion Covered Total %
statement 72 90 80.0
branch 6 18 33.3
condition 9 14 64.2
subroutine 12 13 92.3
pod 6 6 100.0
total 105 141 74.4


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             package Mail::SpamAssassin::DBBasedAddrList;
19              
20 6     6   44 use strict;
  6         14  
  6         234  
21 6     6   38 use warnings;
  6         19  
  6         252  
22             # use bytes;
23 6     6   43 use re 'taint';
  6         14  
  6         274  
24 6     6   38 use Fcntl;
  6         24  
  6         1911  
25              
26 6     6   2084 use Mail::SpamAssassin::PersistentAddrList;
  6         20  
  6         197  
27 6     6   49 use Mail::SpamAssassin::Util qw(untaint_var);
  6         14  
  6         282  
28 6     6   39 use Mail::SpamAssassin::Logger;
  6         15  
  6         5636  
29              
30             our @ISA = qw(Mail::SpamAssassin::PersistentAddrList);
31              
32             ###########################################################################
33              
34             sub new {
35 7     7 1 28 my $class = shift;
36 7   33     48 $class = ref($class) || $class;
37 7         48 my $self = $class->SUPER::new(@_);
38 7         35 $self->{class} = $class;
39 7         16 bless ($self, $class);
40 7         129 $self;
41             }
42              
43             ###########################################################################
44              
45             sub new_checker {
46 10     10 1 24 my ($factory, $main) = @_;
47 10         24 my $class = $factory->{class};
48              
49 10         43 my $self = {
50             'main' => $main,
51             'accum' => { },
52             'is_locked' => 0,
53             'locked_file' => ''
54             };
55              
56 10         80 my @order = split(/\s+/, $main->{conf}->{auto_whitelist_db_modules});
57 10         52 untaint_var(\@order);
58 10         55 my $dbm_module = Mail::SpamAssassin::Util::first_available_module (@order);
59 10 50       40 if (!$dbm_module) {
60             die "auto-whitelist: cannot find a usable DB package from auto_whitelist_db_modules: " .
61 0         0 $main->{conf}->{auto_whitelist_db_modules}."\n";
62             }
63              
64 10         115 my $umask = umask ~ (oct($main->{conf}->{auto_whitelist_file_mode}));
65              
66             # if undef then don't worry -- empty hash!
67 10 50       68 if (defined($main->{conf}->{auto_whitelist_path})) {
68 10         72 my $path = $main->sed_path($main->{conf}->{auto_whitelist_path});
69 10         33 my ($mod1, $mod2);
70              
71 10 50       89 if ($main->{locker}->safe_lock
72             ($path, 30, $main->{conf}->{auto_whitelist_file_mode}))
73             {
74 10         31 $self->{locked_file} = $path;
75 10         23 $self->{is_locked} = 1;
76 10         31 ($mod1, $mod2) = ('R/W', O_RDWR | O_CREAT);
77             }
78             else {
79 0         0 $self->{is_locked} = 0;
80 0         0 ($mod1, $mod2) = ('R/O', O_RDONLY);
81             }
82              
83 10         61 dbg("auto-whitelist: tie-ing to DB file of type $dbm_module $mod1 in $path");
84              
85 10 50 33     65 ($self->{is_locked} && $dbm_module eq 'DB_File') and
86             Mail::SpamAssassin::Util::avoid_db_file_locking_bug($path);
87              
88 10 50       25 if (! tie %{ $self->{accum} }, $dbm_module, $path, $mod2,
  10         720  
89             oct($main->{conf}->{auto_whitelist_file_mode}) & 0666)
90             {
91 0         0 my $err = $!; # might get overwritten later
92 0 0       0 if ($self->{is_locked}) {
93 0         0 $self->{main}->{locker}->safe_unlock($self->{locked_file});
94 0         0 $self->{is_locked} = 0;
95             }
96 0         0 die "auto-whitelist: cannot open auto_whitelist_path $path: $err\n";
97             }
98             }
99 10         70 umask $umask;
100              
101 10         58 bless ($self, $class);
102 10         65 return $self;
103             }
104              
105             ###########################################################################
106              
107             sub finish {
108 10     10 1 22 my $self = shift;
109 10         34 dbg("auto-whitelist: DB addr list: untie-ing and unlocking");
110 10         14 untie %{$self->{accum}};
  10         221  
111 10 50       54 if ($self->{is_locked}) {
112 10         47 dbg("auto-whitelist: DB addr list: file locked, breaking lock");
113 10         67 $self->{main}->{locker}->safe_unlock ($self->{locked_file});
114 10         41 $self->{is_locked} = 0;
115             }
116             # TODO: untrap signals to unlock the db file here
117             }
118              
119             ###########################################################################
120              
121             sub get_addr_entry {
122 11     11 1 39 my ($self, $addr, $signedby) = @_;
123              
124 11         41 my $entry = {
125             addr => $addr,
126             };
127              
128 11   100     307 $entry->{msgcount} = $self->{accum}->{$addr} || 0;
129 11   100     177 $entry->{totscore} = $self->{accum}->{$addr.'|totscore'} || 0;
130              
131 11         108 dbg("auto-whitelist: db-based $addr scores ".$entry->{msgcount}.'/'.$entry->{totscore});
132 11         52 return $entry;
133             }
134              
135             ###########################################################################
136              
137             sub add_score {
138 8     8 1 29 my($self, $entry, $score) = @_;
139              
140 8   100     41 $entry->{msgcount} ||= 0;
141 8   50     44 $entry->{addr} ||= '';
142              
143 8         22 $entry->{msgcount}++;
144 8         30 $entry->{totscore} += $score;
145              
146 8         71 dbg("auto-whitelist: add_score: new count: ".$entry->{msgcount}.", new totscore: ".$entry->{totscore});
147              
148 8         308 $self->{accum}->{$entry->{addr}} = $entry->{msgcount};
149 8         170 $self->{accum}->{$entry->{addr}.'|totscore'} = $entry->{totscore};
150 8         52 return $entry;
151             }
152              
153             ###########################################################################
154              
155             sub remove_entry {
156 0     0 1   my ($self, $entry) = @_;
157              
158 0           my $addr = $entry->{addr};
159 0           delete $self->{accum}->{$addr};
160 0           delete $self->{accum}->{$addr.'|totscore'};
161              
162 0 0         if ($addr =~ /^(.*)\|ip=none$/) {
163             # it doesn't have an IP attached.
164             # try to delete any per-IP entries for this addr as well.
165             # could be slow...
166 0           my $mailaddr = $1;
167              
168 0           while (my ($key, $value) = each %{$self->{accum}}) {
  0            
169             # regex will catch both key and key|totscore entries and delete them
170 0 0         if ($key =~ /^\Q${mailaddr}\E\|/) {
171 0           delete $self->{accum}->{$key};
172             }
173             }
174             }
175             }
176              
177             ###########################################################################
178              
179             1;