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_from_in_blocklist"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_from_in_blacklist"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_to_in_blocklist"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_to_in_blacklist"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_to_in_welcomelist"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_to_in_whitelist"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_to_in_more_spam"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_to_in_all_spam"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_from_in_list"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_replyto_in_list"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_to_in_list"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_from_in_welcomelist"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_from_in_whitelist"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_forged_in_welcomelist"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_forged_in_whitelist"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_from_in_default_welcomelist"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_from_in_default_whitelist"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_forged_in_default_welcomelist"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_forged_in_default_whitelist"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_mailfrom_matches_rcvd"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_uri_host_listed"
,
$Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS
);
$self
->register_eval_rule(
"check_uri_host_in_blocklist"
);
$self
->register_eval_rule(
"check_uri_host_in_blacklist"
);
$self
->register_eval_rule(
"check_uri_host_in_welcomelist"
);
$self
->register_eval_rule(
"check_uri_host_in_whitelist"
);
return
$self
;
}
sub
check_from_in_blocklist {
my
(
$self
,
$pms
) =
@_
;
foreach
(
$pms
->all_from_addrs()) {
if
(
$self
->_check_welcomelist (
$self
->{main}->{conf}->{blocklist_from},
$_
)) {
return
1;
}
}
return
0;
}
*check_from_in_blacklist
= \
&check_from_in_blocklist
;
sub
check_to_in_blocklist {
my
(
$self
,
$pms
) =
@_
;
foreach
(
$pms
->all_to_addrs()) {
if
(
$self
->_check_welcomelist (
$self
->{main}->{conf}->{blocklist_to},
$_
)) {
return
1;
}
}
return
0;
}
*check_to_in_blacklist
= \
&check_to_in_blocklist
;
sub
check_to_in_welcomelist {
my
(
$self
,
$pms
) =
@_
;
foreach
(
$pms
->all_to_addrs()) {
if
(
$self
->_check_welcomelist (
$self
->{main}->{conf}->{welcomelist_to},
$_
)) {
return
1;
}
}
return
0;
}
*check_to_in_whitelist
= \
&check_to_in_welcomelist
;
sub
check_to_in_more_spam {
my
(
$self
,
$pms
) =
@_
;
foreach
(
$pms
->all_to_addrs()) {
if
(
$self
->_check_welcomelist (
$self
->{main}->{conf}->{more_spam_to},
$_
)) {
return
1;
}
}
return
0;
}
sub
check_to_in_all_spam {
my
(
$self
,
$pms
) =
@_
;
foreach
(
$pms
->all_to_addrs()) {
if
(
$self
->_check_welcomelist (
$self
->{main}->{conf}->{all_spam_to},
$_
)) {
return
1;
}
}
return
0;
}
sub
check_from_in_list {
my
(
$self
,
$pms
,
$list
) =
@_
;
my
$list_ref
=
$pms
->{conf}->{
$list
};
unless
(
defined
$list_ref
) {
warn
"eval: could not find list $list"
;
return
0;
}
foreach
my
$addr
(
$pms
->all_from_addrs()) {
if
(
$self
->_check_welcomelist (
$list_ref
,
$addr
)) {
return
1;
}
}
return
0;
}
sub
check_replyto_in_list {
my
(
$self
,
$pms
,
$list
) =
@_
;
my
$list_ref
=
$pms
->{conf}->{
$list
};
unless
(
defined
$list_ref
) {
warn
"eval: could not find list $list"
;
return
0;
}
my
$replyto
=
$pms
->get(
"Reply-To:addr"
);
return
0
if
$replyto
eq
''
;
if
(
$self
->_check_welcomelist (
$list_ref
,
$replyto
)) {
return
1;
}
return
0;
}
sub
check_wb_list {
my
(
$self
,
$params
) =
@_
;
return
unless
(
defined
$params
->{permsgstatus});
return
unless
(
defined
$params
->{type});
return
unless
(
defined
$params
->{list});
if
(
lc
$params
->{type} eq
"to"
) {
return
$self
->check_to_in_list(
$params
->{permsgstatus},
$params
->{list});
}
elsif
(
lc
$params
->{type} eq
"from"
) {
return
$self
->check_from_in_list(
$params
->{permsgstatus},
$params
->{list});
}
return
;
}
sub
check_to_in_list {
my
(
$self
,
$pms
,
$list
) =
@_
;
my
$list_ref
=
$pms
->{conf}->{
$list
};
unless
(
defined
$list_ref
) {
warn
"eval: could not find list $list"
;
return
0;
}
foreach
my
$addr
(
$pms
->all_to_addrs()) {
if
(
$self
->_check_welcomelist (
$list_ref
,
$addr
)) {
return
1;
}
}
return
0;
}
sub
check_from_in_welcomelist {
my
(
$self
,
$pms
) =
@_
;
$self
->_check_from_in_welcomelist(
$pms
)
unless
exists
$pms
->{from_in_welcomelist};
return
(
$pms
->{from_in_welcomelist} > 0);
}
*check_from_in_whitelist
= \
&check_from_in_welcomelist
;
sub
check_forged_in_welcomelist {
my
(
$self
,
$pms
) =
@_
;
$self
->_check_from_in_welcomelist(
$pms
)
unless
exists
$pms
->{from_in_welcomelist};
$self
->_check_from_in_default_welcomelist(
$pms
)
unless
exists
$pms
->{from_in_default_welcomelist};
return
(
$pms
->{from_in_welcomelist} < 0) && (
$pms
->{from_in_default_welcomelist} == 0);
}
*check_forged_in_whitelist
= \
&check_forged_in_welcomelist
;
sub
check_from_in_default_welcomelist {
my
(
$self
,
$pms
) =
@_
;
$self
->_check_from_in_default_welcomelist(
$pms
)
unless
exists
$pms
->{from_in_default_welcomelist};
return
(
$pms
->{from_in_default_welcomelist} > 0);
}
*check_from_in_default_whitelist
= \
&check_from_in_default_welcomelist
;
sub
check_forged_in_default_welcomelist {
my
(
$self
,
$pms
) =
@_
;
$self
->_check_from_in_default_welcomelist(
$pms
)
unless
exists
$pms
->{from_in_default_welcomelist};
$self
->_check_from_in_welcomelist(
$pms
)
unless
exists
$pms
->{from_in_welcomelist};
return
(
$pms
->{from_in_default_welcomelist} < 0) && (
$pms
->{from_in_welcomelist} == 0);
}
*check_forged_in_default_whitelist
= \
&check_forged_in_default_welcomelist
;
sub
_check_from_in_welcomelist {
my
(
$self
,
$pms
) =
@_
;
my
$found_match
= 0;
foreach
(
$pms
->all_from_addrs()) {
if
(
$self
->_check_welcomelist (
$self
->{main}->{conf}->{welcomelist_from},
$_
)) {
$pms
->{from_in_welcomelist} = 1;
return
;
}
my
$wh
=
$self
->_check_welcomelist_rcvd (
$pms
,
$self
->{main}->{conf}->{welcomelist_from_rcvd},
$_
);
if
(
$wh
== 1) {
$pms
->{from_in_welcomelist} = 1;
return
;
}
elsif
(
$wh
== -1) {
$found_match
= -1;
}
}
$pms
->{from_in_welcomelist} =
$found_match
;
return
;
}
sub
_check_from_in_default_welcomelist {
my
(
$self
,
$pms
) =
@_
;
my
$found_match
= 0;
foreach
(
$pms
->all_from_addrs()) {
my
$wh
=
$self
->_check_welcomelist_rcvd (
$pms
,
$self
->{main}->{conf}->{def_welcomelist_from_rcvd},
$_
);
if
(
$wh
== 1) {
$pms
->{from_in_default_welcomelist} = 1;
return
;
}
elsif
(
$wh
== -1) {
$found_match
= -1;
}
}
$pms
->{from_in_default_welcomelist} =
$found_match
;
return
;
}
sub
check_mailfrom_matches_rcvd {
my
(
$self
,
$pms
) =
@_
;
my
$sender
=
$pms
->get(
"EnvelopeFrom:addr"
);
return
0
if
$sender
eq
''
;
return
$self
->_check_addr_matches_rcvd(
$pms
,
$sender
);
}
sub
_check_addr_matches_rcvd {
my
(
$self
,
$pms
,
$addr
) =
@_
;
local
$1;
return
0
if
$addr
!~ / \@ ( [^\@]+ \. [^\@]+ ) \z/x;
my
$addr_domain
=
lc
$1;
my
@relays
;
if
(
$pms
->{num_relays_untrusted} > 0) {
@relays
=
$pms
->{relays_untrusted}->[0];
}
elsif
(
$pms
->{num_relays_trusted} > 0) {
push
(
@relays
, @{
$pms
->{relays_trusted}});
}
return
0
if
!
@relays
;
my
(
$adrh
,
$adrd
) =
$self
->{main}->{registryboundaries}->split_domain(
$addr_domain
);
my
$match
= 0;
my
$any_tried
= 0;
foreach
my
$rly
(
@relays
) {
my
$relay_rdns
=
$rly
->{lc_rdns};
next
if
!
defined
$relay_rdns
||
$relay_rdns
eq
''
;
my
(
$rlyh
,
$rlyd
) =
$self
->{main}->{registryboundaries}->split_domain(
$relay_rdns
);
$any_tried
= 1;
if
(
$adrd
eq
$rlyd
) {
dbg(
"rules: $addr MATCHES relay $relay_rdns ($adrd)"
);
$match
= 1;
last
;
}
}
if
(
$any_tried
&& !
$match
) {
dbg(
"rules: %s does NOT match relay(s) %s"
,
$addr
,
join
(
', '
,
map
{
$_
->{lc_rdns} }
@relays
));
}
return
$match
;
}
sub
_check_welcomelist_rcvd {
my
(
$self
,
$pms
,
$list
,
$addr
) =
@_
;
return
0
unless
(
$pms
->{num_relays_untrusted}+
$pms
->{num_relays_trusted} > 0);
my
@relays
;
if
(
$pms
->{num_relays_untrusted} > 0) {
@relays
=
$pms
->{relays_untrusted}->[0];
}
if
(
$pms
->{num_relays_trusted} > 0 && !
$pms
->{num_relays_untrusted} ) {
push
(
@relays
, @{
$pms
->{relays_trusted}});
}
$addr
=
lc
$addr
;
my
$found_forged
= 0;
foreach
my
$welcome_addr
(
keys
%{
$list
}) {
my
$regexp
=
$list
->{
$welcome_addr
}{re};
foreach
my
$domain
(@{
$list
->{
$welcome_addr
}{domain}}) {
if
(
$addr
=~
$regexp
) {
my
$match
;
foreach
my
$lastunt
(
@relays
) {
local
($1,$2);
if
(
$domain
=~ m{^ \[ (.*) \] ( / \d{1,3} )? \z}sx) {
my
(
$wl_ip
,
$rly_ip
) = ($1,
$lastunt
->{ip});
$wl_ip
.= $2
if
defined
$2;
if
(!
defined
$rly_ip
||
$rly_ip
eq
''
) {
}
elsif
(
$wl_ip
=~ /^\d+\.\d+\.\d+\.\d+\z/s) {
if
(
$wl_ip
eq
$rly_ip
) {
$match
= 1;
last
}
}
elsif
(
$wl_ip
=~ /^[\d\.]+\z/s) {
$wl_ip
=~ s/\.*\z/./;
if
(
$rly_ip
=~ /^\Q
$wl_ip
\E/) {
$match
= 1;
last
}
}
else
{
my
$rly_ip_obj
= NetAddr::IP->new(
$rly_ip
);
if
(!
defined
$rly_ip_obj
) {
dbg(
"rules: bad IP address in relay: %s, sender: %s"
,
$rly_ip
,
$addr
);
}
else
{
my
$wl_ip_obj
= NetAddr::IP->new(
$wl_ip
);
if
(!
defined
$wl_ip_obj
) {
info(
"rules: bad IP address in welcomelist: %s"
,
$wl_ip
);
}
elsif
(
$wl_ip_obj
->contains(
$rly_ip_obj
)) {
dbg(
"rules: relay addr %s matches welcomelist %s, sender: %s"
,
$rly_ip
,
$wl_ip_obj
,
$addr
);
$match
= 1;
last
;
}
else
{
dbg(
"rules: relay addr %s does not match wl %s, sender %s"
,
$rly_ip
,
$wl_ip_obj
,
$addr
);
}
}
}
}
else
{
my
$rdns
=
$lastunt
->{lc_rdns};
if
(
$rdns
=~ /(?:^|\.)\Q${domain}\E$/i) {
$match
=1;
last
}
}
}
if
(
$match
) {
dbg(
"rules: address %s matches (def_)welcomelist_from_rcvd %s %s"
,
$addr
,
$list
->{
$welcome_addr
}{re},
$domain
);
return
1;
}
$found_forged
= -1;
}
}
}
if
(
$found_forged
) {
my
$wlist
=
$pms
->{conf}->{welcomelist_allows_relays};
foreach
my
$regexp
(
values
%{
$wlist
}) {
if
(
$addr
=~
$regexp
) {
$found_forged
= 0;
last
;
}
}
}
return
$found_forged
;
}
sub
_check_welcomelist {
my
(
$self
,
$list
,
$addr
) =
@_
;
$addr
=
lc
$addr
;
if
(
defined
(
$list
->{
$addr
})) {
return
1; }
foreach
my
$regexp
(
values
%{
$list
}) {
if
(
$addr
=~
$regexp
) {
dbg(
"rules: address $addr matches welcomelist or blocklist regexp: $regexp"
);
return
1;
}
}
return
0;
}
sub
check_uri_host_in_blocklist {
my
(
$self
,
$pms
) =
@_
;
$self
->check_uri_host_listed(
$pms
,
'BLOCK'
);
}
*check_uri_host_in_blacklist
= \
&check_uri_host_in_blocklist
;
sub
check_uri_host_in_welcomelist {
my
(
$self
,
$pms
) =
@_
;
$self
->check_uri_host_listed(
$pms
,
'WELCOME'
);
}
*check_uri_host_in_whitelist
= \
&check_uri_host_in_welcomelist
;
sub
check_uri_host_listed {
my
(
$self
,
$pms
,
$subname
) =
@_
;
my
$host_enlisted_ref
=
$self
->_check_uri_host_listed(
$pms
);
if
(
$host_enlisted_ref
) {
my
$matched_host
=
$host_enlisted_ref
->{
$subname
};
if
(
$matched_host
) {
dbg(
"rules: uri host enlisted (%s): %s"
,
$subname
,
$matched_host
);
$pms
->test_log(
"URI: $matched_host"
);
return
1;
}
}
return
0;
}
sub
_check_uri_host_listed {
my
(
$self
,
$pms
) =
@_
;
if
(
$pms
->{
'uri_host_enlisted'
}) {
return
$pms
->{
'uri_host_enlisted'
};
}
my
$uri_lists_href
=
$pms
->{conf}->{uri_host_lists};
if
(!
$uri_lists_href
|| !
%$uri_lists_href
) {
$pms
->{
'uri_host_enlisted'
} = {};
return
$pms
->{
'uri_host_enlisted'
};
}
my
%host_enlisted
;
my
@uri_listnames
=
sort
keys
%$uri_lists_href
;
if
(would_log(
"dbg"
,
"rules"
)) {
foreach
my
$nm
(
@uri_listnames
) {
dbg(
"rules: check_uri_host_listed: (%s) %s"
,
$nm
,
join
(
', '
,
map
{
$uri_lists_href
->{
$nm
}{
$_
} ?
$_
:
'!'
.
$_
}
sort
keys
%{
$uri_lists_href
->{
$nm
}}));
}
}
my
$uris
=
$pms
->get_uri_detail_list();
my
%seen
;
while
(
my
(
$uri
,
$info
) =
each
%$uris
) {
next
if
$uri
=~ /^mailto:/i;
while
(
my
(
$host
,
$domain
) =
each
( %{
$info
->{hosts}} )) {
next
if
$seen
{
$host
};
$seen
{
$host
} = 1;
local
($1,$2);
my
@query_keys
;
if
(
$host
=~ /^\[(.*)\]\z/) {
@query_keys
= ( $1 );
}
elsif
(
$host
=~ /^\d+\.\d+\.\d+\.\d+\z/) {
@query_keys
= (
$host
);
}
elsif
(
$host
ne
''
) {
my
(
$h
) =
$host
;
for
(;;) {
shift
@query_keys
if
@query_keys
> 10;
push
(
@query_keys
,
$h
);
last
if
$h
!~ s{^([^.]*)\.(.*)\z}{$2}s;
}
}
foreach
my
$nm
(
@uri_listnames
) {
my
$match
;
my
$verdict
;
my
$hash_nm_ref
=
$uri_lists_href
->{
$nm
};
foreach
my
$q
(
@query_keys
) {
$verdict
=
$hash_nm_ref
->{
$q
};
if
(
defined
$verdict
) {
$match
=
$q
eq
$host
?
$host
:
"$host ($q)"
;
$match
=
'!'
if
!
$verdict
;
last
;
}
}
if
(
defined
$verdict
) {
$host_enlisted
{
$nm
} =
$match
if
$verdict
;
dbg(
"rules: check_uri_host_listed %s, (%s): %s, search: %s"
,
$uri
,
$nm
,
$match
,
join
(
', '
,
@query_keys
));
}
}
}
}
$pms
->{
'uri_host_enlisted'
} = \
%host_enlisted
;
return
$pms
->{
'uri_host_enlisted'
};
}
1;