File Coverage

blib/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm
Criterion Covered Total %
statement 238 450 52.8
branch 77 188 40.9
condition 24 71 33.8
subroutine 20 40 50.0
pod 2 18 11.1
total 361 767 47.0


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             URIDNSBL - look up URLs against DNS blocklists
21              
22             =head1 SYNOPSIS
23              
24             loadplugin Mail::SpamAssassin::Plugin::URIDNSBL
25             uridnsbl URIBL_SBLXBL sbl-xbl.spamhaus.org. TXT
26              
27             =head1 DESCRIPTION
28              
29             This works by analysing message text and HTML for URLs, extracting host
30             names from those, then querying various DNS blocklists for either:
31             IP addresses of these hosts (uridnsbl,a) or their nameservers (uridnsbl,ns),
32             or domain names of these hosts (urirhsbl), or domain names of their
33             nameservers (urinsrhsbl, urifullnsrhsbl).
34              
35             =head1 USER SETTINGS
36              
37             =over 4
38              
39             =item skip_uribl_checks ( 0 | 1 ) (default: 0)
40              
41             Turning on the skip_uribl_checks setting will disable the URIDNSBL plugin.
42              
43             By default, SpamAssassin will run URI DNSBL checks. Individual URI blocklists
44             may be disabled selectively by setting a score of a corresponding rule to 0
45             or through the uridnsbl_skip_domain parameter.
46              
47             See also a related configuration parameter skip_rbl_checks,
48             which controls the DNSEval plugin (documented in the Conf man page).
49              
50             =back
51              
52             =over 4
53              
54             =item uridnsbl_skip_domain domain1 domain2 ...
55              
56             Specify a domain, or a number of domains, which should be skipped for the
57             URIBL checks. This is very useful to specify very common domains which are
58             not going to be listed in URIBLs.
59              
60             =back
61              
62             =over 4
63              
64             =item clear_uridnsbl_skip_domain [domain1 domain2 ...]
65              
66             If no argument is given, then clears the entire list of domains declared
67             by I<uridnsbl_skip_domain> configuration directives so far. Any subsequent
68             I<uridnsbl_skip_domain> directives will start creating a new list of skip
69             domains.
70              
71             When given a list of domains as arguments, only the specified domains
72             are removed from the list of skipped domains.
73              
74             =back
75              
76             =head1 RULE DEFINITIONS AND PRIVILEGED SETTINGS
77              
78             =over 4
79              
80             =item uridnsbl NAME_OF_RULE dnsbl_zone lookuptype
81              
82             Specify a lookup. C<NAME_OF_RULE> is the name of the rule to be
83             used, C<dnsbl_zone> is the zone to look up IPs in, and C<lookuptype>
84             is the type of lookup (B<TXT> or B<A>). Note that you must also
85             define a body-eval rule calling C<check_uridnsbl()> to use this.
86              
87             This works by collecting domain names from URLs and querying DNS
88             blocklists with an IP address of host names found in URLs or with
89             IP addresses of their name servers, according to tflags as follows.
90              
91             If the corresponding body rule has a tflag 'a', the DNS blocklist will
92             be queried with an IP address of a host found in URLs.
93              
94             If the corresponding body rule has a tflag 'ns', DNS will be queried
95             for name servers (NS records) of a domain name found in URLs, then
96             these name server names will be resolved to their IP addresses, which
97             in turn will be sent to DNS blocklist.
98              
99             Tflags directive may specify either 'a' or 'ns' or both flags. In absence
100             of any of these two flags, a default is a 'ns', which is compatible with
101             pre-3.4 versions of SpamAssassin.
102              
103             The choice of tflags must correspond to the policy and expected use of
104             each DNS blocklist and is normally not a local decision. As an example,
105             a blocklist expecting queries resulting from an 'a' tflag is a
106             "black_a.txt" ( http://www.uribl.com/datasets.shtml ).
107              
108             Example:
109              
110             uridnsbl URIBL_SBLXBL sbl-xbl.spamhaus.org. TXT
111             body URIBL_SBLXBL eval:check_uridnsbl('URIBL_SBLXBL')
112             describe URIBL_SBLXBL Contains a URL listed in the SBL/XBL blocklist
113             tflags URIBL_SBLXBL net ns
114              
115             =item uridnssub NAME_OF_RULE dnsbl_zone lookuptype subtest
116              
117             Specify a DNSBL-style domain lookup with a sub-test. C<NAME_OF_RULE> is the
118             name of the rule to be used, C<dnsbl_zone> is the zone to look up IPs in,
119             and C<lookuptype> is the type of lookup (B<TXT> or B<A>).
120              
121             Tflags 'ns' and 'a' on a corresponding body rule are recognized and have
122             the same meaning as in the uridnsbl directive.
123              
124             C<subtest> is a sub-test to run against the returned data. The sub-test may
125             be in one of the following forms: m, n1-n2, or n/m, where n,n1,n2,m can be
126             any of: decimal digits, 0x followed by up to 8 hexadecimal digits, or an IPv4
127             address in quad-dot form. The 'A' records (IPv4 dotted address) as returned
128             by DNSBLs lookups are converted into a numerical form (r) and checked against
129             the specified sub-test as follows:
130             for a range n1-n2 the following must be true: (r >= n1 && r <= n2);
131             for a n/m form the following must be true: (r & m) == (n & m);
132             for a single value in quad-dot form the following must be true: r == n;
133             for a single decimal or hex form the following must be true:
134             ((r & n) != 0) && ((r & 0xff000000) == 0x7f000000), i.e. within 127.0.0.0/8
135              
136             Some typical examples of a sub-test are: 127.0.1.2, 127.0.1.20-127.0.1.39,
137             127.0.1.0/255.255.255.0, 0.0.0.16/0.0.0.16, 0x10/0x10, 16, 0x10 .
138              
139             Note that, as with C<uridnsbl>, you must also define a body-eval rule calling
140             C<check_uridnsbl()> to use this.
141              
142             Example:
143              
144             uridnssub URIBL_DNSBL_4 dnsbl.example.org. A 127.0.0.4
145             uridnssub URIBL_DNSBL_8 dnsbl.example.org. A 8
146              
147             =item urirhsbl NAME_OF_RULE rhsbl_zone lookuptype
148              
149             Specify a RHSBL-style domain lookup. C<NAME_OF_RULE> is the name of the rule
150             to be used, C<rhsbl_zone> is the zone to look up domain names in, and
151             C<lookuptype> is the type of lookup (B<TXT> or B<A>). Note that you must also
152             define a body-eval rule calling C<check_uridnsbl()> to use this.
153              
154             An RHSBL zone is one where the domain name is looked up, as a string; e.g. a
155             URI using the domain C<foo.com> will cause a lookup of
156             C<foo.com.uriblzone.net>. Note that hostnames are stripped from the domain
157             used in the URIBL lookup, so the domain C<foo.bar.com> will look up
158             C<bar.com.uriblzone.net>, and C<foo.bar.co.uk> will look up
159             C<bar.co.uk.uriblzone.net>.
160              
161             If an URI consists of an IP address instead of a hostname, the IP address is
162             looked up (using the standard reversed quads method) in each C<rhsbl_zone>.
163              
164             Example:
165              
166             urirhsbl URIBL_RHSBL rhsbl.example.org. TXT
167              
168             =item urirhssub NAME_OF_RULE rhsbl_zone lookuptype subtest
169              
170             Specify a RHSBL-style domain lookup with a sub-test. C<NAME_OF_RULE> is the
171             name of the rule to be used, C<rhsbl_zone> is the zone to look up domain names
172             in, and C<lookuptype> is the type of lookup (B<TXT> or B<A>).
173              
174             C<subtest> is a sub-test to run against the returned data. The sub-test may
175             be in one of the following forms: m, n1-n2, or n/m, where n,n1,n2,m can be
176             any of: decimal digits, 0x followed by up to 8 hexadecimal digits, or an IPv4
177             address in quad-dot form. The 'A' records (IPv4 dotted address) as returned
178             by DNSBLs lookups are converted into a numerical form (r) and checked against
179             the specified sub-test as follows:
180             for a range n1-n2 the following must be true: (r >= n1 && r <= n2);
181             for a n/m form the following must be true: (r & m) == (n & m);
182             for a single value in quad-dot form the following must be true: r == n;
183             for a single decimal or hex form the following must be true:
184             ((r & n) != 0) && ((r & 0xff000000) == 0x7f000000), i.e. within 127.0.0.0/8
185              
186             Some typical examples of a sub-test are: 127.0.1.2, 127.0.1.20-127.0.1.39,
187             127.2.3.0/255.255.255.0, 0.0.0.16/0.0.0.16, 0x10/0x10, 16, 0x10 .
188              
189             Note that, as with C<urirhsbl>, you must also define a body-eval rule calling
190             C<check_uridnsbl()> to use this.
191              
192             Example:
193              
194             urirhssub URIBL_RHSBL_4 rhsbl.example.org. A 127.0.0.4
195             urirhssub URIBL_RHSBL_8 rhsbl.example.org. A 8
196              
197             =item urinsrhsbl NAME_OF_RULE rhsbl_zone lookuptype
198              
199             Perform a RHSBL-style domain lookup against the contents of the NS records
200             for each URI. In other words, a URI using the domain C<foo.com> will cause
201             an NS lookup to take place; assuming that domain has an NS of C<ns0.bar.com>,
202             that will cause a lookup of C<bar.com.uriblzone.net>. Note that hostnames
203             are stripped from both the domain used in the URI, and the domain in the
204             lookup.
205              
206             C<NAME_OF_RULE> is the name of the rule to be used, C<rhsbl_zone> is the zone
207             to look up domain names in, and C<lookuptype> is the type of lookup (B<TXT> or
208             B<A>).
209              
210             Note that, as with C<urirhsbl>, you must also define a body-eval rule calling
211             C<check_uridnsbl()> to use this.
212              
213             =item urinsrhssub NAME_OF_RULE rhsbl_zone lookuptype subtest
214              
215             Specify a RHSBL-style domain-NS lookup, as above, with a sub-test.
216             C<NAME_OF_RULE> is the name of the rule to be used, C<rhsbl_zone> is the zone
217             to look up domain names in, and C<lookuptype> is the type of lookup (B<TXT> or
218             B<A>). C<subtest> is the sub-test to run against the returned data; see
219             <urirhssub>.
220              
221             Note that, as with C<urirhsbl>, you must also define a body-eval rule calling
222             C<check_uridnsbl()> to use this.
223              
224             =item urifullnsrhsbl NAME_OF_RULE rhsbl_zone lookuptype
225              
226             Perform a RHSBL-style domain lookup against the contents of the NS records for
227             each URI. In other words, a URI using the domain C<foo.com> will cause an NS
228             lookup to take place; assuming that domain has an NS of C<ns0.bar.com>, that
229             will cause a lookup of C<ns0.bar.com.uriblzone.net>. Note that hostnames are
230             stripped from the domain used in the URI.
231              
232             C<NAME_OF_RULE> is the name of the rule to be used, C<rhsbl_zone> is the zone
233             to look up domain names in, and C<lookuptype> is the type of lookup (B<TXT> or
234             B<A>).
235              
236             Note that, as with C<urirhsbl>, you must also define a body-eval rule calling
237             C<check_uridnsbl()> to use this.
238              
239             =item urifullnsrhssub NAME_OF_RULE rhsbl_zone lookuptype subtest
240              
241             Specify a RHSBL-style domain-NS lookup, as above, with a sub-test.
242             C<NAME_OF_RULE> is the name of the rule to be used, C<rhsbl_zone> is the zone
243             to look up domain names in, and C<lookuptype> is the type of lookup (B<TXT> or
244             B<A>). C<subtest> is the sub-test to run against the returned data; see
245             <urirhssub>.
246              
247             Note that, as with C<urirhsbl>, you must also define a body-eval rule calling
248             C<check_uridnsbl()> to use this.
249              
250             =item tflags NAME_OF_RULE ips_only
251              
252             Only URIs containing IP addresses as the "host" component will be matched
253             against the named "urirhsbl"/"urirhssub" rule.
254              
255             =item tflags NAME_OF_RULE domains_only
256              
257             Only URIs containing a non-IP-address "host" component will be matched against
258             the named "urirhsbl"/"urirhssub" rule.
259              
260             =item tflags NAME_OF_RULE ns
261              
262             The 'ns' flag may be applied to rules corresponding to uridnsbl and uridnssub
263             directives. Host names from URLs will be mapped to their name server IP
264             addresses (a NS lookup followed by an A lookup), which in turn will be sent
265             to blocklists. This is a default when neither 'a' nor 'ns' flags are specified.
266              
267             =item tflags NAME_OF_RULE a
268              
269             The 'a' flag may be applied to rules corresponding to uridnsbl and uridnssub
270             directives. Host names from URLs will be mapped to their IP addresses, which
271             will be sent to blocklists. When both 'ns' and 'a' flags are specified,
272             both queries will be performed.
273              
274             =back
275              
276             =head1 ADMINISTRATOR SETTINGS
277              
278             =over 4
279              
280             =item uridnsbl_max_domains N (default: 20)
281              
282             The maximum number of domains to look up.
283              
284             =item parse_dkim_uris ( 0 / 1 )
285              
286             Include DKIM uris in lookups. This option is documented in
287             Mail::SpamAssassin::Conf.
288              
289             =back
290              
291             =head1 NOTES
292              
293             The C<uridnsbl_timeout> option has been obsoleted by the C<rbl_timeout>
294             option. See the C<Mail::SpamAssassin::Conf> POD for details on C<rbl_timeout>.
295              
296             =cut
297              
298              
299             use Mail::SpamAssassin::Plugin;
300 22     22   172 use Mail::SpamAssassin::Constants qw(:ip);
  22         54  
  22         854  
301 22     22   150 use Mail::SpamAssassin::Util;
  22         53  
  22         3261  
302 22     22   153 use Mail::SpamAssassin::Logger;
  22         37  
  22         815  
303 22     22   115 use strict;
  22         43  
  22         1112  
304 22     22   125 use warnings;
  22         42  
  22         521  
305 22     22   103 # use bytes;
  22         57  
  22         773  
306             use re 'taint';
307 22     22   150  
  22         47  
  22         1243  
308             our @ISA = qw(Mail::SpamAssassin::Plugin);
309              
310             use constant LOG_COMPLETION_TIMES => 0;
311 22     22   135  
  22         40  
  22         112412  
312             # constructor
313             my $class = shift;
314             my $samain = shift;
315 63     63 1 187  
316 63         119 # some boilerplate...
317             $class = ref($class) || $class;
318             my $self = $class->SUPER::new($samain);
319 63   33     437 bless ($self, $class);
320 63         592  
321 63         157 # this can be effectively global, at least in each process, safely
322              
323             $self->{finished} = { };
324              
325 63         288 $self->register_eval_rule ("check_uridnsbl");
326             $self->set_config($samain->{conf});
327 63         442  
328 63         289 return $self;
329             }
330 63         666  
331             # this is just a placeholder; in fact the results are dealt with later
332             return 0;
333             }
334              
335 88     88 0 1241 # ---------------------------------------------------------------------------
336              
337             # once the metadata is parsed, we can access the URI list. So start off
338             # the lookups here!
339             my ($self, $opts) = @_;
340             my $pms = $opts->{permsgstatus};
341             my $conf = $pms->{conf};
342              
343 81     81 1 202 return 0 if $conf->{skip_uribl_checks};
344 81         176 return 0 if !$pms->is_dns_available();
345 81         164  
346             $pms->{'uridnsbl_activerules'} = { };
347 81 50       219 $pms->{'uridnsbl_hits'} = { };
348 81 100       444 $pms->{'uridnsbl_seen_lookups'} = { };
349              
350 4         11 # only hit DNSBLs for active rules (defined and score != 0)
351 4         8 $pms->{'uridnsbl_active_rules_rhsbl'} = { };
352 4         8 $pms->{'uridnsbl_active_rules_rhsbl_ipsonly'} = { };
353             $pms->{'uridnsbl_active_rules_rhsbl_domsonly'} = { };
354             $pms->{'uridnsbl_active_rules_nsrhsbl'} = { };
355 4         7 $pms->{'uridnsbl_active_rules_fullnsrhsbl'} = { };
356 4         8 $pms->{'uridnsbl_active_rules_nsrevipbl'} = { };
357 4         6 $pms->{'uridnsbl_active_rules_arevipbl'} = { };
358 4         7  
359 4         9 foreach my $rulename (keys %{$conf->{uridnsbls}}) {
360 4         8 next unless ($conf->is_rule_active('body_evals',$rulename));
361 4         9  
362             my $rulecf = $conf->{uridnsbls}->{$rulename};
363 4         7 my $tflags = $conf->{tflags}->{$rulename};
  4         29  
364 88 50       160 $tflags = '' if !defined $tflags;
365             my %tfl = map { ($_,1) } split(' ',$tflags);
366 88         115  
367 88         162 my $is_rhsbl = $rulecf->{is_rhsbl};
368 88 100       158 if ( $is_rhsbl && $tfl{'ips_only'}) {
369 88         157 $pms->{uridnsbl_active_rules_rhsbl_ipsonly}->{$rulename} = 1;
  76         190  
370             } elsif ($is_rhsbl && $tfl{'domains_only'}) {
371 88         143 $pms->{uridnsbl_active_rules_rhsbl_domsonly}->{$rulename} = 1;
372 88 50 33     329 } elsif ($is_rhsbl) {
    100 33        
    50          
    0          
    0          
373 0         0 $pms->{uridnsbl_active_rules_rhsbl}->{$rulename} = 1;
374             } elsif ($rulecf->{is_fullnsrhsbl}) {
375 76         217 $pms->{uridnsbl_active_rules_fullnsrhsbl}->{$rulename} = 1;
376             } elsif ($rulecf->{is_nsrhsbl}) {
377 12         32 $pms->{uridnsbl_active_rules_nsrhsbl}->{$rulename} = 1;
378             } else { # just a plain dnsbl rule (IP based), not a RHS rule (name-based)
379 0         0 if ($tfl{'a'}) { # tflag 'a' explicitly
380             $pms->{uridnsbl_active_rules_arevipbl}->{$rulename} = 1;
381 0         0 }
382             if ($tfl{'ns'} || !$tfl{'a'}) { # tflag 'ns' explicitly, or default
383 0 0       0 $pms->{uridnsbl_active_rules_nsrevipbl}->{$rulename} = 1;
384 0         0 }
385             }
386 0 0 0     0 }
387 0         0  
388             # get all domains in message
389              
390             # don't keep dereferencing this
391             my $skip_domains = $conf->{uridnsbl_skip_domains};
392             $skip_domains = {} if !$skip_domains;
393              
394             # list of hashes to use in order
395 4         16 my @uri_ordered;
396 4 50       12  
397             # Generate the full list of html-parsed domains.
398             my $uris = $pms->get_uri_detail_list();
399 4         5  
400             # go from uri => info to uri_ordered
401             # 0: a
402 4         18 # 1: form
403             # 2: img
404             # 3: !a_empty
405             # 4: parsed
406             # 5: a_empty
407             while (my($uri, $info) = each %{$uris}) {
408             # we want to skip mailto: uris
409             next if ($uri =~ /^mailto:/i);
410              
411 4         8 # no hosts/domains were found via this uri, so skip
  11         28  
412             next unless ($info->{hosts});
413 7 50       17  
414             my $entry = 3;
415              
416 7 50       13 if ($info->{types}->{a}) {
417             $entry = 5;
418 7         9  
419             # determine a vs a_empty
420 7 50 33     44 foreach my $at (@{$info->{anchor_text}}) {
    50          
    50          
    50          
421 0         0 if (length $at) {
422             $entry = 0;
423             last;
424 0         0 }
  0         0  
425 0 0       0 }
426 0         0 }
427 0         0 elsif ($info->{types}->{form}) {
428             $entry = 1;
429             }
430             elsif ($info->{types}->{img}) {
431             $entry = 2;
432 0         0 }
433             elsif ($info->{types}->{parsed} && (keys %{$info->{types}} == 1)) {
434             $entry = 4;
435 0         0 }
436              
437 7         23 # take the usable domains and add them to the ordered list
438 7         10 while (my($host,$domain) = each( %{$info->{hosts}} )) {
439             if ($skip_domains->{$domain}) {
440             dbg("uridnsbl: domain $domain in skip list, host $host");
441             } else {
442 7         9 # use hostname as a key, and drag along the stripped domain name part
  14         46  
443 7 50       12 $uri_ordered[$entry]->{$host} = $domain;
444 0         0 }
445             }
446             }
447 7         16  
448             # at this point, @uri_ordered is an ordered array of hostname hashes
449              
450             my %hostlist; # keys are host names, values are their domain parts
451              
452             my $umd = $conf->{uridnsbl_max_domains};
453             while (keys %hostlist < $umd && @uri_ordered) {
454 4         8 my $array = shift @uri_ordered;
455             next unless $array;
456 4         6  
457 4   66     20 # run through and find the new domains in this grouping
458 15         19 my @hosts = grep(!$hostlist{$_}, keys %{$array});
459 15 100       37 next unless @hosts;
460              
461             # the new hosts are all useful, just add them in
462 3         4 if (keys(%hostlist) + @hosts <= $umd) {
  3         13  
463 3 50       8 foreach my $host (@hosts) {
464             $hostlist{$host} = $array->{$host};
465             }
466 3 50       10 }
467 3         6 else {
468 7         20 dbg("uridnsbl: more than $umd URIs, picking a subset");
469             # trim down to a limited number - pick randomly
470             while (@hosts && keys %hostlist < $umd) {
471             my $r = int rand(scalar @hosts);
472 0         0 my $picked_host = splice(@hosts, $r, 1);
473             $hostlist{$picked_host} = $array->{$picked_host};
474 0   0     0 }
475 0         0 }
476 0         0 }
477 0         0  
478             my @hnames = sort keys %hostlist;
479             $pms->set_tag('URIHOSTS',
480             @hnames == 1 ? $hnames[0] : \@hnames);
481             my @dnames = do { my %seen; grep { !$seen{$_}++ } sort values %hostlist };
482 4         14 $pms->set_tag('URIDOMAINS',
483 4 100       22 @dnames == 1 ? $dnames[0] : \@dnames);
484              
485 4         6 # and query
  4         7  
  4         8  
  7         20  
