File Coverage

blib/lib/Mail/SpamAssassin/DnsResolver.pm
Criterion Covered Total %
statement 350 493 70.9
branch 108 278 38.8
condition 16 55 29.0
subroutine 30 35 85.7
pod 15 22 68.1
total 519 883 58.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             Mail::SpamAssassin::DnsResolver - DNS resolution engine
21              
22             =head1 DESCRIPTION
23              
24             This is a DNS resolution engine for SpamAssassin, implemented in order to
25             reduce file descriptor usage by Net::DNS and avoid a response collision bug in
26             that module.
27              
28             =head1 METHODS
29              
30             =over 4
31              
32             =cut
33              
34             # TODO: caching in this layer instead of in callers.
35              
36             package Mail::SpamAssassin::DnsResolver;
37              
38 40     40   291 use strict;
  40         113  
  40         1183  
39 40     40   250 use warnings;
  40         107  
  40         1227  
40             # use bytes;
41 40     40   221 use re 'taint';
  40         98  
  40         1671  
42              
43             require 5.008001; # needs utf8::is_utf8()
44              
45 40     40   268 use Mail::SpamAssassin;
  40         111  
  40         1019  
46 40     40   325 use Mail::SpamAssassin::Logger;
  40         136  
  40         2565  
47 40     40   307 use Mail::SpamAssassin::Constants qw(:ip);
  40         117  
  40         5045  
48 40     40   360 use Mail::SpamAssassin::Util qw(untaint_var decode_dns_question_entry);
  40         137  
  40         2902  
49              
50 40     40   301 use Socket;
  40         94  
  40         33757  
51 40     40   341 use Errno qw(EADDRINUSE EACCES);
  40         105  
  40         3218  
52 40     40   317 use Time::HiRes qw(time);
  40         101  
  40         733  
