File Coverage

blib/lib/Mail/SpamAssassin/Plugin/DNSEval.pm
Criterion Covered Total %
statement 40 274 14.6
branch 0 148 0.0
condition 1 54 1.8
subroutine 10 30 33.3
pod 5 21 23.8
total 56 527 10.6


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