486 4 100       17 $self->query_hosts_or_domains($pms, \%hostlist);
487              
488             return 1;
489             }
490 4         15  
491             # Accepts argument in one of the following forms: m, n1-n2, or n/m,
492 4         18 # where n,n1,n2,m can be any of: decimal digits, 0x followed by up to 8
493             # hexadecimal digits, or an IPv4 address in quad-dot form. The argument
494             # is checked for syntax (undef is returned on syntax errors), hex numbers
495             # are converted to decimal, and quad-dot is converted to decimal, then
496             # reassembled into original string delimited by '-' or '/'. As a special
497             # backward compatibility measure, a single quad-dot (with no second number)
498             # is converted into n-n, to distinguish it from a traditional mask-only form.
499             #
500             # In practice, arguments like the following are anticipated:
501             # 127.0.1.2 (same as 127.0.1.2-127.0.1.2 or 127.0.1.2/255.255.255.255)
502             # 127.0.1.20-127.0.1.39 (= 0x7f000114-0x7f000127 or 2130706708-2130706727)
503             # 0.0.0.16/0.0.0.16 (same as 0x10/0x10 or 16/0x10 or 16/16)
504             # 16 (traditional style mask-only, same as 0x10)
505             #
506             my($subtest) = @_;
507             my $digested_subtest;
508              
509             local($1,$2,$3);
510             if ($subtest =~ m{^ ([^/-]+) (?: ([/-]) (.+) )? \z}xs) {
511 22     22 0 34 my($n1,$delim,$n2) = ($1,$2,$3);
512 22         23 my $any_quad_dot;
513             for ($n1,$n2) {
514 22         55 if (!defined $_) {
515 22 50       93 # ok, $n2 may not exist
516 22         76 } elsif (/^\d{1,10}\z/) {
517 22         28 # ok, already a decimal number
518 22         34 } elsif (/^0x[0-9a-zA-Z]{1,8}\z/) {
519 44 100       192 $_ = hex($_); # hex -> number
    100          
    100          
    50          
520             } elsif (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/) {
521             $_ = Mail::SpamAssassin::Util::my_inet_aton($_); # quad-dot -> number
522             $any_quad_dot = 1;
523             } else {
524 5         14 return;
525             }
526 26         58 }
527 26         58 $digested_subtest = defined $n2 ? $n1.$delim.$n2
528             : $any_quad_dot ? $n1.'-'.$n1 : "$n1";
529 0         0 }
530             return $digested_subtest;
531             }
532 22 100       105  
    100          