53              
54             our @ISA = qw();
55              
56             our $io_socket_module_name;
57             BEGIN {
58 40 50   40   11736 if (eval { require IO::Socket::IP }) {
  40 0       520  
    0          
59 40         210813 $io_socket_module_name = 'IO::Socket::IP';
60 0         0 } elsif (eval { require IO::Socket::INET6 }) {
61 0         0 $io_socket_module_name = 'IO::Socket::INET6';
62 0         0 } elsif (eval { require IO::Socket::INET }) {
63 0         0 $io_socket_module_name = 'IO::Socket::INET';
64             }
65             }
66              
67             ###########################################################################
68              
69             sub new {
70 90     90 0 298 my $class = shift;
71 90   33     674 $class = ref($class) || $class;
72              
73 90         250 my ($main) = @_;
74             my $self = {
75             'main' => $main,
76             'conf' => $main->{conf},
77 90         666 'id_to_callback' => { },
78             };
79 90         331 bless ($self, $class);
80              
81 90         576 $self->load_resolver();
82 90         780 $self;
83             }
84              
85             ###########################################################################
86              
87             =item $res->load_resolver()
88              
89             Load the C<Net::DNS::Resolver> object. Returns 0 if Net::DNS cannot be used,
90             1 if it is available.
91              
92             =cut
93              
94             sub load_resolver {
95 91     91 1 262 my ($self) = @_;
96              
97 91 50       510 if ($self->{res}) { return 1; }
  0         0  
98 91         383 $self->{no_resolver} = 1;
99              
100             # force only ipv4 if no IO::Socket::INET6 or ipv6 doesn't work
101 91         277 my $force_ipv4 = $self->{main}->{force_ipv4};
102 91         232 my $force_ipv6 = $self->{main}->{force_ipv6};
103              
104 91 50 33     803 if (!$force_ipv4 && $io_socket_module_name eq 'IO::Socket::INET') {
105 0         0 dbg("dns: socket module for IPv6 support not available");
106 0 0       0 die "Use of IPv6 requested, but not available\n" if $force_ipv6;
107 0         0 $force_ipv4 = 1; $force_ipv6 = 0;
  0         0  
108             }
109 91 50       331 if (!$force_ipv4) { # test drive IPv6
110             eval {
111 91         177 my $sock6;
112 91 50       290 if ($io_socket_module_name) {
113 91         1693 $sock6 = $io_socket_module_name->new(LocalAddr=>'::', Proto=>'udp');
114             }
115 91 0       60563 if ($sock6) { $sock6->close() or warn "error closing socket: $!" }
  0 50       0  
116 91         526 $sock6;
117 91 50       188 } or do {
118 91         451 dbg("dns: socket module %s is available, but no host support for IPv6",
119             $io_socket_module_name);
120 91 50       293 die "Use of IPv6 requested, but not available\n" if $force_ipv6;
121 91         199 $force_ipv4 = 1; $force_ipv6 = 0;
  91         218  
122             }
123             }
124            
125             eval {
126 91         954 require Net::DNS;
127             # force_v4 is set in new() to avoid error in older versions of Net::DNS
128             # that don't have it; other options are set by function calls so a typo
129             # or API change will cause an error here
130 91         2025 my $res = $self->{res} = Net::DNS::Resolver->new(force_v4 => $force_ipv4);
131 91 50       28955 if ($res) {
132 91         258 $self->{no_resolver} = 0;
133 91         730 $self->{force_ipv4} = $force_ipv4;
134 91         342 $self->{force_ipv6} = $force_ipv6;
135 91         328 $self->{retry} = 1; # retries for non-backgrounded query
136 91         259 $self->{retrans} = 3; # initial timeout for "non-backgrounded"
137             # query run in background
138              
139 91         1071 $res->retry(1); # If it fails, it fails
140 91         3108 $res->retrans(0); # If it fails, it fails
141 91         1779 $res->dnsrch(0); # ignore domain search-list
142 91         1570 $res->defnames(0); # don't append stuff to end of query
143 91         1662 $res->tcp_timeout(3); # timeout of 3 seconds only
144 91         1530 $res->udp_timeout(3); # timeout of 3 seconds only
145 91         1560 $res->persistent_tcp(0); # bug 3997
146 91         1739 $res->persistent_udp(0); # bug 3997
147              
148             # RFC 6891 (ex RFC 2671): EDNS0, value is a requestor's UDP payload size
149 91         1369 my $edns = $self->{conf}->{dns_options}->{edns};
150 91 50 33     849 if ($edns && $edns > 512) {
151 91         644 $res->udppacketsize($edns);
152 91         1647 dbg("dns: EDNS, UDP payload size %d", $edns);
153             }
154              
155             # set $res->nameservers for the benefit of plugins which don't use
156             # our send/bgsend infrastructure but rely on Net::DNS::Resolver entirely
157 91         656 my @ns_addr_port = $self->available_nameservers();
158 91         311 local($1,$2);
159             # drop port numbers, Net::DNS::Resolver can't take them
160 91 50       1218 @ns_addr_port = map(/^\[(.*)\]:(\d+)\z/ ? $1 : $_, @ns_addr_port);
161 91         569 dbg("dns: nameservers set to %s", join(', ', @ns_addr_port));
162 91         405 $res->nameservers(@ns_addr_port);
163             }
164 91         8942 1;
165 91 50       202 } or do {
166 0 0       0 my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
  0         0  
167 0         0 dbg("dns: eval failed: $eval_stat");
168             };
169              
170             dbg("dns: using socket module: %s version %s%s",
171             $io_socket_module_name,
172             $io_socket_module_name->VERSION,
173             $self->{force_ipv4} ? ', forced IPv4' :
174 91 0       2347 $self->{force_ipv6} ? ', forced IPv6' : '');
    50          
175             dbg("dns: is Net::DNS::Resolver available? %s",
176 91 50       629 $self->{no_resolver} ? "no" : "yes" );
177 91 50 33     771 if (!$self->{no_resolver} && defined $Net::DNS::VERSION) {
178 91         422 dbg("dns: Net::DNS version: %s", $Net::DNS::VERSION);
179             }
180              
181 91         262 return (!$self->{no_resolver});
182             }
183              
184             =item $resolver = $res->get_resolver()
185              
186             Return the C<Net::DNS::Resolver> object.
187              
188             =cut
189              
190             sub get_resolver {
191 4     4 1 13 my ($self) = @_;
192 4         12 return $self->{res};
193             }
194              
195             =item $res->configured_nameservers()
196              
197             Get a list of nameservers as configured by dns_server directives
198             or as provided by Net::DNS, typically from /etc/resolv.conf
199              
200             =cut
201              
202             sub configured_nameservers {
203 90     90 1 254 my $self = shift;
204              
205 90         224 my $res = $self->{res};
206 90         178 my @ns_addr_port; # list of name servers: [addr]:port entries
207 90 100       500 if ($self->{conf}->{dns_servers}) { # specified in a config file
    50          
208 1         3 @ns_addr_port = @{$self->{conf}->{dns_servers}};
  1         4  
209 1         10 dbg("dns: servers set by config to: %s", join(', ',@ns_addr_port));
210             } elsif ($res) { # default as provided by Net::DNS, e.g. /etc/resolv.conf
211             my @ns = $res->UNIVERSAL::can('nameservers') ? $res->nameservers
212 89 50       1201 : @{$res->{nameservers}};
  0         0  
213 89 50       3931 my $port = $res->UNIVERSAL::can('port') ? $res->port : $res->{port};
214 89         985 @ns_addr_port = map(untaint_var("[$_]:" . $port), @ns);
215 89         498 dbg("dns: servers obtained from Net::DNS : %s", join(', ',@ns_addr_port));
216             }
217 90         592 return @ns_addr_port;
218             }
219              
220             =item $res->available_nameservers()
221              
222             Get or set a list of currently available nameservers,
223             which is typically a known-to-be-good subset of configured nameservers
224              
225             =cut
226              
227             sub available_nameservers {
228 114     114 1 301 my $self = shift;
229              
230 114 50       593 if (@_) {
    100          
231 0         0 $self->{available_dns_servers} = [ @_ ]; # copy
232             dbg("dns: servers set by a caller to: %s",
233 0         0 join(', ',@{$self->{available_dns_servers}}));
  0         0  
234             } elsif (!$self->{available_dns_servers}) {
235             # a list of configured name servers: [addr]:port entries
236 90         419 $self->{available_dns_servers} = [ $self->configured_nameservers() ];
237             }
238 114 50 33     563 if ($self->{force_ipv4} || $self->{force_ipv6}) {
239             # filter the list according to a chosen protocol family
240 114         281 my $ip4_re = IPV4_ADDRESS;
241 114         215 my(@filtered_addr_port);
242 114         214 for (@{$self->{available_dns_servers}}) {
  114         435  
243 203         561 local($1,$2);
244 203 50       1315 /^ \[ (.*) \] : (\d+) \z/xs or next;
245 203         816 my($addr,$port) = ($1,$2);
246 203 50       3070 if ($addr =~ /^${ip4_re}\z/o) {
    0          
247 203 50       1310 push(@filtered_addr_port, $_) unless $self->{force_ipv6};
248             } elsif ($addr =~ /:.*:/) {
249 0 0       0 push(@filtered_addr_port, $_) unless $self->{force_ipv4};
250             } else {
251 0         0 warn "Unrecognized DNS server specification: $_";
252             }
253             }
254 114 50       272 if (@filtered_addr_port < @{$self->{available_dns_servers}}) {
  114         438  
255 0         0 dbg("dns: filtered DNS servers according to protocol family: %s",
256             join(", ",@filtered_addr_port));
257             }
258 114         254 @{$self->{available_dns_servers}} = @filtered_addr_port;
  114         421  
259             }
260             die "available_nameservers: No DNS servers available!\n"
261 114 50       211 if !@{$self->{available_dns_servers}};
  114         514  
262 114         215 return @{$self->{available_dns_servers}};
  114         433  
263             }
264              
265             sub disable_available_port {
266 0     0 0 0 my($self, $lport) = @_;
267 0 0 0     0 if ($lport >= 0 && $lport <= 65535) {
268 0         0 my $conf = $self->{conf};
269 0 0       0 if (!defined $conf->{dns_available_portscount}) {
270 0         0 $self->pick_random_available_port(); # initialize
271             }
272 0 0       0 if (vec($conf->{dns_available_ports_bitset}, $lport, 1)) {
273 0         0 dbg("dns: disabling local port %d", $lport);
274 0         0 vec($conf->{dns_available_ports_bitset}, $lport, 1) = 0;
275 0         0 $conf->{dns_available_portscount_buckets}->[$lport >> 8] --;
276 0         0 $conf->{dns_available_portscount} --;
277             }
278             }
279             }
280              
281             sub pick_random_available_port {
282 2     2 0 5 my $self = shift;
283 2         4 my $port_number; # resulting port number, or undef if none available
284              
285 2         5 my $conf = $self->{conf};
286 2         5 my $available_portscount = $conf->{dns_available_portscount};
287              
288             # initialize when called for the first time or after a config change
289 2 100       9 if (!defined $available_portscount) {
290 1         4 my $ports_bitset = $conf->{dns_available_ports_bitset};
291 1 50       5 if (!defined $ports_bitset) { # ensure it is initialized
292 1         10 Mail::SpamAssassin::Conf::set_ports_range(\$ports_bitset, 0, 0, 0);
293 1         5 $conf->{dns_available_ports_bitset} = $ports_bitset;
294             }
295             # prepare auxiliary data structure to speed up further free-port lookups;
296             # 256 buckets, each accounting for 256 ports: 8+8 = 16 bit port numbers;
297             # each bucket holds a count of available ports in its range
298 1         30 my @bucket_counts = (0) x 256;
299 1         4 my $all_zeroes = "\000" x 32; # one bucket's worth (256) of zeroes
300 1         2 my $all_ones = "\377" x 32; # one bucket's worth (256) of ones
301 1         2 my $ind = 0;
302 1         2 $available_portscount = 0; # number of all available ports
303 1         13 foreach my $bucket (0..255) {
304 256         278 my $cnt = 0;
305 256         352 my $b = substr($ports_bitset, $bucket*32, 32); # one bucket: 256 bits
306 256 100       483 if ($b eq $all_zeroes) { $ind += 256 }
  4 50       7  
307 252         297 elsif ($b eq $all_ones) { $ind += 256; $cnt += 256 }
  252         274  
308             else { # count nontrivial cases the slow way
309 0   0     0 vec($ports_bitset, $ind++, 1) && $cnt++ for 0..255;
310             }
311 256         285 $available_portscount += $cnt;
312 256         405 $bucket_counts[$bucket] = $cnt;
313             }
314 1         5 $conf->{dns_available_portscount} = $available_portscount;
315 1 50       13 if ($available_portscount) {
316 1         5 $conf->{dns_available_portscount_buckets} = \@bucket_counts;
317             } else { # save some storage
318 0         0 $conf->{dns_available_portscount_buckets} = undef;
319 0         0 $conf->{dns_available_ports_bitset} = '';
320             }
321             }
322              
323             # find the n-th port number from the ordered set of available port numbers
324 2         9 dbg("dns: %d configured local ports for DNS queries", $available_portscount);
325 2 50       8 if ($available_portscount > 0) {
326 2         14 my $ports_bitset = $conf->{dns_available_ports_bitset};
327 2         10 my $n = int(rand($available_portscount));
328 2         4 my $bucket_counts_ref = $conf->{dns_available_portscount_buckets};
329 2         4 my $ind = 0;
330 2         8 foreach my $bucket (0..255) {
331             # find the bucket containing n-th turned-on bit
332 65         86 my $cnt = $bucket_counts_ref->[$bucket];
333 65 100       88 if ($cnt > $n) { last } else { $n -= $cnt; $ind += 256 }
  2         6  
  63         69  
  63         98  
334             }
335 2         19 while ($ind <= 65535) { # scans one bucket, runs at most 256 iterations
336             # find the n-th turned-on bit within the corresponding bucket
337 320 50       502 if (vec($ports_bitset, $ind, 1)) {
338 320 100       446 if ($n <= 0) { $port_number = $ind; last } else { $n-- }
  2         5  
  2         5  
  318         377  
339             }
340 318         497 $ind++;
341             }
342             }
343 2         6 return $port_number;
344             }
345              
346             =item $res->connect_sock()
347              
348             Re-connect to the first nameserver listed in C</etc/resolv.conf> or similar
349             platform-dependent source, as provided by C<Net::DNS>.
350              
351             =cut
352              
353             sub connect_sock {
354 2     2 1 5 my ($self) = @_;
355              
356 2 50       12 dbg("dns: connect_sock, resolver: %s", $self->{no_resolver} ? "no" : "yes");
357 2 50       7 return if $self->{no_resolver};
358              
359 2 50       12 $io_socket_module_name
360             or die "No Perl modules for network socket available";
361              
362 2 50       8 if ($self->{sock}) {
363             $self->{sock}->close()
364 0 0       0 or info("connect_sock: error closing socket %s: %s", $self->{sock}, $!);
365 0         0 $self->{sock} = undef;
366             }
367 2         6 my $sock;
368             my $errno;
369              
370             # list of name servers: [addr]:port entries
371 2         7 my @ns_addr_port = $self->available_nameservers();
372             # use the first name server in a list
373 2         6 my($ns_addr,$ns_port); local($1,$2);
  2         7  
374 2 50       33 ($ns_addr,$ns_port) = ($1,$2) if $ns_addr_port[0] =~ /^\[(.*)\]:(\d+)\z/;
375              
376             # Ensure families of src and dest addresses match (bug 4412 comment 29).
377             # Older IO::Socket::INET6 may choose a wrong LocalAddr if protocol family
378             # is unspecified, causing EINVAL failure when automatically assigned local
379             # IP address and a remote address do not belong to the same address family.
380             # Let's choose a suitable source address if possible.
381 2         8 my $ip4_re = IPV4_ADDRESS;
382 2         3 my $srcaddr;
383 2 50       10 if ($self->{force_ipv4}) {
    0          
    0          
    0          
384 2         14 $srcaddr = "0.0.0.0";
385             } elsif ($self->{force_ipv6}) {
386 0         0 $srcaddr = "::";
387             } elsif ($ns_addr =~ /^${ip4_re}\z/o) {
388 0         0 $srcaddr = "0.0.0.0";
389             } elsif ($ns_addr =~ /:.*:/) {
390 0         0 $srcaddr = "::";
391             } else { # unrecognized
392             # unspecified address, unspecified protocol family
393             }
394              
395             # find a free local random port from a set of declared-to-be-available ports
396 2         7 my $lport;
397 2         4 my $attempts = 0;
398 2         4 for (;;) {
399 2         4 $attempts++;
400 2         11 $lport = $self->pick_random_available_port();
401 2 50       7 if (!defined $lport) {
402 0         0 $lport = 0;
403 0         0 dbg("no configured local ports for DNS queries, letting OS choose");
404             }
405 2 50       8 if ($attempts+1 > 50) { # sanity check
406 0         0 warn "could not create a DNS resolver socket in $attempts attempts\n";
407 0         0 $errno = 0;
408 0         0 last;
409             }
410 2   50     13 dbg("dns: LocalAddr: [%s]:%d, name server: [%s]:%d, module %s",
411             $srcaddr||'x', $lport, $ns_addr, $ns_port, $io_socket_module_name);
412 2         23 my %args = (
413             PeerAddr => $ns_addr,
414             PeerPort => $ns_port,
415             LocalAddr => $srcaddr,
416             LocalPort => $lport,
417             Type => SOCK_DGRAM,
418             Proto => 'udp',
419             );
420 2         29 $sock = $io_socket_module_name->new(%args);
421              
422 2 50       1858 last if $sock; # ok, got it
423              
424             # IO::Socket::IP constructor provides full error messages in $@
425 0 0       0 $errno = $io_socket_module_name eq 'IO::Socket::IP' ? $@ : $!;
426              
427 0 0 0     0 if ($! == EADDRINUSE || $! == EACCES) {
428             # in use, let's try another source port
429 0         0 dbg("dns: UDP port $lport already in use, trying another port");
430 0 0       0 if ($self->{conf}->{dns_available_portscount} > 100) { # still abundant
431 0         0 $self->disable_available_port($lport);
432             }
433             } else {
434 0         0 warn "error creating a DNS resolver socket: $errno";
435 0         0 goto no_sock;
436             }
437             }
438 2 50       10 if (!$sock) {
439 0         0 warn "could not create a DNS resolver socket in $attempts attempts: $errno";
440 0         0 goto no_sock;
441             }
442              
443             eval {
444 2         5 my($bufsiz,$newbufsiz);
445 2 50       45 $bufsiz = $sock->sockopt(Socket::SO_RCVBUF)
446             or die "cannot get a resolver socket rx buffer size: $!";
447 2 50       95 if ($bufsiz >= 32*1024) {
448 2         10 dbg("dns: resolver socket rx buffer size is %d bytes, local port %d",
449             $bufsiz, $lport);
450             } else {
451 0 0       0 $sock->sockopt(Socket::SO_RCVBUF, 32*1024)
452             or die "cannot set a resolver socket rx buffer size: $!";
453 0 0       0 $newbufsiz = $sock->sockopt(Socket::SO_RCVBUF)
454             or die "cannot get a resolver socket rx buffer size: $!";
455 0         0 dbg("dns: resolver socket rx buffer size changed from %d to %d bytes, ".
456             "local port %d", $bufsiz, $newbufsiz, $lport);
457             }
458 2         15 1;
459 2 50       5 } or do {
460 0 0       0 my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
  0         0  
461 0         0 info("dns: socket buffer size error: $eval_stat");
462             };
463              
464 2         7 $self->{sock} = $sock;
465 2         10 $self->{sock_as_vec} = $self->fhs_to_vec($self->{sock});
466 2         11 return;
467              
468             no_sock:
469 0         0 undef $self->{sock};
470 0         0 undef $self->{sock_as_vec};
471             }
472              
473             sub connect_sock_if_reqd {
474 21     21 0 43 my ($self) = @_;
475 21 100       86 $self->connect_sock() if !$self->{sock};
476             }
477              
478             =item $res->get_sock()
479              
480             Return the C<IO::Socket::INET> object used to communicate with
481             the nameserver.
482              
483             =cut
484              
485             sub get_sock {
486 0     0 1 0 my ($self) = @_;
487 0         0 $self->connect_sock_if_reqd();
488 0         0 return $self->{sock};
489             }
490              
491             ###########################################################################
492              
493             =item $packet = new_dns_packet ($domain, $type, $class)
494              
495             A wrapper for C<Net::DNS::Packet::new()> which traps a die thrown by it.
496              
497             To use this, change calls to C<Net::DNS::Resolver::bgsend> from:
498              
499             $res->bgsend($domain, $type);
500              
501             to:
502              
503             $res->bgsend(Mail::SpamAssassin::DnsResolver::new_dns_packet($domain, $type, $class));
504              
505             =cut
506              
507             # implements draft-vixie-dnsext-dns0x20-00
508             #
509             sub dnsext_dns0x20 {
510 0     0 0 0 my ($string) = @_;
511 0         0 my $rnd;
512 0         0 my $have_rnd_bits = 0;
513 0         0 my $result = '';
514 0         0 for my $ic (unpack("C*",$string)) {
515 0 0       0 if (chr($ic) =~ /^[A-Za-z]\z/) {
516 0 0       0 if ($have_rnd_bits < 1) {
517             # only reveal few bits at a time, hiding most of the accumulator
518 0         0 $rnd = int(rand(0x7fffffff)) & 0xff; $have_rnd_bits = 8;
  0         0  
519             }
520 0 0       0 $ic ^= 0x20 if $rnd & 1; # flip the 0x20 bit in name if dice says so
521 0         0 $rnd = $rnd >> 1; $have_rnd_bits--;
  0         0  
522             }
523 0         0 $result .= chr($ic);
524             }
525 0         0 return $result;
526             }
527              
528             # this subroutine mimics the Net::DNS::Resolver::Base::make_query_packet()
529             #
530             sub new_dns_packet {
531 21     21 1 72 my ($self, $domain, $type, $class) = @_;
532              
533 21 50       52 return if $self->{no_resolver};
534              
535             # construct a PTR query if it looks like an IPv4 address
536 21 50 33     144 if (!defined($type) || $type eq 'PTR') {
537 0         0 local($1,$2,$3,$4);
538 0 0       0 if ($domain =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
539 0         0 $domain = "$4.$3.$2.$1.in-addr.arpa.";
540 0         0 $type = 'PTR';
541             }
542             }
543 21 50       62 $type = 'A' if !defined $type; # a Net::DNS::Packet default
544 21 50       59 $class = 'IN' if !defined $class; # a Net::DNS::Packet default
545              
546 21         29 my $packet;
547             eval {
548              
549 21 50       81 if (utf8::is_utf8($domain)) { # since Perl 5.8.1
550 0         0 dbg("dns: new_dns_packet: domain is utf8 flagged: %s", $domain);
551             }
552              
553 21         202 $domain =~ s/\.*\z/./s;
554 21 50       196 if (length($domain) > 255) {
    50          
555 0         0 die "domain name longer than 255 bytes\n";
556             } elsif ($domain !~ /^ (?: [^.]{1,63} \. )+ \z/sx) {
557 0 0       0 if ($domain !~ /^ (?: [^.]+ \. )+ \z/sx) {
558 0         0 die "a domain name contains a null label\n";
559             } else {
560 0         0 die "a label in a domain name is longer than 63 bytes\n";
561             }
562             }
563              
564 21 50       76 if ($self->{conf}->{dns_options}->{dns0x20}) {
565 0         0 $domain = dnsext_dns0x20($domain);
566             } else {
567 21         62 $domain =~ tr/A-Z/a-z/; # lowercase, limited to plain ASCII
568             }
569              
570             # Net::DNS expects RFC 1035 zone format encoding even in its API, silly!
571             # Since 0.68 it also assumes that domain names containing characters
572             # with codes above 0177 imply that IDN translation is to be performed.
573             # Protect also nonprintable characters just in case, ensuring transparency.
574 21 0       61 $domain =~ s{ ( [\000-\037\177-\377\\] ) }
  0         0  
575             { $1 eq '\\' ? "\\$1" : sprintf("\\%03d",ord($1)) }xgse;
576 21         154  
577             $packet = Net::DNS::Packet->new($domain, $type, $class);
578              
579             # a bit noisy, so commented by default...
580             #dbg("dns: new DNS packet time=%.3f domain=%s type=%s id=%s",
581 21         2917 # time, $domain, $type, $packet->id);
582 21 50       36 1;
583             } or do {
584             # get here if a domain name in a query is invalid, or if a timeout signal
585 0 0       0 # happened to be trapped by this eval, or if Net::DNS signalled an error
  0         0  
586             my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
587 0 0       0 # resignal if alarm went off
588 0         0 die "dns: (1) $eval_stat\n" if $eval_stat =~ /__alarm__ignore__\(.*\)/s;
589             info("dns: new_dns_packet (domain=%s type=%s class=%s) failed: %s",
590             $domain, $type, $class, $eval_stat);
591             };
592 21 50       57  
593             if ($packet) {
594 21         81 # RD flag needs to be set explicitly since Net::DNS 1.01, Bug 7223
595             $packet->header->rd(1);
596              
597 21         613 # my $udp_payload_size = $self->{res}->udppacketsize;
598 21 50 33     124 my $udp_payload_size = $self->{conf}->{dns_options}->{edns};
599             if ($udp_payload_size && $udp_payload_size > 512) {
600 21 50       150 # dbg("dns: adding EDNS ext, UDP payload size %d", $udp_payload_size);
601 21         66 if ($packet->UNIVERSAL::can('edns')) { # available since Net::DNS 0.69
602             $packet->edns->size($udp_payload_size);
603 0         0 } else { # legacy mechanism
604             my $optrr = Net::DNS::RR->new(Type => 'OPT', Name => '', TTL => 0,
605 0         0 Class => $udp_payload_size);
606             $packet->push('additional', $optrr);
607             }
608             }
609             }
610 21         9262  
611             return $packet;
612             }
613              
614             # Internal function used only in this file
615             ## compute a unique ID for a packet to match the query to the reply
616             ## It must use only data that is returned unchanged by the nameserver.
617             ## Argument is a Net::DNS::Packet that has a non-empty question section,
618             ## return is an (opaque) string that can be used as a hash key
619 42     42   87 sub _packet_id {
620 42         110 my ($self, $packet) = @_;
621 42         266 my $header = $packet->header;
622 42         353 my $id = $header->id;
623             my @questions = $packet->question;
624 42 50       294  
625             @questions <= 1
626             or warn "dns: packet has multiple questions: " . $packet->string . "\n";
627 42 50       116  
628             if ($questions[0]) {
629             # Bug 6232: Net::DNS::Packet::new is not consistent in keeping data in
630             # sections of a packet either as original bytes or presentation-encoded:
631             # creating a query packet as above in new_dns_packet() keeps label in
632             # non-encoded form, yet on parsing an answer packet, its query section
633             # is converted to presentation form by Net::DNS::Question::parse calling
634             # Net::DNS::Packet::dn_expand and Net::DNS::wire2presentation in turn.
635             # Let's undo the effect of the wire2presentation routine here to make
636             # sure the query section of an answer packet matches the query section
637             # in our packet as formed by new_dns_packet():
638 42         138 #
639 42 50       1277 my($class,$type,$qname) = decode_dns_question_entry($questions[0]);
640 42         280 $qname =~ tr/A-Z/a-z/ if !$self->{conf}->{dns_options}->{dns0x20};
641             return join('/', $id, $class, $type, $qname);
642              
643             } else {
644             # Odd, this should not happen, a DNS servers is supposed to retain
645             # a question section in its reply. There is a bug in Net::DNS 0.72
646             # and earlier where a signal (e.g. a timeout alarm) during decoding
647             # of a reply packet produces a seemingly valid packet object, but
648             # with missing sections - see [rt.cpan.org #83451] .
649             #
650             # Better support it; just return the (safe) ID part, along with
651             # a text token indicating that the packet had no question part.
652 0         0 #
653             return $id . "/NO_QUESTION_IN_PACKET";
654             }
655             }
656              
657             ###########################################################################
658              
659             =item $id = $res->bgsend($domain, $type, $class, $cb)
660              
661             Quite similar to C<Net::DNS::Resolver::bgsend>, except that when a reply
662             packet eventually arrives, and C<poll_responses> is called, the callback
663             sub reference C<$cb> will be called.
664              
665             Note that C<$type> and C<$class> may be C<undef>, in which case they
666             will default to C<A> and C<IN>, respectively.
667              
668             The callback sub will be called with three arguments -- the packet that was
669             delivered, and an id string that fingerprints the query packet and the expected
670             reply. The third argument is a timestamp (Unix time, floating point), captured
671             at the time the packet was collected. It is expected that a closure callback
672             be used, like so:
673              
674             my $id = $self->{resolver}->bgsend($domain, $type, undef, sub {
675             my ($reply, $reply_id, $timestamp) = @_;
676             $self->got_a_reply ($reply, $reply_id);
677             });
678              
679             The callback can ignore the reply as an invalid packet sent to the listening
680             port if the reply id does not match the return value from bgsend.
681              
682             =cut
683              
684 21     21 1 99 sub bgsend {
685 21 50       64 my ($self, $domain, $type, $class, $cb) = @_;
686             return if $self->{no_resolver};
687 21         100  
688             $self->{send_timed_out} = 0;
689 21         62  
690 21 50       64 my $pkt = $self->new_dns_packet($domain, $type, $class);
691             return if !$pkt; # just bail out, new_dns_packet already reported a failure
692 21         72  
693 21         96 my @ns_addr_port = $self->available_nameservers();
694 21         38 dbg("dns: bgsend, DNS servers: %s", join(', ',@ns_addr_port));
695             my $n_servers = scalar @ns_addr_port;
696 21         30  
697 21         58 my $ok;
698 21         65 for (my $attempts=1; $attempts <= $n_servers; $attempts++) {
699             dbg("dns: attempt %d/%d, trying connect/sendto to %s",
700 21         81 $attempts, $n_servers, $ns_addr_port[0]);
701 21 50 33     127 $self->connect_sock_if_reqd();
702 21         7378 if ($self->{sock} && defined($self->{sock}->send($pkt->data, 0))) {
  21         56  
703             $ok = 1; last;
704 0 0       0 } else { # any other DNS servers in a list to try?
705             my $msg = !$self->{sock} ? "unable to connect to $ns_addr_port[0]"
706 0         0 : "sendto() to $ns_addr_port[0] failed: $!";
707 0 0       0 $self->finish_socket();
708 0         0 if ($attempts >= $n_servers) {
709 0         0 warn "dns: $msg, no more alternatives\n";
710             last;
711             }
712 0         0 # try with a next DNS server, rotate the list left
713 0         0 warn "dns: $msg, failing over to $ns_addr_port[1]\n";
714 0         0 push(@ns_addr_port, shift(@ns_addr_port));
715             $self->available_nameservers(@ns_addr_port);
716             }
717 21 50       66 }
718 21         81 return if !$ok;
719 21         189 my $id = $self->_packet_id($pkt);
720 21         67 dbg("dns: providing a callback for id: $id");
721 21         206 $self->{id_to_callback}->{$id} = $cb;
722             return $id;
723             }
724              
725             ###########################################################################
726              
727             =item $id = $res->bgread()
728              
729             Similar to C<Net::DNS::Resolver::bgread>. Reads a DNS packet from
730             a supplied socket, decodes it, and returns a Net::DNS::Packet object
731             if successful. Dies on error.
732              
733             =cut
734              
735 21     21 1 43 sub bgread {
736 21         42 my ($self) = @_;
737 21         128 my $sock = $self->{sock};
738 21 50       353 my $packetsize = $self->{res}->udppacketsize;
739 21         38 $packetsize = 512 if $packetsize < 512; # just in case
740 21         94 my $data = '';
741 21 50       1033 my $peeraddr = $sock->recv($data, $packetsize+256); # with some size margin for troubleshooting
742 21         92 defined $peeraddr or die "bgread: recv() failed: $!";
743 21 50       1131 my $peerhost = $sock->peerhost;
744 21         124 $data ne '' or die "bgread: received empty packet from $peerhost";
745 21         100 dbg("dns: bgread: received %d bytes from %s", length($data), $peerhost);
746 21 50       5436 my($answerpkt, $decoded_length) = Net::DNS::Packet->new(\$data);
747 21         94 $answerpkt or die "bgread: decoding DNS packet failed: $@";
748 21 50 33     427 $answerpkt->answerfrom($peerhost);
      33        
749 0         0 if (defined $decoded_length && $decoded_length ne "" && $decoded_length != length($data)) {
750             warn sprintf("bgread: received a %d bytes packet from %s, decoded %d bytes\n",
751             length($data), $peerhost, $decoded_length);
752 21         110 }
753             return $answerpkt;
754             }
755              
756             ###########################################################################
757              
758             =item $nfound = $res->poll_responses()
759              
760             See if there are any C<bgsend> reply packets ready, and return
761             the number of such packets delivered to their callbacks.
762              
763             =cut
764              
765 11     11 1 29 sub poll_responses {
766 11 50       31 my ($self, $timeout) = @_;
767 11 50       31 return if $self->{no_resolver};
768 11         21 return if !$self->{sock};
769             my $cnt = 0;
770 11         21  
771 11         17 my $rin = $self->{sock_as_vec};
772             my $rout;
773 11         15  
774 32         67 for (;;) {
775             my ($nfound, $timeleft, $eval_stat);
776 32         49 eval { # use eval to catch alarm signal
777 32 100 66     144 my $timer; # collects timestamp when variable goes out of scope
778 8         33 if (!defined($timeout) || $timeout > 0)
779 32         81 { $timer = $self->{main}->time_method("poll_dns_idle") }
780 32         24058 $! = 0;
781 32         181 ($nfound, $timeleft) = select($rout=$rin, undef, undef, $timeout);
782 32 50       54 1;
783 0 0       0 } or do {
  0         0  
784             $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
785 32 50 33     240 };
    50          
    100          
786             if (defined $eval_stat) {
787 0 0       0 # most likely due to an alarm signal, resignal if so
788 0         0 die "dns: (2) $eval_stat\n" if $eval_stat =~ /__alarm__ignore__\(.*\)/s;
789 0         0 warn "dns: select aborted: $eval_stat\n";
790             return;
791 0 0       0 } elsif (!defined $nfound || $nfound < 0) {
  0         0  
792 0         0 if ($!) { warn "dns: select failed: $!\n" }
793 0         0 else { info("dns: select interrupted") } # shouldn't happen
794             return;
795 11 50       42 } elsif (!$nfound) {
  0 50       0  
796 0         0 if (!defined $timeout) { warn("dns: select returned empty-handed\n") }
797 11         51 elsif ($timeout > 0) { dbg("dns: select timed out %.3f s", $timeout) }
798             return;
799             }
800 21         74  
801 21         31 my $now = time;
802 21 50       56 $timeout = 0; # next time around collect whatever is available, then exit
803             last if $nfound == 0;
804 21         28  
805             my $packet;
806             # Bug 7265, use our own bgread() below
807             # $packet = $self->{res}->bgread($self->{sock});
808 21         73 eval {
809 21 50       41 $packet = $self->bgread(); # Bug 7265, use our own bgread()
810 0         0 } or do {
811 0 0       0 undef $packet;
  0         0  
812             my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
813 0 0       0 # resignal if alarm went off
814 0         0 die $eval_stat if $eval_stat =~ /__alarm__ignore__\(.*\)/s;
815             info("dns: bad dns reply: %s", $eval_stat);
816             };
817 21 50       63  
818             if (!$packet) {
819             # error already reported above
820             # my $dns_err = $self->{res}->errorstring;
821             # die "dns (3) $dns_err\n" if $dns_err =~ /__alarm__ignore__\(.*\)/s;
822             # info("dns: bad dns reply: $dns_err");
823 21         61 } else {
824 21 50       126 my $header = $packet->header;
825 0         0 if (!$header) {
826             info("dns: dns reply is missing a header section");
827 21         77 } else {
828 21         2490 my $rcode = $header->rcode;
829 21         210 my $packet_id = $header->id;
830             my $id = $self->_packet_id($packet);
831 21 100       75  
832             if ($rcode eq 'NOERROR') { # success
833 10         34 # NOERROR, may or may not have answer records
834             dbg("dns: dns reply %s is OK, %d answer records",
835 10 50       31 $packet_id, $header->ancount);
836 0   0     0 if ($header->tc) { # truncation flag turned on
837 0 0       0 my $edns = $self->{conf}->{dns_options}->{edns} || 512;
838             info("dns: reply to %s truncated (%s), %d answer records", $id,
839             $edns == 512 ? "EDNS off" : "EDNS $edns bytes",
840             $header->ancount);
841             }
842             } else {
843             # some failure, e.g. NXDOMAIN, SERVFAIL, FORMERR, REFUSED, ...
844 11         39 # btw, one reason for SERVFAIL is an RR signature failure in DNSSEC
845             dbg("dns: dns reply to %s: %s", $id, $rcode);
846             }
847              
848             # A hash lookup: the id must match exactly (case-sensitively).
849             # The domain name part of the id was lowercased if dns0x20 is off,
850             # and case-randomized when dns0x20 option is on.
851 21         232 #
852             my $cb = delete $self->{id_to_callback}->{$id};
853 21 50       51  
854 21         82 if ($cb) {
855 21         210 $cb->($packet, $id, $now);
856             $cnt++;
857 0 0 0     0 } else { # no match, report the problem
858             if ($rcode eq 'REFUSED' || $id =~ m{^\d+/NO_QUESTION_IN_PACKET\z}) {
859             # the failure was already reported above
860 0         0 } else {
861             info("dns: no callback for id $id, ignored, packet on next debug line");
862 0 0       0 # prevent filling normal logs with huge packet dumps
863             dbg("dns: %s", $packet ? $packet->string : "undef");
864             }
865 0         0 # report a likely matching query for diagnostic purposes
866 0 0       0 local $1;
867 0         0 if ($id =~ m{^(\d+)/}) {
868             my $dnsid = $1; # the raw DNS packet id
869 0         0 my @matches =
  0         0  
870 0 0       0 grep(m{^\Q$dnsid\E/}, keys %{$self->{id_to_callback}});
871 0         0 if (!@matches) {
872             info("dns: no likely matching queries for id %s", $dnsid);
873 0         0 } else {
874             info("dns: a likely matching query: %s", join(', ', @matches));
875             }
876             }
877             }
878             }
879             }
880             }
881 0         0  
882             return $cnt;
883             }
884              
885             ###########################################################################
886              
887             =item $res->bgabort()
888              
889             Call this to release pending requests from memory, when aborting backgrounded
890             requests, or when the scan is complete.
891             C<Mail::SpamAssassin::PerMsgStatus::check> calls this before returning.
892              
893             =cut
894              
895 192     192 1 484 sub bgabort {
896 192         681 my ($self) = @_;
897             $self->{id_to_callback} = {};
898             }
899              
900             ###########################################################################
901              
902             =item $packet = $res->send($name, $type, $class)
903              
904             Emulates C<Net::DNS::Resolver::send()>.
905              
906             This subroutine is a simple synchronous leftover from SpamAssassin version
907             3.3 and does not participate in packet query caching and callback grouping
908             as implemented by AsyncLoop::bgsend_and_start_lookup(). As such it should
909             be avoided for mainstream usage. Currently used through Mail::SPF::Server
910             by the SPF plugin.
911              
912             =cut
913              
914 8     8 1 1833 sub send {
915 8 50       30 my ($self, $name, $type, $class) = @_;
916             return if $self->{no_resolver};
917              
918             # Avoid passing utf8 character strings to DNS, as it has no notion of
919             # character set encodings - encode characters somehow to plain bytes
920             # using some arbitrary encoding (they are normally just 7-bit ascii
921             # characters anyway, just need to get rid of the utf8 flag). Bug 6959
922             # Most if not all af these come from a SPF plugin.
923 8         40 #
924             utf8::encode($name);
925 8         25  
926 8         16 my $retrans = $self->{retrans};
927 8         14 my $retries = $self->{retry};
928 8         15 my $timeout = $retrans;
929 8         14 my $answerpkt;
930 8   66     77 my $answerpkt_avail = 0;
931             for (my $i = 0;
932             (($i < $retries) && !defined($answerpkt));
933             ++$i, $retrans *= 2, $timeout = $retrans) {
934 8 50       25  
935             $timeout = 1 if ($timeout < 1);
936             # note nifty use of a closure here. I love closures ;)
937 8     8   31 my $id = $self->bgsend($name, $type, $class, sub {
938 8         17 my ($reply, $reply_id, $timestamp) = @_;
  8         20  
939 8         86 $answerpkt = $reply; $answerpkt_avail = 1;
940             });
941 8 50       33  
942             last if !defined $id; # perhaps a restricted zone or a serious failure
943 8         25  
944 8         22 my $now = time;
945             my $deadline = $now + $timeout;
946 8         22  
947 8 50       24 while (!$answerpkt_avail) {
  0         0  
  0         0  
948 8         34 if ($now >= $deadline) { $self->{send_timed_out} = 1; last }
949 8         77 $self->poll_responses(1);
950             $now = time;
951             }
952 8         34 }
953             return $answerpkt;
954             }
955              
956             ###########################################################################
957              
958             =item $res->errorstring()
959              
960             Little more than a stub for callers expecting this from C<Net::DNS::Resolver>.
961              
962             If called immediately after a call to $res->send this will return
963             C<query timed out> if the $res->send DNS query timed out. Otherwise
964             C<unknown error or no error> will be returned.
965              
966             No other errors are reported.
967              
968             =cut
969              
970 0     0 1 0 sub errorstring {
971 0 0       0 my ($self) = @_;
972 0         0 return 'query timed out' if $self->{send_timed_out};
973             return 'unknown error or no error';
974             }
975              
976             ###########################################################################
977              
978             =item $res->finish_socket()
979              
980             Reset socket when done with it.
981              
982             =cut
983              
984 53     53 1 145 sub finish_socket {
985 53 100       238 my ($self) = @_;
986             if ($self->{sock}) {
987 2 50       21 $self->{sock}->close()
988 2         136 or warn "finish_socket: error closing socket $self->{sock}: $!";
989             undef $self->{sock};
990             }
991             }
992              
993             ###########################################################################
994              
995             =item $res->finish()
996              
997             Clean up for destruction.
998              
999             =cut
1000              
1001 52     52 1 163 sub finish {
1002 52         273 my ($self) = @_;
1003 52         127 $self->finish_socket();
  52         1046  
1004             %{$self} = ();
1005             }
1006              
1007             ###########################################################################
1008             # non-public methods.
1009              
1010             # should move to Util.pm (TODO)
1011 2     2 0 9 sub fhs_to_vec {
1012 2         5 my ($self, @fhlist) = @_;
1013 2         16 my $rin = '';
1014 2         7 foreach my $sock (@fhlist) {
1015 2 50       7 my $fno = fileno($sock);
1016 0         0 if (!defined $fno) {
1017             warn "dns: oops! fileno now undef for $sock";
1018 2         14 } else {
1019             vec ($rin, $fno, 1) = 1;
1020             }
1021 2         8 }
1022             return $rin;
1023             }
1024              
1025             # call Mail::SA::init() instead
1026 0     0 0   sub reinit_post_fork {
1027             my ($self) = @_;
1028 0           # release parent's socket, don't want all spamds sharing the same socket
1029             $self->finish_socket();
1030             }
1031              
1032             1;
1033              
1034             =back
1035              
1036             =cut