| # <@LICENSE> |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to you under the Apache License, Version 2.0 |
| # (the "License"); you may not use this file except in compliance with |
| # the License. You may obtain a copy of the License at: |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # </@LICENSE> |
| |
| package Mail::SpamAssassin::DBBasedAddrList; |
| |
| use strict; |
| use warnings; |
| use bytes; |
| use re 'taint'; |
| use Fcntl; |
| |
| use Mail::SpamAssassin::PersistentAddrList; |
| use Mail::SpamAssassin::Util qw(untaint_var); |
| use Mail::SpamAssassin::Logger; |
| |
| our @ISA = qw(Mail::SpamAssassin::PersistentAddrList); |
| |
| ########################################################################### |
| |
| sub new { |
| my $class = shift; |
| $class = ref($class) || $class; |
| my $self = $class->SUPER::new(@_); |
| $self->{class} = $class; |
| bless ($self, $class); |
| $self; |
| } |
| |
| ########################################################################### |
| |
| sub new_checker { |
| my ($factory, $main) = @_; |
| my $class = $factory->{class}; |
| |
| my $self = { |
| 'main' => $main, |
| 'accum' => { }, |
| 'is_locked' => 0, |
| 'locked_file' => '' |
| }; |
| |
| my @order = split (' ', $main->{conf}->{auto_whitelist_db_modules}); |
| untaint_var(\@order); |
| my $dbm_module = Mail::SpamAssassin::Util::first_available_module (@order); |
| if (!$dbm_module) { |
| die "auto-whitelist: cannot find a usable DB package from auto_whitelist_db_modules: " . |
| $main->{conf}->{auto_whitelist_db_modules}."\n"; |
| } |
| |
| my $umask = umask ~ (oct($main->{conf}->{auto_whitelist_file_mode})); |
| |
| # if undef then don't worry -- empty hash! |
| if (defined($main->{conf}->{auto_whitelist_path})) { |
| my $path = $main->sed_path($main->{conf}->{auto_whitelist_path}); |
| my ($mod1, $mod2); |
| |
| if ($main->{locker}->safe_lock |
| ($path, 30, $main->{conf}->{auto_whitelist_file_mode})) |
| { |
| $self->{locked_file} = $path; |
| $self->{is_locked} = 1; |
| ($mod1, $mod2) = ('R/W', O_RDWR | O_CREAT); |
| } |
| else { |
| $self->{is_locked} = 0; |
| ($mod1, $mod2) = ('R/O', O_RDONLY); |
| } |
| |
| dbg("auto-whitelist: tie-ing to DB file of type $dbm_module $mod1 in $path"); |
| |
| ($self->{is_locked} && $dbm_module eq 'DB_File') and |
| Mail::SpamAssassin::Util::avoid_db_file_locking_bug($path); |
| |
| if (! tie %{ $self->{accum} }, $dbm_module, $path, $mod2, |
| oct($main->{conf}->{auto_whitelist_file_mode}) & 0666) |
| { |
| my $err = $!; # might get overwritten later |
| if ($self->{is_locked}) { |
| $self->{main}->{locker}->safe_unlock($self->{locked_file}); |
| $self->{is_locked} = 0; |
| } |
| die "auto-whitelist: cannot open auto_whitelist_path $path: $err\n"; |
| } |
| } |
| umask $umask; |
| |
| bless ($self, $class); |
| return $self; |
| } |
| |
| ########################################################################### |
| |
| sub finish { |
| my $self = shift; |
| dbg("auto-whitelist: DB addr list: untie-ing and unlocking"); |
| untie %{$self->{accum}}; |
| if ($self->{is_locked}) { |
| dbg("auto-whitelist: DB addr list: file locked, breaking lock"); |
| $self->{main}->{locker}->safe_unlock ($self->{locked_file}); |
| $self->{is_locked} = 0; |
| } |
| # TODO: untrap signals to unlock the db file here |
| } |
| |
| ########################################################################### |
| |
| sub get_addr_entry { |
| my ($self, $addr, $signedby) = @_; |
| |
| my $entry = { |
| addr => $addr, |
| }; |
| |
| $entry->{count} = $self->{accum}->{$addr} || 0; |
| $entry->{totscore} = $self->{accum}->{$addr.'|totscore'} || 0; |
| |
| dbg("auto-whitelist: db-based $addr scores ".$entry->{count}.'/'.$entry->{totscore}); |
| return $entry; |
| } |
| |
| ########################################################################### |
| |
| sub add_score { |
| my($self, $entry, $score) = @_; |
| |
| $entry->{count} ||= 0; |
| $entry->{addr} ||= ''; |
| |
| $entry->{count}++; |
| $entry->{totscore} += $score; |
| |
| dbg("auto-whitelist: add_score: new count: ".$entry->{count}.", new totscore: ".$entry->{totscore}); |
| |
| $self->{accum}->{$entry->{addr}} = $entry->{count}; |
| $self->{accum}->{$entry->{addr}.'|totscore'} = $entry->{totscore}; |
| return $entry; |
| } |
| |
| ########################################################################### |
| |
| sub remove_entry { |
| my ($self, $entry) = @_; |
| |
| my $addr = $entry->{addr}; |
| delete $self->{accum}->{$addr}; |
| delete $self->{accum}->{$addr.'|totscore'}; |
| |
| if ($addr =~ /^(.*)\|ip=none$/) { |
| # it doesn't have an IP attached. |
| # try to delete any per-IP entries for this addr as well. |
| # could be slow... |
| my $mailaddr = $1; |
| |
| while (my ($key, $value) = each %{$self->{accum}}) { |
| # regex will catch both key and key|totscore entries and delete them |
| if ($key =~ /^\Q${mailaddr}\E\|/) { |
| delete $self->{accum}->{$key}; |
| } |
| } |
| } |
| } |
| |
| ########################################################################### |
| |
| 1; |