533             my($self, $conf) = @_;
534             my @cmds;
535 22         73  
536             push(@cmds, {
537             setting => 'skip_uribl_checks',
538             default => 0,
539 63     63 0 179 type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL,
540 63         135 });
541              
542 63         434 push(@cmds, {
543             setting => 'uridnsbl_max_domains',
544             is_admin => 1,
545             default => 20,
546             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
547             });
548 63         307  
549             push (@cmds, {
550             setting => 'uridnsbl',
551             is_priv => 1,
552             code => sub {
553             my ($self, $key, $value, $line) = @_;
554             local($1,$2,$3);
555             if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)$/) {
556             my $rulename = $1;
557             my $zone = $2;
558             my $type = $3;
559 0     0   0 $zone =~ s/\.\z//; # strip a redundant trailing dot
560 0         0 $self->{uridnsbls}->{$rulename} = {
561 0 0       0 zone => $zone, type => $type,
    0          
562 0         0 is_rhsbl => 0
563 0         0 };
564 0         0 }
565 0         0 elsif ($value =~ /^$/) {
566 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
567             }
568             else {
569             return $Mail::SpamAssassin::Conf::INVALID_VALUE;
570             }
571             }
572 0         0 });
573              
574             push (@cmds, {
575 0         0 setting => 'uridnssub',
576             is_priv => 1,
577             code => sub {
578 63         572 my ($self, $key, $value, $line) = @_;
579             local($1,$2,$3,$4);
580             if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(.*?)\s*$/) {
581             my $rulename = $1;
582             my $zone = $2;
583             my $type = $3;
584 0     0   0 my $subrule = $4;
585 0         0 $zone =~ s/\.\z//; # strip a redundant trailing dot
586 0 0       0 $subrule = parse_and_canonicalize_subtest($subrule);
    0          
587 0         0 defined $subrule or return $Mail::SpamAssassin::Conf::INVALID_VALUE;
588 0         0 $self->{uridnsbls}->{$rulename} = {
589 0         0 zone => $zone, type => $type,
590 0         0 is_rhsbl => 0, subtest => $subrule,
591 0         0 };
592 0         0 }
593 0 0       0 elsif ($value =~ /^$/) {
594 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
595             }
596             else {
597             return $Mail::SpamAssassin::Conf::INVALID_VALUE;
598             }
599             }
600 0         0 });
601              
602             push (@cmds, {
603 0         0 setting => 'urirhsbl',
604             is_priv => 1,
605             code => sub {
606 63         451 my ($self, $key, $value, $line) = @_;
607             local($1,$2,$3);
608             if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)$/) {
609             my $rulename = $1;
610             my $zone = $2;
611             my $type = $3;
612 0     0   0 $zone =~ s/\.\z//; # strip a redundant trailing dot
613 0         0 $self->{uridnsbls}->{$rulename} = {
614 0 0       0 zone => $zone, type => $type,
    0          
615 0         0 is_rhsbl => 1
616 0         0 };
617 0         0 }
618 0         0 elsif ($value =~ /^$/) {
619 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
620             }
621             else {
622             return $Mail::SpamAssassin::Conf::INVALID_VALUE;
623             }
624             }
625 0         0 });
626              
627             push (@cmds, {
628 0         0 setting => 'urirhssub',
629             is_priv => 1,
630             code => sub {
631 63         426 my ($self, $key, $value, $line) = @_;
632             local($1,$2,$3,$4);
633             if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(.*?)\s*$/) {
634             my $rulename = $1;
635             my $zone = $2;
636             my $type = $3;
637 22     22   57 my $subrule = $4;
638 22         61 $zone =~ s/\.\z//; # strip a redundant trailing dot
639 22 50       111 $subrule = parse_and_canonicalize_subtest($subrule);
    0          
640 22         46 defined $subrule or return $Mail::SpamAssassin::Conf::INVALID_VALUE;
641 22         40 $self->{uridnsbls}->{$rulename} = {
642 22         38 zone => $zone, type => $type,
643 22         38 is_rhsbl => 1, subtest => $subrule,
644 22         33 };
645 22         40 }
646 22 50       46 elsif ($value =~ /^$/) {
647 22         161 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
648             }
649             else {
650             return $Mail::SpamAssassin::Conf::INVALID_VALUE;
651             }
652             }
653 0         0 });
654              
655             push (@cmds, {
656 0         0 setting => 'urinsrhsbl',
657             is_priv => 1,
658             code => sub {
659 63         487 my ($self, $key, $value, $line) = @_;
660             local($1,$2,$3);
661             if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)$/) {
662             my $rulename = $1;
663             my $zone = $2;
664             my $type = $3;
665 0     0   0 $zone =~ s/\.\z//; # strip a redundant trailing dot
666 0         0 $self->{uridnsbls}->{$rulename} = {
667 0 0       0 zone => $zone, type => $type,
    0          
668 0         0 is_nsrhsbl => 1
669 0         0 };
670 0         0 }
671 0         0 elsif ($value =~ /^$/) {
672 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
673             }
674             else {
675             return $Mail::SpamAssassin::Conf::INVALID_VALUE;
676             }
677             }
678 0         0 });
679              
680             push (@cmds, {
681 0         0 setting => 'urinsrhssub',
682             is_priv => 1,
683             code => sub {
684 63         459 my ($self, $key, $value, $line) = @_;
685             local($1,$2,$3,$4);
686             if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(.*?)\s*$/) {
687             my $rulename = $1;
688             my $zone = $2;
689             my $type = $3;
690 0     0   0 my $subrule = $4;
691 0         0 $zone =~ s/\.\z//; # strip a redundant trailing dot
692 0 0       0 $subrule = parse_and_canonicalize_subtest($subrule);
    0          
693 0         0 defined $subrule or return $Mail::SpamAssassin::Conf::INVALID_VALUE;
694 0         0 $self->{uridnsbls}->{$rulename} = {
695 0         0 zone => $zone, type => $type,
696 0         0 is_nsrhsbl => 1, subtest => $subrule,
697 0         0 };
698 0         0 }
699 0 0       0 elsif ($value =~ /^$/) {
700 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
701             }
702             else {
703             return $Mail::SpamAssassin::Conf::INVALID_VALUE;
704             }
705             }
706 0         0 });
707              
708             push (@cmds, {
709 0         0 setting => 'urifullnsrhsbl',
710             is_priv => 1,
711             code => sub {
712 63         526 my ($self, $key, $value, $line) = @_;
713             local($1,$2,$3);
714             if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)$/) {
715             my $rulename = $1;
716             my $zone = $2;
717             my $type = $3;
718 0     0   0 $zone =~ s/\.\z//; # strip a redundant trailing dot
719 0         0 $self->{uridnsbls}->{$rulename} = {
720 0 0       0 zone => $zone, type => $type,
    0          
721 0         0 is_fullnsrhsbl => 1
722 0         0 };
723 0         0 }
724 0         0 elsif ($value =~ /^$/) {
725 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
726             }
727             else {
728             return $Mail::SpamAssassin::Conf::INVALID_VALUE;
729             }
730             }
731 0         0 });
732              
733             push (@cmds, {
734 0         0 setting => 'urifullnsrhssub',
735             is_priv => 1,
736             code => sub {
737 63         420 my ($self, $key, $value, $line) = @_;
738             local($1,$2,$3,$4);
739             if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(.*?)\s*$/) {
740             my $rulename = $1;
741             my $zone = $2;
742             my $type = $3;
743 0     0   0 my $subrule = $4;
744 0         0 $zone =~ s/\.\z//; # strip a redundant trailing dot
745 0 0       0 $subrule = parse_and_canonicalize_subtest($subrule);
    0          
746 0         0 defined $subrule or return $Mail::SpamAssassin::Conf::INVALID_VALUE;
747 0         0 $self->{uridnsbls}->{$rulename} = {
748 0         0 zone => $zone, type => $type,
749 0         0 is_fullnsrhsbl => 1, subtest => $subrule,
750 0         0 };
751 0         0 }
752 0 0       0 elsif ($value =~ /^$/) {
753 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
754             }
755             else {
756             return $Mail::SpamAssassin::Conf::INVALID_VALUE;
757             }
758             }
759 0         0 });
760              
761             push (@cmds, {
762 0         0 setting => 'uridnsbl_skip_domain',
763             default => {},
764             type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE,
765 63         467 code => sub {
766             my ($self, $key, $value, $line) = @_;
767             if ($value =~ /^$/) {
768             return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
769             }
770             foreach my $domain (split(/\s+/, $value)) {
771             $self->{uridnsbl_skip_domains}->{lc $domain} = 1;
772 0     0   0 }
773 0 0       0 }
774 0         0 });
775              
776 0         0 push (@cmds, {
777 0         0 setting => 'clear_uridnsbl_skip_domain',
778             type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE,
779             code => sub {
780 63         563 my ($self, $key, $value, $line) = @_;
781             if (!defined $value || $value eq '') {
782             # clear the entire list
783             $self->{uridnsbl_skip_domains} = {};
784             } else {
785             foreach my $domain (split(/\s+/, $value)) {
786 0     0   0 delete $self->{uridnsbl_skip_domains}->{lc $domain};
787 0 0 0     0 }
788             }
789 0         0 }
790             });
791 0         0  
792 0         0 # obsolete
793             push(@cmds, {
794             setting => 'uridnsbl_timeout',
795             code => sub {
796 63         433 # not a lint_warn(), since it's pretty harmless and we don't want
797             # to break stuff like sa-update
798             warn("config: 'uridnsbl_timeout' is obsolete, use 'rbl_timeout' instead");
799             return 0;
800             }
801             });
802              
803             $conf->{parser}->register_commands(\@cmds);
804 0     0   0 }
805 0         0  
806             # ---------------------------------------------------------------------------
807 63         361  
808             my ($self, $pms, $hosthash_ref) = @_;
809 63         353 my $conf = $pms->{conf};
810             my $seen_lookups = $pms->{'uridnsbl_seen_lookups'};
811              
812             my $rhsblrules = $pms->{uridnsbl_active_rules_rhsbl};
813             my $rhsbliprules = $pms->{uridnsbl_active_rules_rhsbl_ipsonly};
814             my $rhsbldomrules = $pms->{uridnsbl_active_rules_rhsbl_domsonly};
815 4     4 0 9 my $nsrhsblrules = $pms->{uridnsbl_active_rules_nsrhsbl};
816 4         7 my $fullnsrhsblrules = $pms->{uridnsbl_active_rules_fullnsrhsbl};
817 4         6 my $nsreviprules = $pms->{uridnsbl_active_rules_nsrevipbl};
818             my $areviprules = $pms->{uridnsbl_active_rules_arevipbl};
819 4         8  
820 4         5 while (my($host,$domain) = each(%$hosthash_ref)) {
821 4         6 $domain = lc $domain; # just in case
822 4         7 $host = lc $host;
823 4         7 dbg("uridnsbl: considering host=$host, domain=$domain");
824 4         5 my $obj = { dom => $domain };
825 4         5  
826             my ($is_ip, $single_dnsbl);
827 4         16 if ($host =~ /^\d+\.\d+\.\d+\.\d+$/) {
828 7         14 my $IPV4_ADDRESS = IPV4_ADDRESS;
829 7         9 my $IP_PRIVATE = IP_PRIVATE;
830 7         25 # only look up the IP if it is public and valid
831 7         15 if ($host =~ /^$IPV4_ADDRESS$/o && $host !~ /^$IP_PRIVATE$/o) {
832             my $obj = { dom => $host };
833 7         10 $self->lookup_dnsbl_for_ip($pms, $obj, $host);
834 7 100       20 # and check the IP in RHSBLs too
835 1         2 local($1,$2,$3,$4);
836 1         2 if ($host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
837             $domain = "$4.$3.$2.$1";
838 1 50 33     227 $single_dnsbl = 1;
839 1         5 $is_ip = 1;
840 1         5 }
841             }
842 1         3 }
843 1 50       4 else {
844 1         4 $single_dnsbl = 1;
845 1         2 }
846 1         3  
847             if ($single_dnsbl) {
848             # rule names which look up a domain in the basic RHSBL subset
849             my @rhsblrules = keys %{$rhsblrules};
850              
851 6         8 # and add the "domains_only" and "ips_only" subsets as appropriate
852             if ($is_ip) {
853             push @rhsblrules, keys %{$rhsbliprules};
854 7 50       19 } else {
855             push @rhsblrules, keys %{$rhsbldomrules};
856 7         9 }
  7         16  
857              
858             foreach my $rulename (@rhsblrules) {
859 7 100       16 my $rulecf = $conf->{uridnsbls}->{$rulename};
860 1         2 $self->lookup_single_dnsbl($pms, $obj, $rulename,
  1         2  
861             $domain, $rulecf->{zone}, $rulecf->{type});
862 6         7  
  6         26  
863             # note that these rules are now underway. important: unless the
864             # rule hits, in the current design, these will not be considered
865 7         18 # "finished" until harvest_dnsbl_queries() completes
866 135         199 $pms->register_async_rule_start($rulename);
867             }
868 135         324  
869             # perform NS+A or A queries to look up the domain in the non-RHSBL subset,
870             # but only if there are active reverse-IP-URIBL rules
871             if ($host !~ /^\d+\.\d+\.\d+\.\d+$/) {
872             if ( !$seen_lookups->{'NS:'.$domain} &&
873 135         246 (%$nsreviprules || %$nsrhsblrules || %$fullnsrhsblrules) ) {
874             $seen_lookups->{'NS:'.$domain} = 1;
875             $self->lookup_domain_ns($pms, $obj, $domain);
876             }
877             if (%$areviprules && !$seen_lookups->{'A:'.$host}) {
878 7 100       24 $seen_lookups->{'A:'.$host} = 1;
879 6 50 33     44 my $obj = { dom => $host, is_arevip => 1 };
      33        
880             $self->lookup_a_record($pms, $obj, $host);
881 0         0 $pms->register_async_rule_start($_) for keys %$areviprules;
882 0         0 }
883             }
884 6 50 33     38 }
885 0         0 }
886 0         0 }
887 0         0  
888 0         0 # ---------------------------------------------------------------------------
889              
890             my ($self, $pms, $obj, $dom) = @_;
891              
892             my $key = "NS:" . $dom;
893             my $ent = {
894             key => $key, zone => $dom, obj => $obj, type => "URI-NS",
895             };
896             # dig $dom ns
897             $ent = $pms->{async}->bgsend_and_start_lookup(
898 0     0 0 0 $dom, 'NS', undef, $ent,
899             sub { my ($ent2,$pkt) = @_;
900 0         0 $self->complete_ns_lookup($pms, $ent2, $pkt, $dom) },
901 0         0 master_deadline => $pms->{master_deadline} );
902              
903             return $ent;
904             }
905              
906             my ($self, $pms, $ent, $pkt, $dom) = @_;
907 0     0   0  
908 0         0 if (!$pkt) {
909 0         0 # $pkt will be undef if the DNS query was aborted (e.g. timed out)
910             dbg("uridnsbl: complete_ns_lookup aborted %s", $ent->{key});
911 0         0 return;
912             }
913              
914             dbg("uridnsbl: complete_ns_lookup %s", $ent->{key});
915 0     0 0 0 my $conf = $pms->{conf};
916             my @answer = $pkt->answer;
917 0 0       0  
918             my $IPV4_ADDRESS = IPV4_ADDRESS;
919 0         0 my $IP_PRIVATE = IP_PRIVATE;
920 0         0 my $nsrhsblrules = $pms->{uridnsbl_active_rules_nsrhsbl};
921             my $fullnsrhsblrules = $pms->{uridnsbl_active_rules_fullnsrhsbl};
922             my $seen_lookups = $pms->{'uridnsbl_seen_lookups'};
923 0         0  
924 0         0 my $j = 0;
925 0         0 foreach my $rr (@answer) {
926             $j++;
927 0         0 my $str = $rr->string;
928 0         0 next unless (defined($str) && defined($dom));
929 0         0 dbg("uridnsbl: got($j) NS for $dom: $str");
930 0         0  
931 0         0 if ($rr->type eq 'NS') {
932             my $nsmatch = lc $rr->nsdname; # available since at least Net::DNS 0.14
933 0         0 my $nsrhblstr = $nsmatch;
934 0         0 my $fullnsrhblstr = $nsmatch;
935 0         0  
936 0         0 if ($nsmatch =~ /^\d+\.\d+\.\d+\.\d+$/) {
937 0 0 0     0 # only look up the IP if it is public and valid
938 0         0 if ($nsmatch =~ /^$IPV4_ADDRESS$/o && $nsmatch !~ /^$IP_PRIVATE$/o) {
939             $self->lookup_dnsbl_for_ip($pms, $ent->{obj}, $nsmatch);
940 0 0       0 }
941 0         0 $nsrhblstr = $nsmatch;
942 0         0 }
943 0         0 else {
944             if (!$seen_lookups->{'A:'.$nsmatch}) {
945 0 0       0 $seen_lookups->{'A:'.$nsmatch} = 1;
946             $self->lookup_a_record($pms, $ent->{obj}, $nsmatch);
947 0 0 0     0 }
948 0         0 $nsrhblstr = $self->{main}->{registryboundaries}->trim_domain($nsmatch);
949             }
950 0         0  
951             foreach my $rulename (keys %{$nsrhsblrules}) {
952             my $rulecf = $conf->{uridnsbls}->{$rulename};
953 0 0       0 $self->lookup_single_dnsbl($pms, $ent->{obj}, $rulename,
954 0         0 $nsrhblstr, $rulecf->{zone}, $rulecf->{type});
955 0         0  
956             $pms->register_async_rule_start($rulename);
957 0         0 }
958              
959             foreach my $rulename (keys %{$fullnsrhsblrules}) {
960 0         0 my $rulecf = $conf->{uridnsbls}->{$rulename};
  0         0  
961 0         0 $self->lookup_single_dnsbl($pms, $ent->{obj}, $rulename,
962             $fullnsrhblstr, $rulecf->{zone}, $rulecf->{type});
963 0         0  
964             $pms->register_async_rule_start($rulename);
965 0         0 }
966             }
967             }
968 0         0 }
  0         0  
