mirror of
https://git.proxmox.com/git/proxmox-spamassassin
synced 2025-04-28 21:37:19 +00:00
174 lines
4.6 KiB
Perl
174 lines
4.6 KiB
Perl
# <@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>
|
|
|
|
=head1 NAME
|
|
|
|
Mail::SpamAssassin::Plugin::AccessDB - check message against Access Database
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
loadplugin Mail::SpamAssassin::Plugin::AccessDB
|
|
|
|
header ACCESSDB eval:check_access_database('/etc/mail/access.db')
|
|
describe ACCESSDB Message would have been caught by accessdb
|
|
tflags ACCESSDB userconf
|
|
score ACCESSDB 2
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
Many MTAs support access databases, such as Sendmail, Postfix, etc.
|
|
This plugin does similar checks to see whether a message would have
|
|
been flagged.
|
|
|
|
The rule returns false if an entry isn't found, or the entry has a RHS of
|
|
I<OK> or I<SKIP>.
|
|
|
|
The rule returns true if an entry exists and has a RHS of I<REJECT>, I<ERROR>,
|
|
or I<DISCARD>.
|
|
|
|
Note: only the first word (split on non-word characters) of the RHS
|
|
is checked, so C<error:5.7.1:...> means C<ERROR>.
|
|
|
|
B<AccessDB Pointers:>
|
|
|
|
http://www.faqs.org/docs/securing/chap22sec178.html
|
|
http://www.postfix.org/access.5.html
|
|
|
|
=cut
|
|
|
|
package Mail::SpamAssassin::Plugin::AccessDB;
|
|
|
|
use Mail::SpamAssassin::Plugin;
|
|
use Mail::SpamAssassin::Logger;
|
|
use Fcntl;
|
|
use strict;
|
|
use warnings;
|
|
# use bytes;
|
|
use re 'taint';
|
|
|
|
our @ISA = qw(Mail::SpamAssassin::Plugin);
|
|
|
|
use constant HAS_DB_FILE => eval { require DB_File; };
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $mailsaobject = shift;
|
|
|
|
$class = ref($class) || $class;
|
|
my $self = $class->SUPER::new($mailsaobject);
|
|
bless ($self, $class);
|
|
|
|
$self->register_eval_rule("check_access_database", $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS);
|
|
|
|
return $self;
|
|
}
|
|
|
|
sub check_access_database {
|
|
my ($self, $pms, $path) = @_;
|
|
|
|
if (!HAS_DB_FILE) {
|
|
return 0;
|
|
}
|
|
|
|
my %access;
|
|
my %ok = map { $_ => 1 } qw/ OK SKIP /;
|
|
my %bad = map { $_ => 1 } qw/ REJECT ERROR DISCARD /;
|
|
|
|
$path = $self->{main}->sed_path ($path);
|
|
dbg("accessdb: tie-ing to DB file R/O in $path");
|
|
if (tie %access,"DB_File",$path, O_RDONLY) {
|
|
my @lookfor;
|
|
|
|
# Look for "From:" versions as well!
|
|
foreach my $from ($pms->all_from_addrs()) {
|
|
# $user."\@"
|
|
# rotate through $domain and check
|
|
my ($user,$domain) = split(/\@/, $from,2);
|
|
push(@lookfor, "From:$from",$from);
|
|
if ($user) {
|
|
push(@lookfor, "From:$user\@", "$user\@");
|
|
}
|
|
if ($domain) {
|
|
while ($domain =~ /\./) {
|
|
push(@lookfor, "From:$domain", $domain);
|
|
$domain =~ s/^[^.]*\.//;
|
|
}
|
|
push(@lookfor, "From:$domain", $domain);
|
|
}
|
|
}
|
|
|
|
# we can only match this if we have at least 1 untrusted header
|
|
if ($pms->{num_relays_untrusted} > 0) {
|
|
my $lastunt = $pms->{relays_untrusted}->[0];
|
|
|
|
# If there was a reverse lookup, use it in a lookup
|
|
if (! $lastunt->{no_reverse_dns}) {
|
|
my $rdns = $lastunt->{lc_rdns};
|
|
while($rdns =~ /\./) {
|
|
push(@lookfor, "From:$rdns", $rdns);
|
|
$rdns =~ s/^[^.]*\.//;
|
|
}
|
|
push(@lookfor, "From:$rdns", $rdns);
|
|
}
|
|
|
|
# do both IP and net (rotate over IP)
|
|
my ($ip) = $lastunt->{ip};
|
|
$ip =~ tr/0-9.//cd;
|
|
while($ip =~ /\./) {
|
|
push(@lookfor, "From:$ip", $ip);
|
|
$ip =~ s/\.[^.]*$//;
|
|
}
|
|
push(@lookfor, "From:$ip", $ip);
|
|
}
|
|
|
|
my $retval = 0;
|
|
my %cache;
|
|
foreach (@lookfor) {
|
|
next if ($cache{$_}++);
|
|
dbg("accessdb: looking for $_");
|
|
|
|
# Some systems put a null at the end of the key, most don't...
|
|
my $result = $access{$_} || $access{"$_\000"} || next;
|
|
|
|
my ($type) = split(/\W/,$result);
|
|
$type = uc $type;
|
|
|
|
if (exists $ok{$type}) {
|
|
dbg("accessdb: hit OK: $type, $_");
|
|
$retval = 0;
|
|
last;
|
|
}
|
|
if (exists $bad{$type} || $type =~ /^\d+$/) {
|
|
$retval = 1;
|
|
dbg("accessdb: hit not-OK: $type, $_");
|
|
}
|
|
}
|
|
|
|
dbg("accessdb: untie-ing DB file $path");
|
|
untie %access;
|
|
|
|
return $retval;
|
|
}
|
|
else {
|
|
dbg("accessdb: cannot open accessdb $path R/O: $!");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
1;
|