File Coverage

blib/lib/Mail/SpamAssassin/Plugin/DNSEval.pm
Criterion Covered Total %
statement 40 266 15.0
branch 0 140 0.0
condition 1 63 1.5
subroutine 10 30 33.3
pod 5 21 23.8
total 56 520 10.7


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             =head1 NAME
19              
20             DNSEVAL - look up URLs against DNS blocklists
21              
22             =head1 SYNOPSIS
23              
24             loadplugin Mail::SpamAssassin::Plugin::DNSEval
25              
26             rbl_headers EnvelopeFrom,Reply-To,Disposition-Notification-To
27             header RBL_IP eval:check_rbl_headers('rbl', 'rbl.example.com.', '127.0.0.2')
28             describe RBL_IP From address associated with spam domains
29             tflags RBL_IP net
30             reuse RBL_IP
31              
32             Supported extra tflags from SpamAssassin 3.4.3:
33             domains_only - only non-IP-address "host" components are queried
34             ips_only - only IP addresses as the "host" component will be queried
35              
36             =head1 DESCRIPTION
37              
38             The DNSEval plugin queries dns to see if a domain or an ip address
39             present on one of email's headers is on a particular rbl.
40              
41             =cut
42              
43             package Mail::SpamAssassin::Plugin::DNSEval;
44              
45 21     21   160 use Mail::SpamAssassin::Plugin;
  21         54  
  21         677  
46 21     21   118 use Mail::SpamAssassin::Logger;
  21         41  
  21         1318  
47 21     21   161 use Mail::SpamAssassin::Constants qw(:ip);
  21         58  
  21         2621  
48 21     21   161 use Mail::SpamAssassin::Util qw(reverse_ip_address);
  21         71  
  21         1223  
49              
50 21     21   178 use strict;
  21         57  
  21         530  
51 21     21   121 use warnings;
  21         69  
  21         742  
52             # use bytes;
53 21     21   134 use re 'taint';
  21         54  
  21         79063  