969 0         0  
970             # ---------------------------------------------------------------------------
971 0         0  
972             my ($self, $pms, $obj, $hname) = @_;
973 0         0  
974             my $key = "A:" . $hname;
975             my $ent = {
976             key => $key, zone => $hname, obj => $obj, type => "URI-A",
977             };
978             # dig $hname a
979             $ent = $pms->{async}->bgsend_and_start_lookup(
980             $hname, 'A', undef, $ent,
981             sub { my ($ent2,$pkt) = @_;
982 0     0 0 0 $self->complete_a_lookup($pms, $ent2, $pkt, $hname) },
983             master_deadline => $pms->{master_deadline} );
984 0         0  
985 0         0 return $ent;
986             }
987              
988             my ($self, $pms, $ent, $pkt, $hname) = @_;
989              
990             if (!$pkt) {
991 0     0   0 # $pkt will be undef if the DNS query was aborted (e.g. timed out)
992 0         0 dbg("uridnsbl: complete_a_lookup aborted %s", $ent->{key});
993 0         0 return;
994             }
995 0         0 dbg("uridnsbl: complete_a_lookup %s", $ent->{key});
996             my $j = 0;
997             my @answer = $pkt->answer;
998             foreach my $rr (@answer) {
999 0     0 0 0 $j++;
1000             my $str = $rr->string;
1001 0 0       0 if (!defined $hname) {
1002             warn "complete_a_lookup-1: $j, (hname is undef), $str";
1003 0         0 } elsif (!defined $str) {
1004 0         0 warn "complete_a_lookup-2: $j, $hname, (str is undef)";
1005             next;
1006 0         0 }
1007 0         0 dbg("uridnsbl: complete_a_lookup got(%d) A for %s: %s", $j,$hname,$str);
1008 0         0  
1009 0         0 if ($rr->type eq 'A') {
1010 0         0 my $ip_address = $rr->rdatastr;
1011 0         0 $self->lookup_dnsbl_for_ip($pms, $ent->{obj}, $ip_address);
1012 0 0       0 }
    0          
1013 0         0 }
1014             }
1015 0         0  
1016 0         0 # ---------------------------------------------------------------------------
1017              
1018 0         0 my ($self, $pms, $obj, $ip) = @_;
1019              
1020 0 0       0 local($1,$2,$3,$4);
1021 0         0 $ip =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
1022 0         0 my $revip = "$4.$3.$2.$1";
1023              
1024             my $conf = $pms->{conf};
1025              
1026             my @rulenames;
1027             if ($obj->{is_arevip}) {
1028             @rulenames = keys %{$pms->{uridnsbl_active_rules_arevipbl}};
1029             } else {
1030 1     1 0 3 @rulenames = keys %{$pms->{uridnsbl_active_rules_nsrevipbl}};
1031             }
1032 1         5 foreach my $rulename (@rulenames) {
1033 1         4 my $rulecf = $conf->{uridnsbls}->{$rulename};
1034 1         5  
1035             my $tflags = $conf->{tflags}->{$rulename} || '';
1036 1         2 # ips_only/domains_only lookups should not act on this kind of BL
1037             next if $tflags =~ /\b(?:ips_only|domains_only)\b/;
1038 1         2  
1039 1 50       3 $self->lookup_single_dnsbl($pms, $obj, $rulename,
1040 0         0 $revip, $rulecf->{zone}, $rulecf->{type});
  0         0  
1041             }
1042 1         2 }
  1         4  
