—# <@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:
#
#
# 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:>
=cut
package
Mail::SpamAssassin::Plugin::AccessDB;
use
Fcntl;
use
strict;
use
warnings;
# use bytes;
our
@ISA
=
qw(Mail::SpamAssassin::Plugin)
;
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;