54              
55             our @ISA = qw(Mail::SpamAssassin::Plugin);
56              
57             my $IP_ADDRESS = IP_ADDRESS;
58             my $IP_PRIVATE = IP_PRIVATE;
59              
60             # constructor: register the eval rule
61             sub new {
62 62     62 1 230 my $class = shift;
63 62         169 my $mailsaobject = shift;
64              
65             # some boilerplate...
66 62   33     512 $class = ref($class) || $class;
67 62         426 my $self = $class->SUPER::new($mailsaobject);
68 62         165 bless ($self, $class);
69              
70             # this is done this way so that the same list can be used here and in
71             # check_start()
72 62         477 $self->{'evalrules'} = [
73             'check_rbl_accreditor',
74             'check_rbl',
75             'check_rbl_ns_from',
76             'check_rbl_txt',
77             'check_rbl_sub',
78             'check_rbl_results_for',
79             'check_rbl_from_host',
80             'check_rbl_from_domain',
81             'check_rbl_envfrom',
82             'check_rbl_headers',
83             'check_rbl_rcvd',
84             'check_dns_sender',
85             ];
86              
87 62         347 $self->set_config($mailsaobject->{conf});
88 62         197 foreach(@{$self->{'evalrules'}}) {
  62         194  
89 744         1518 $self->register_eval_rule($_);
90             }
91              
92 62         591 return $self;
93             }
94              
95             =head1 USER PREFERENCES
96              
97             The following options can be used in both site-wide (C<local.cf>) and
98             user-specific (C<user_prefs>) configuration files to customize how
99             SpamAssassin handles incoming email messages.
100              
101             =over
102              
103             =item rbl_headers
104              
105             This option tells SpamAssassin in which headers to check for content
106             used to query the specified rbl.
107             If on the headers content there is an email address, an ip address
108             or a domain name, it will be checked on the specified rbl.
109             The configuration option can be overridden by passing an headers list as
110             last parameter to check_rbl_headers.
111             The default headers checked are:
112              
113             =back
114              
115             =over
116              
117             =item *
118              
119             EnvelopeFrom
120              
121             =item *
122              
123             Reply-To
124              
125             =item *
126              
127             Disposition-Notification-To
128              
129             =item *
130              
131             X-WebmailclientIP
132              
133             =item *
134              
135             X-Source-IP
136              
137             =back
138              
139             =cut
140              
141             sub set_config {
142 62     62 0 198 my ($self, $conf) = @_;
143 62         151 my @cmds;
144 62         427 push(@cmds, {
145             setting => 'rbl_headers',
146             default => 'EnvelopeFrom,Reply-To,Disposition-Notification-To,X-WebmailclientIP,X-Source-IP',
147             type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
148             }
149             );
150 62         324 $conf->{parser}->register_commands(\@cmds);
151             }
152              
153             # this is necessary because PMS::run_rbl_eval_tests() calls these functions
154             # directly as part of PMS
155             sub check_start {
156 81     81 1 231 my ($self, $opts) = @_;
157              
158 81         167 foreach(@{$self->{'evalrules'}}) {
  81         301  
159 972         2032 $opts->{'permsgstatus'}->register_plugin_eval_glue($_);
160             }
161             }
162              
163             sub ip_list_uniq_and_strip_private {
164 0     0 0   my ($self, @origips) = @_;
165 0           my @ips;
166             my %seen;
167 0           my $IP_PRIVATE = IP_PRIVATE;
168 0           foreach my $ip (@origips) {
169 0 0         next unless $ip;
170 0 0         next if (exists ($seen{$ip})); $seen{$ip} = 1;
  0            
171 0 0         next if ($ip =~ /$IP_PRIVATE/o);
172 0           push(@ips, $ip);
173             }
174 0           return @ips;
175             }
176              
177             # check an RBL if the message contains an "accreditor assertion,"
178             # that is, the message contains the name of a service that will vouch
179             # for their practices.
180             #
181             sub check_rbl_accreditor {
182 0     0 0   my ($self, $pms, $rule, $set, $rbl_server, $subtest, $accreditor) = @_;
183              
184 0 0         if (!defined $pms->{accreditor_tag}) {
185 0           $self->message_accreditor_tag($pms);
186             }
187 0 0         if ($pms->{accreditor_tag}->{$accreditor}) {
188 0           $self->check_rbl_backend($pms, $rule, $set, $rbl_server, 'A', $subtest);
189             }
190 0           return 0;
191             }
192              
193             # Check for an Accreditor Assertion within the message, that is, the name of
194             # a third-party who will vouch for the sender's practices. The accreditor
195             # can be asserted in the EnvelopeFrom like this:
196             #
197             # listowner@a--accreditor.mail.example.com
198             #
199             # or in an 'Accreditor" Header field, like this:
200             #
201             # Accreditor: accreditor1, parm=value; accreditor2, parm-value
202             #
203             # This implementation supports multiple accreditors, but ignores any
204             # parameters in the header field.
205             #
206             sub message_accreditor_tag {
207 0     0 0   my ($self, $pms) = @_;
208 0           my %acctags;
209              
210 0 0         if ($pms->get('EnvelopeFrom:addr') =~ /[@.]a--([a-z0-9]{3,})\./i) {
211 0           (my $tag = $1) =~ tr/A-Z/a-z/;
212 0           $acctags{$tag} = -1;
213             }
214 0           my $accreditor_field = $pms->get('Accreditor',undef);
215 0 0         if (defined $accreditor_field) {
216 0           my @accreditors = split(/,/, $accreditor_field);
217 0           foreach my $accreditor (@accreditors) {
218 0           my @terms = split(' ', $accreditor);
219 0 0         if ($#terms >= 0) {
220 0           my $tag = $terms[0];
221 0           $tag =~ tr/A-Z/a-z/;
222 0           $acctags{$tag} = -1;
223             }
224             }
225             }
226 0           $pms->{accreditor_tag} = \%acctags;
227             }
228              
229             sub check_rbl_backend {
230 0     0 0   my ($self, $pms, $rule, $set, $rbl_server, $type, $subtest) = @_;
231 0           local ($_);
232              
233             # First check that DNS is available, if not do not perform this check
234 0 0         return 0 if $self->{main}->{conf}->{skip_rbl_checks};
235 0 0         return 0 unless $pms->is_dns_available();
236              
237 0 0 0       if (($rbl_server !~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) &&
      0        
238             (index($rbl_server, '.') >= 0) &&
239             ($rbl_server !~ /\.$/)) {
240 0           $rbl_server .= ".";
241             }
242              
243 0           dbg("dns: checking RBL $rbl_server, set $set");
244              
245             # ok, make a list of all the IPs in the untrusted set
246 0           my @fullips = map { $_->{ip} } @{$pms->{relays_untrusted}};
  0            
  0            
247              
248             # now, make a list of all the IPs in the external set, for use in
249             # notfirsthop testing. This will often be more IPs than found
250             # in @fullips. It includes the IPs that are trusted, but
251             # not in internal_networks.
252             my @fullexternal = map {
253 0 0         (!$_->{internal}) ? ($_->{ip}) : ()
254 0           } @{$pms->{relays_trusted}};
  0            
255 0           push (@fullexternal, @fullips); # add untrusted set too
256              
257             # Make sure a header significantly improves results before adding here
258             # X-Sender-Ip: could be worth using (very low occurence for me)
259             # X-Sender: has a very low bang-for-buck for me
260 0           my $IP_ADDRESS = IP_ADDRESS;
261 0           my @originating;
262 0           for my $header (@{$pms->{conf}->{originating_ip_headers}}) {
  0            
263 0           my $str = $pms->get($header,undef);
264 0 0 0       next unless defined $str && $str ne '';
265 0           push (@originating, ($str =~ m/($IP_ADDRESS)/g));
266             }
267              
268             # Let's go ahead and trim away all private ips (KLC)
269             # also uniq the list and strip dups. (jm)
270 0           my @ips = $self->ip_list_uniq_and_strip_private(@fullips);
271              
272             # if there's no untrusted IPs, it means we trust all the open-internet
273             # relays, so we can return right now.
274 0 0         return 0 unless (scalar @ips + scalar @originating > 0);
275              
276 0           dbg("dns: IPs found: full-external: ".join(", ", @fullexternal).
277             " untrusted: ".join(", ", @ips).
278             " originating: ".join(", ", @originating));
279              
280 0           my $trusted = $self->{main}->{conf}->{trusted_networks};
281              
282             # If name is foo-notfirsthop, check all addresses except for
283             # the originating one. Suitable for use with dialup lists, like the PDL.
284             # note that if there's only 1 IP in the untrusted set, do NOT pop the
285             # list, since it'd remove that one, and a legit user is supposed to
286             # use their SMTP server (ie. have at least 1 more hop)!
287             # If name is foo-lastexternal, check only the Received header just before
288             # it enters our internal networks; we can trust it and it's the one that
289             # passed mail between networks
290 0 0         if ($set =~ /-(notfirsthop|lastexternal)$/)
    0          
291             {
292             # use the external IP set, instead of the trusted set; the user may have
293             # specified some third-party relays as trusted. Also, don't use
294             # @originating; those headers are added by a phase of relaying through
295             # a server like Hotmail, which is not going to be in dialup lists anyway.
296 0           @ips = $self->ip_list_uniq_and_strip_private(@fullexternal);
297 0 0         if ($1 eq "lastexternal") {
298 0 0         @ips = (defined $ips[0]) ? ($ips[0]) : ();
299             } else {
300 0 0         pop @ips if (scalar @ips > 1);
301             }
302             }
303             # If name is foo-firsttrusted, check only the Received header just
304             # after it enters our trusted networks; that's the only one we can
305             # trust the IP address from (since our relay added that header).
306             # And if name is foo-untrusted, check any untrusted IP address.
307             elsif ($set =~ /-(first|un)trusted$/)
308             {
309 0           my @tips;
310 0           foreach my $ip (@originating) {
311 0 0 0       if ($ip && !$trusted->contains_ip($ip)) {
312 0           push(@tips, $ip);
313             }
314             }
315 0           @ips = $self->ip_list_uniq_and_strip_private (@ips, @tips);
316 0 0         if ($1 eq "first") {
317 0 0         @ips = (defined $ips[0]) ? ($ips[0]) : ();
318             } else {
319 0           shift @ips;
320             }
321             }
322             else
323             {
324 0           my @tips;
325 0           foreach my $ip (@originating) {
326 0 0 0       if ($ip && !$trusted->contains_ip($ip)) {
327 0           push(@tips, $ip);
328             }
329             }
330              
331             # add originating IPs as untrusted IPs (if they are untrusted)
332 0           @ips = reverse $self->ip_list_uniq_and_strip_private (@ips, @tips);
333             }
334              
335             # How many IPs max you check in the received lines
336 0           my $checklast=$self->{main}->{conf}->{num_check_received};
337              
338 0 0         if (scalar @ips > $checklast) {
339 0           splice (@ips, $checklast); # remove all others
340             }
341              
342 0           my $tflags = $pms->{conf}->{tflags}->{$rule};
343              
344             # Trusted relays should only be checked against nice rules (dnswls)
345 0 0 0       if (defined $tflags && $tflags !~ /\bnice\b/) {
346             # remove trusted hosts from beginning
347 0   0       while (@ips && $trusted->contains_ip($ips[0])) { shift @ips }
  0            
348             }
349              
350 0 0         unless (scalar @ips > 0) {
351 0           dbg("dns: no untrusted IPs to check");
352 0           return 0;
353             }
354              
355 0           dbg("dns: only inspecting the following IPs: ".join(", ", @ips));
356              
357 0           eval {
358 0           foreach my $ip (@ips) {
359 0           my $revip = reverse_ip_address($ip);
360 0 0         $pms->do_rbl_lookup($rule, $set, $type,
361             $revip.'.'.$rbl_server, $subtest) if defined $revip;
362             }
363             };
364              
365             # note that results are not handled here, hits are handled directly
366             # as DNS responses are harvested
367 0           return 0;
368             }
369              
370             sub check_rbl {
371 0     0 0   my ($self, $pms, $rule, $set, $rbl_server, $subtest) = @_;
372 0           $self->check_rbl_backend($pms, $rule, $set, $rbl_server, 'A', $subtest);
373             }
374              
375             sub check_rbl_txt {
376 0     0 0   my ($self, $pms, $rule, $set, $rbl_server, $subtest) = @_;
377 0           $self->check_rbl_backend($pms, $rule, $set, $rbl_server, 'TXT', $subtest);
378             }
379              
380             # run for first message
381             sub check_rbl_sub {
382 0     0 0   my ($self, $pms, $rule, $set, $subtest) = @_;
383              
384 0 0         return 0 if $self->{main}->{conf}->{skip_rbl_checks};
385 0 0         return 0 unless $pms->is_dns_available();
386              
387 0           $pms->register_rbl_subtest($rule, $set, $subtest);
388             }
389              
390             # backward compatibility
391             sub check_rbl_results_for {
392             #warn "dns: check_rbl_results_for() is deprecated, use check_rbl_sub()\n";
393 0     0 0   check_rbl_sub(@_);
394             }
395              
396             # this only checks the address host name and not the domain name because
397             # using the domain name had much worse results for dsn.rfc-ignorant.org
398             sub check_rbl_from_host {
399 0     0 0   my ($self, $pms, $rule, $set, $rbl_server, $subtest) = @_;
400 0           _check_rbl_addresses($self, $pms, $rule, $set, $rbl_server, $subtest, $_[1]->all_from_addrs());
401             }
402              
403             sub check_rbl_headers {
404 0     0 0   my ($self, $pms, $rule, $set, $rbl_server, $subtest, $test_headers) = @_;
405              
406 0 0         return 0 if $self->{main}->{conf}->{skip_rbl_checks};
407 0 0         return 0 if !$pms->is_dns_available();
408              
409 0           my @env_hdr;
410 0           my $conf = $self->{main}->{conf};
411              
412 0 0         if ( defined $test_headers ) {
413 0           @env_hdr = split(/,/, $test_headers);
414             } else {
415 0           @env_hdr = split(/,/, $conf->{rbl_headers});
416             }
417              
418 0           foreach my $rbl_headers (@env_hdr) {
419 0           my $addr = $_[1]->get($rbl_headers.':addr', undef);
420 0 0 0       if ( defined $addr && $addr =~ /\@([^\@\s]+)/ ) {
421 0           $self->_check_rbl_addresses($pms, $rule, $set, $rbl_server,
422             $subtest, $addr);
423             } else {
424 0           my $host = $pms->get($rbl_headers);
425 0           chomp($host);
426 0 0         if($host =~ /^$IP_ADDRESS$/ ) {
427 0 0 0       return if ($conf->{tflags}->{$rule}||'') =~ /\bdomains_only\b/;
428 0           $host = reverse_ip_address($host);
429             } else {
430 0 0 0       return if ($conf->{tflags}->{$rule}||'') =~ /\bips_only\b/;
431             }
432 0 0 0       $pms->do_rbl_lookup($rule, $set, 'A',
433             "$host.$rbl_server", $subtest) if ( defined $host and $host ne "");
434             }
435             }
436             }
437              
438             =over 4
439              
440             =item check_rbl_from_domain
441              
442             This checks all the from addrs domain names as an alternate to check_rbl_from_host. As of v3.4.1, it has been improved to include a subtest for a specific octet.
443              
444             =back
445              
446             =cut
447              
448             sub check_rbl_from_domain {
449 0     0 1   my ($self, $pms, $rule, $set, $rbl_server, $subtest) = @_;
450 0           _check_rbl_addresses($self, $pms, $rule, $set, $rbl_server, $subtest, $_[1]->all_from_addrs_domains());
451             }
452              
453             =over 4
454              
455             =item check_rbl_ns_from
456              
457             This checks the dns server of the from addrs domain name.
458             It is possible to include a subtest for a specific octet.
459              
460             =back
461              
462             =cut
463              
464             sub check_rbl_ns_from {
465 0     0 1   my ($self, $pms, $rule, $set, $rbl_server, $subtest) = @_;
466 0           my $domain;
467 0           my @nshost = ();
468              
469 0 0         return 0 if $self->{main}->{conf}->{skip_rbl_checks};
470 0 0         return 0 unless $pms->is_dns_available();
471              
472 0           for my $from ($pms->get('EnvelopeFrom:addr')) {
473 0 0         next unless defined $from;
474 0           $from =~ tr/././s; # bug 3366
475 0 0         if ($from =~ m/ \@ ( [^\@\s]+ \. [^\@\s]+ )/x ) {
476 0           $domain = lc($1);
477 0           last;
478             }
479             }
480 0 0         return 0 unless defined $domain;
481              
482 0           dbg("dns: checking NS for host $domain");
483              
484 0           my $key = "NS:" . $domain;
485 0           my $obj = { dom => $domain, rule => $rule, set => $set, rbl_server => $rbl_server, subtest => $subtest };
486 0           my $ent = {
487             key => $key, zone => $domain, obj => $obj, type => "URI-NS",
488             };
489             # dig $dom ns
490             $ent = $pms->{async}->bgsend_and_start_lookup(
491             $domain, 'NS', undef, $ent,
492 0     0     sub { my ($ent2,$pkt) = @_;
493 0           $self->complete_ns_lookup($pms, $ent2, $pkt, $domain) },
494 0           master_deadline => $pms->{master_deadline} );
495 0           return $ent;
496             }
497              
498             sub complete_ns_lookup {
499 0     0 0   my ($self, $pms, $ent, $pkt, $host) = @_;
500              
501 0           my $rule = $ent->{obj}->{rule};
502 0           my $set = $ent->{obj}->{set};
503 0           my $rbl_server = $ent->{obj}->{rbl_server};
504 0           my $subtest = $ent->{obj}->{subtest};
505              
506 0 0         if (!$pkt) {
507             # $pkt will be undef if the DNS query was aborted (e.g. timed out)
508 0           dbg("DNSEval: complete_ns_lookup aborted %s", $ent->{key});
509 0           return;
510             }
511              
512 0           dbg("DNSEval: complete_ns_lookup %s", $ent->{key});
513 0           my @ns = $pkt->authority;
514              
515 0           foreach my $rr (@ns) {
516 0           my $nshost = $rr->mname;
517 0 0         if(defined($nshost)) {
518 0           chomp($nshost);
519 0 0         if ( defined $subtest ) {
520 0           dbg("dns: checking [$nshost] / $rule / $set / $rbl_server / $subtest");
521             } else {
522 0           dbg("dns: checking [$nshost] / $rule / $set / $rbl_server");
523             }
524 0 0 0       $pms->do_rbl_lookup($rule, $set, 'A',
525             "$nshost.$rbl_server", $subtest) if ( defined $nshost and $nshost ne "");
526             }
527             }
528             }
529              
530             =over 4
531              
532             =item check_rbl_rcvd
533              
534             This checks all received headers domains or ip addresses against a specific rbl.
535             It is possible to include a subtest for a specific octet.
536              
537             =back
538              
539             =cut
540              
541             sub check_rbl_rcvd {
542 0     0 1   my ($self, $pms, $rule, $set, $rbl_server, $subtest) = @_;
543 0           my %seen;
544 0           my @udnsrcvd = ();
545              
546 0 0         return 0 if $self->{main}->{conf}->{skip_rbl_checks};
547 0 0         return 0 if !$pms->is_dns_available();
548              
549 0           my $rcvd = $pms->{relays_untrusted}->[$pms->{num_relays_untrusted} - 1];
550 0           my @dnsrcvd = ( $rcvd->{ip}, $rcvd->{by}, $rcvd->{helo}, $rcvd->{rdns} );
551             # unique values
552 0           foreach my $value (@dnsrcvd) {
553 0 0 0       if ( ( defined $value ) && (! $seen{$value}++ ) ) {
554 0           push @udnsrcvd, $value;
555             }
556             }
557              
558 0           foreach my $host ( @udnsrcvd ) {
559 0 0 0       if((defined $host) and ($host ne "")) {
560 0           chomp($host);
561 0 0         if($host =~ /^$IP_ADDRESS$/ ) {
562 0 0 0       next if ($pms->{conf}->{tflags}->{$rule}||'') =~ /\bdomains_only\b/;
563 0           $host = reverse_ip_address($host);
564             } else {
565 0 0 0       next if ($pms->{conf}->{tflags}->{$rule}||'') =~ /\bips_only\b/;
566 0           $host =~ s/\.$//;
567             }
568 0 0         if ( defined $subtest ) {
569 0           dbg("dns: checking [$host] / $rule / $set / $rbl_server / $subtest");
570             } else {
571 0           dbg("dns: checking [$host] / $rule / $set / $rbl_server");
572             }
573 0 0 0       $pms->do_rbl_lookup($rule, $set, 'A',
574             "$host.$rbl_server", $subtest) if ( defined $host and $host ne "");
575             }
576             }
577 0           return 0;
578             }
579              
580             # this only checks the address host name and not the domain name because
581             # using the domain name had much worse results for dsn.rfc-ignorant.org
582             sub check_rbl_envfrom {
583 0     0 0   my ($self, $pms, $rule, $set, $rbl_server, $subtest) = @_;
584 0           _check_rbl_addresses($self, $pms, $rule, $set, $rbl_server, $subtest, $_[1]->get('EnvelopeFrom:addr',undef));
585             }
586              
587             sub _check_rbl_addresses {
588 0     0     my ($self, $pms, $rule, $set, $rbl_server, $subtest, @addresses) = @_;
589            
590 0 0         return 0 if $self->{main}->{conf}->{skip_rbl_checks};
591 0 0         return 0 unless $pms->is_dns_available();
592              
593 0           my %hosts;
594 0           for (@addresses) {
595 0 0 0       next if !defined($_) || !/ \@ ( [^\@\s]+ )/x;
596 0           my $address = $1;
597             # strip leading & trailing dots (as seen in some e-mail addresses)
598 0           $address =~ s/^\.+//; $address =~ s/\.+\z//;
  0            
599             # squash duplicate dots to avoid an invalid DNS query with a null label
600 0           $address =~ tr/.//s;
601 0 0         $hosts{lc($address)} = 1 if $address =~ /\./; # must by a FQDN
602             }
603 0 0         return unless scalar keys %hosts;
604              
605 0 0 0       if (($rbl_server !~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) &&
      0        
606             (index($rbl_server, '.') >= 0) &&
607             ($rbl_server !~ /\.$/)) {
608 0           $rbl_server .= ".";
609             }
610              
611 0           for my $host (keys %hosts) {
612 0 0         if ($host =~ /^$IP_ADDRESS$/) {
613 0 0 0       next if ($pms->{conf}->{tflags}->{$rule}||'') =~ /\bdomains_only\b/;
614 0           $host = reverse_ip_address($host);
615             } else {
616 0 0 0       next if ($pms->{conf}->{tflags}->{$rule}||'') =~ /\bips_only\b/;
617             }
618 0           dbg("dns: checking [$host] / $rule / $set / $rbl_server");
619 0           $pms->do_rbl_lookup($rule, $set, 'A', "$host.$rbl_server", $subtest);
620             }
621             }
622              
623             sub check_dns_sender {
624 0     0 0   my ($self, $pms, $rule) = @_;
625              
626 0 0         return 0 if $self->{main}->{conf}->{skip_rbl_checks};
627 0 0         return 0 unless $pms->is_dns_available();
628              
629 0           my $host;
630 0           for my $from ($pms->get('EnvelopeFrom:addr',undef)) {
631 0 0         next unless defined $from;
632              
633 0           $from =~ tr/././s; # bug 3366
634 0 0         if ($from =~ m/ \@ ( [^\@\s]+ \. [^\@\s]+ )/x ) {
635 0           $host = lc($1);
636 0           last;
637             }
638             }
639 0 0         return 0 unless defined $host;
640              
641 0 0         if ($host eq 'compiling.spamassassin.taint.org') {
642             # only used when compiling
643 0           return 0;
644             }
645              
646 0           dbg("dns: checking A and MX for host $host");
647              
648 0           $pms->do_dns_lookup($rule, 'A', $host);
649 0           $pms->do_dns_lookup($rule, 'MX', $host);
650              
651             # cache name of host for later checking
652 0           $pms->{sender_host} = $host;
653              
654 0           return 0;
655             }
656              
657             # capability checks for "if can(Mail::SpamAssassin::Plugin::DNSEval::XXX)":
658             #
659 0     0 0   sub has_tflags_domains_only { 1 }
660 0     0 0   sub has_tflags_ips_only { 1 }
661              
662             1;