1043              
1044 1         5 my ($self, $pms, $obj, $rulename, $lookupstr, $dnsbl, $qtype) = @_;
1045 0         0  
1046             my $qkey = "$rulename:$lookupstr:$dnsbl:$qtype";
1047 0   0     0 return if exists $pms->{uridnsbl_seen_lookups}{$qkey};
1048             $pms->{uridnsbl_seen_lookups}{$qkey} = 1;
1049 0 0       0  
1050             my $key = "DNSBL:" . $lookupstr . ':' . $dnsbl;
1051             my $ent = {
1052 0         0 key => $key, zone => $dnsbl, obj => $obj, type => 'URI-DNSBL',
1053             rulename => $rulename,
1054             };
1055             $ent = $pms->{async}->bgsend_and_start_lookup(
1056             $lookupstr.".".$dnsbl, $qtype, undef, $ent,
1057 135     135 0 417 sub { my ($ent2,$pkt) = @_;
1058             $self->complete_dnsbl_lookup($pms, $ent2, $pkt) },
1059 135         381 master_deadline => $pms->{master_deadline} );
1060 135 50       300  
1061 135         275 return $ent;
1062             }
1063 135         302  
1064 135         420 my ($self, $pms, $ent, $pkt) = @_;
1065              
1066             if (!$pkt) {
1067             # $pkt will be undef if the DNS query was aborted (e.g. timed out)
1068             dbg("uridnsbl: complete_dnsbl_lookup aborted %s %s",
1069             $ent->{rulename}, $ent->{key});
1070 135     135   196 return;
1071 135         264 }
1072 135         740  
1073             dbg("uridnsbl: complete_dnsbl_lookup %s %s", $ent->{rulename}, $ent->{key});
1074 135         318 my $conf = $pms->{conf};
1075              
1076             my $zone = $ent->{zone};
1077             my $dom = $ent->{obj}->{dom};
1078 135     135 0 194 my $rulename = $ent->{rulename};
1079             my $rulecf = $conf->{uridnsbls}->{$rulename};
1080 135 50       576  
1081             my @subtests;
1082             my @answer = $pkt->answer;
1083 0         0 foreach my $rr (@answer)
1084 0         0 {
1085             my($rdatastr,$rdatanum);
1086             my $rr_type = $rr->type;
1087 135         287  
1088 135         193 if ($rr_type eq 'A') {
1089             # Net::DNS::RR::A::address() is available since Net::DNS 0.69
1090 135         226 $rdatastr = $rr->UNIVERSAL::can('address') ? $rr->address
1091 135         200 : $rr->rdatastr;
1092 135         151 if ($rdatastr =~ m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
1093 135         222 $rdatanum = Mail::SpamAssassin::Util::my_inet_aton($rdatastr);
1094             }
1095 135         143 } elsif ($rr_type eq 'TXT') {
1096 135         273 # txtdata returns a non- zone-file-format encoded result, unlike rdatastr;
1097 135         774 # avoid space-separated RDATA <character-string> fields if possible;
1098             # txtdata provides a list of strings in list context since Net::DNS 0.69
1099 126         146 $rdatastr = join('',$rr->txtdata);
1100 126         286 } else {
1101             next;
1102 126 50       1085 }
    0          
1103              
1104 126 50       467 my $subtest = $rulecf->{subtest};
1105              
1106 126 50       1544 dbg("uridnsbl: %s . %s -> %s, %s%s",
1107 126         285 $dom, $zone, $rdatastr, $rulename,
1108             !defined $subtest ? '' : ', subtest:'.$subtest);
1109              
1110             my $match;
1111             if (!defined $subtest) {
1112             # this zone is a simple rule, not a set of subrules
1113 0         0 # skip any A record that isn't on 127/8
1114             if ($rr_type eq 'A' && $rdatastr !~ /^127\./) {
1115 0         0 warn("uridnsbl: bogus rr for domain=$dom, rule=$rulename, id=" .
1116             $pkt->header->id." rr=".$rr->string);
1117             next;
1118 126         294 }
1119             $match = 1;
1120 126 50       484 } elsif ($subtest eq $rdatastr) {
1121             $match = 1;
1122             } elsif ($subtest =~ m{^ (\d+) (?: ([/-]) (\d+) )? \z}x) {
1123             my($n1,$delim,$n2) = ($1,$2,$3);
1124 126         142 $match =
1125 126 50       574 !defined $n2 ? ($rdatanum & $n1) && # mask only
    50          
    50          
1126             (($rdatanum & 0xff000000) == 0x7f000000) # 127/8
1127             : $delim eq '-' ? $rdatanum >= $n1 && $rdatanum <= $n2 # range
1128 0 0 0     0 : $delim eq '/' ? ($rdatanum & $n2) == (int($n1) & $n2) # value/mask
1129 0         0 : 0; # notice int($n1) to fix perl ~5.14 taint bug (Bug 7725)
1130              
1131 0         0 dbg("uridnsbl: %s . %s -> %s, %s, %08x %s %s",
1132             $dom, $zone, $rdatastr, $rulename, $rdatanum,
1133 0         0 !defined $n2 ? sprintf('& %08x', $n1)
1134             : $n1 == $n2 ? sprintf('== %08x', $n1)
1135 0         0 : sprintf('%08x%s%08x', $n1,$delim,$n2),
1136             $match ? 'match' : 'no');
1137 126         424 }
1138 126 50 100     563 $self->got_dnsbl_hit($pms, $ent, $rdatastr, $dom, $rulename) if $match;
    100 100        
    100          
1139             }
1140             }
1141              
1142             my ($self, $pms, $ent, $str, $dom, $rulename) = @_;
1143              
1144             $str =~ s/\s+/ /gs; # long whitespace => short
1145 126 100       658 dbg("uridnsbl: domain \"$dom\" listed ($rulename): $str");
    100          
    100          
1146              
1147             if (!defined $pms->{uridnsbl_hits}->{$rulename}) {
1148             $pms->{uridnsbl_hits}->{$rulename} = { };
1149             };
1150             $pms->{uridnsbl_hits}->{$rulename}->{$dom} = 1;
1151              
1152 126 100       471 if ( $pms->{uridnsbl_active_rules_nsrevipbl}->{$rulename}
1153             || $pms->{uridnsbl_active_rules_arevipbl}->{$rulename}
1154             || $pms->{uridnsbl_active_rules_nsrhsbl}->{$rulename}
1155             || $pms->{uridnsbl_active_rules_fullnsrhsbl}->{$rulename}
1156             || $pms->{uridnsbl_active_rules_rhsbl}->{$rulename}
1157 42     42 0 102 || $pms->{uridnsbl_active_rules_rhsbl_ipsonly}->{$rulename}
1158             || $pms->{uridnsbl_active_rules_rhsbl_domsonly}->{$rulename})
1159 42         84 {
1160 42         160 # TODO: this needs to handle multiple domain hits per rule
1161             $pms->clear_test_state();
1162 42 100       94 my $uris = join (' ', keys %{$pms->{uridnsbl_hits}->{$rulename}});
1163 34         93 $pms->test_log ("URIs: $uris");
1164             $pms->got_hit ($rulename, "");
1165 42         78  
1166             # note that this rule has completed (since it got at least 1 hit)
1167 42 50 33     390 $pms->register_async_rule_finish($rulename);
      33        
      33        
      66        
      66        
      33        
1168             }
1169             }
1170              
1171             # ---------------------------------------------------------------------------
1172              
1173             # capability checks for "if can()":
1174             #
1175              
1176 42         121 1;