File Coverage

blib/lib/Mail/SpamAssassin/DnsResolver.pm
Criterion Covered Total %
statement 326 492 66.2
branch 103 278 37.0
condition 14 55 25.4
subroutine 27 35 77.1
pod 15 22 68.1
total 485 882 54.9


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   308 use strict;
  40         103  
  40         1269  
39 40     40   236 use warnings;
  40         139  
  40         1404  
40             # use bytes;
41 40     40   268 use re 'taint';
  40         139  
  40         1595  
42              
43             require 5.008001; # needs utf8::is_utf8()
44              
45 40     40   260 use Mail::SpamAssassin;
  40         114  
  40         990  
46 40     40   236 use Mail::SpamAssassin::Logger;
  40         136  
  40         2412  
47 40     40   274 use Mail::SpamAssassin::Constants qw(:ip);
  40         111  
  40         5202  
48 40     40   345 use Mail::SpamAssassin::Util qw(untaint_var decode_dns_question_entry);
  40         110  
  40         2879  
49              
50 40     40   294 use Socket;
  40         106  
  40         40722  
51 40     40   375 use Errno qw(EADDRINUSE EACCES);
  40         96  
  40         3475  
52 40     40   334 use Time::HiRes qw(time);
  40         103  
  40         849  
53              
54             our @ISA = qw();
55              
56             our $io_socket_module_name;
57             BEGIN {
58 40 50   40   11515 if (eval { require IO::Socket::IP }) {
  40 0       514  
    0          
59 40         207898 $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 79     79 0 367 my $class = shift;
71 79   33     720 $class = ref($class) || $class;
72              
73 79         261 my ($main) = @_;
74             my $self = {
75             'main' => $main,
76             'conf' => $main->{conf},
77 79         689 'id_to_callback' => { },
78             };
79 79         393 bless ($self, $class);
80              
81 79         654 $self->load_resolver();
82 79         639 $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 80     80 1 269 my ($self) = @_;
96              
97 80 50       426 if ($self->{res}) { return 1; }
  0         0  
98 80         450 $self->{no_resolver} = 1;
99              
100             # force only ipv4 if no IO::Socket::INET6 or ipv6 doesn't work
101 80         291 my $force_ipv4 = $self->{main}->{force_ipv4};
102 80         241 my $force_ipv6 = $self->{main}->{force_ipv6};
103              
104 80 50 33     809 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 80 50       316 if (!$force_ipv4) { # test drive IPv6
110             eval {
111 80         227 my $sock6;
112 80 50       309 if ($io_socket_module_name) {
113 80         1946 $sock6 = $io_socket_module_name->new(LocalAddr=>'::', Proto=>'udp');
114             }
115 80 0       60921 if ($sock6) { $sock6->close() or warn "error closing socket: $!" }
  0 50       0  
116 80         461 $sock6;
117 80 50       279 } or do {
118 80         477 dbg("dns: socket module %s is available, but no host support for IPv6",
119             $io_socket_module_name);
120 80 50       280 die "Use of IPv6 requested, but not available\n" if $force_ipv6;
121 80         219 $force_ipv4 = 1; $force_ipv6 = 0;
  80         183  
122             }
123             }
124            
125             eval {
126 80         754 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 80         2082 my $res = $self->{res} = Net::DNS::Resolver->new(force_v4 => $force_ipv4);
131 80 50       22043 if ($res) {
132 80         255 $self->{no_resolver} = 0;
133 80         236 $self->{force_ipv4} = $force_ipv4;
134 80         344 $self->{force_ipv6} = $force_ipv6;
135 80         298 $self->{retry} = 1; # retries for non-backgrounded query
136 80         329 $self->{retrans} = 3; # initial timeout for "non-backgrounded"
137             # query run in background
138              
139 80         1139 $res->retry(1); # If it fails, it fails
140 80         2878 $res->retrans(0); # If it fails, it fails
141 80         1722 $res->dnsrch(0); # ignore domain search-list
142 80         1341 $res->defnames(0); # don't append stuff to end of query
143 80         1307 $res->tcp_timeout(3); # timeout of 3 seconds only
144 80         1317 $res->udp_timeout(3); # timeout of 3 seconds only
145 80         1352 $res->persistent_tcp(0); # bug 3997
146 80         1381 $res->persistent_udp(0); # bug 3997
147              
148             # RFC 6891 (ex RFC 2671): EDNS0, value is a requestor's UDP payload size
149 80         1172 my $edns = $self->{conf}->{dns_options}->{edns};
150 80 50 33     843 if ($edns && $edns > 512) {
151 80         660 $res->udppacketsize($edns);
152 80         1802 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 80         648 my @ns_addr_port = $self->available_nameservers();
158 80         269 local($1,$2);
159             # drop port numbers, Net::DNS::Resolver can't take them
160 80 50       1148 @ns_addr_port = map(/^\[(.*)\]:(\d+)\z/ ? $1 : $_, @ns_addr_port);
161 80         474 dbg("dns: nameservers set to %s", join(', ', @ns_addr_port));
162 80         348 $res->nameservers(@ns_addr_port);
163             }
164 80         7769 1;
165 80 50       198 } 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 80 0       2444 $self->{force_ipv6} ? ', forced IPv6' : '');
    50          
175             dbg("dns: is Net::DNS::Resolver available? %s",
176 80 50       534 $self->{no_resolver} ? "no" : "yes" );
177 80 50 33     687 if (!$self->{no_resolver} && defined $Net::DNS::VERSION) {
178 80         316 dbg("dns: Net::DNS version: %s", $Net::DNS::VERSION);
179             }
180              
181 80         295 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 0     0 1 0 my ($self) = @_;
192 0         0 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 79     79 1 185 my $self = shift;
204              
205 79         190 my $res = $self->{res};
206 79         172 my @ns_addr_port; # list of name servers: [addr]:port entries
207 79 100       449 if ($self->{conf}->{dns_servers}) { # specified in a config file
    50          
208 1         3 @ns_addr_port = @{$self->{conf}->{dns_servers}};
  1         5  
209 1         9 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 78 50       1074 : @{$res->{nameservers}};
  0         0  
213 78 50       3374 my $port = $res->UNIVERSAL::can('port') ? $res->port : $res->{port};
214 78         891 @ns_addr_port = map(untaint_var("[$_]:" . $port), @ns);
215 78         467 dbg("dns: servers obtained from Net::DNS : %s", join(', ',@ns_addr_port));
216             }
217 79         618 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 94     94 1 250 my $self = shift;
229              
230 94 50       545 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 79         416 $self->{available_dns_servers} = [ $self->configured_nameservers() ];
237             }
238 94 50 33     554 if ($self->{force_ipv4} || $self->{force_ipv6}) {
239             # filter the list according to a chosen protocol family
240 94         318 my $ip4_re = IPV4_ADDRESS;
241 94         206 my(@filtered_addr_port);
242 94         199 for (@{$self->{available_dns_servers}}) {
  94         369  
243 172         510 local($1,$2);
244 172 50       1307 /^ \[ (.*) \] : (\d+) \z/xs or next;
245 172         744 my($addr,$port) = ($1,$2);
246 172 50       2775 if ($addr =~ /^${ip4_re}\z/o) {
    0          
247 172 50       1053 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 94 50       243 if (@filtered_addr_port < @{$self->{available_dns_servers}}) {
  94         351  
255 0         0 dbg("dns: filtered DNS servers according to protocol family: %s",
256             join(", ",@filtered_addr_port));
257             }
258 94         208 @{$self->{available_dns_servers}} = @filtered_addr_port;
  94         302  
259             }
260             die "available_nameservers: No DNS servers available!\n"
261 94 50       187 if !@{$self->{available_dns_servers}};
  94         386  
262 94         197 return @{$self->{available_dns_servers}};
  94         296  
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 1     1 0 4 my $self = shift;
283 1         2 my $port_number; # resulting port number, or undef if none available
284              
285 1         4 my $conf = $self->{conf};
286 1         3 my $available_portscount = $conf->{dns_available_portscount};
287              
288             # initialize when called for the first time or after a config change
289 1 50       5 if (!defined $available_portscount) {
290 1         4 my $ports_bitset = $conf->{dns_available_ports_bitset};
291 1 50       4 if (!defined $ports_bitset) { # ensure it is initialized
292 1         8 Mail::SpamAssassin::Conf::set_ports_range(\$ports_bitset, 0, 0, 0);
293 1         4 $conf->{dns_available_ports_bitset} = $ports_bitset;
294             }
295             # prepare auxilliary 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         20 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         11 foreach my $bucket (0..255) {
304 256         281 my $cnt = 0;
305 256         321 my $b = substr($ports_bitset, $bucket*32, 32); # one bucket: 256 bits
306 256 100       438 if ($b eq $all_zeroes) { $ind += 256 }
  4 50       14  
307 252         279 elsif ($b eq $all_ones) { $ind += 256; $cnt += 256 }
  252         257  
308             else { # count nontrivial cases the slow way
309 0   0     0 vec($ports_bitset, $ind++, 1) && $cnt++ for 0..255;
310             }
311 256         273 $available_portscount += $cnt;
312 256         318 $bucket_counts[$bucket] = $cnt;
313             }
314 1         5 $conf->{dns_available_portscount} = $available_portscount;
315 1 50       4 if ($available_portscount) {
316 1         15 $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 1         8 dbg("dns: %d configured local ports for DNS queries", $available_portscount);
325 1 50       4 if ($available_portscount > 0) {
326 1         2 my $ports_bitset = $conf->{dns_available_ports_bitset};
327 1         8 my $n = int(rand($available_portscount));
328 1         2 my $bucket_counts_ref = $conf->{dns_available_portscount_buckets};
329 1         1 my $ind = 0;
330 1         4 foreach my $bucket (0..255) {
331             # find the bucket containing n-th turned-on bit
332 114         121 my $cnt = $bucket_counts_ref->[$bucket];
333 114 100       144 if ($cnt > $n) { last } else { $n -= $cnt; $ind += 256 }
  1         6  
  113         116  
  113         131  
334             }
335 1         7 while ($ind <= 65535) { # scans one bucket, runs at most 256 iterations
336             # find the n-th turned-on bit within the corresponding bucket
337 202 50       294 if (vec($ports_bitset, $ind, 1)) {
338 202 100       257 if ($n <= 0) { $port_number = $ind; last } else { $n-- }
  1         3  
  1         4  
  201         210  
339             }
340 201         277 $ind++;
341             }
342             }
343 1         3 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 1     1 1 4 my ($self) = @_;
355              
356 1 50       9 dbg("dns: connect_sock, resolver: %s", $self->{no_resolver} ? "no" : "yes");
357 1 50       4 return if $self->{no_resolver};
358              
359 1 50       6 $io_socket_module_name
360             or die "No Perl modules for network socket available";
361              
362 1 50       5 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 1         3 my $sock;
368             my $errno;
369              
370             # list of name servers: [addr]:port entries
371 1         3 my @ns_addr_port = $self->available_nameservers();
372             # use the first name server in a list
373 1         5 my($ns_addr,$ns_port); local($1,$2);
  1         3  
374 1 50       19 ($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 1         3 my $ip4_re = IPV4_ADDRESS;
382 1         2 my $srcaddr;
383 1 50       5 if ($self->{force_ipv4}) {
    0          
    0          
    0          
384 1         3 $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 1         3 my $lport;
397 1         2 my $attempts = 0;
398 1         2 for (;;) {
399 1         2 $attempts++;
400 1         13 $lport = $self->pick_random_available_port();
401 1 50       11 if (!defined $lport) {
402 0         0 $lport = 0;
403 0         0 dbg("no configured local ports for DNS queries, letting OS choose");
404             }
405 1 50       6 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 1   50     9 dbg("dns: LocalAddr: [%s]:%d, name server: [%s]:%d, module %s",
411             $srcaddr||'x', $lport, $ns_addr, $ns_port, $io_socket_module_name);
412 1         14 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 1         19 $sock = $io_socket_module_name->new(%args);
421              
422 1 50       1219 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 1 50       6 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 1         3 my($bufsiz,$newbufsiz);
445 1 50       40 $bufsiz = $sock->sockopt(Socket::SO_RCVBUF)
446             or die "cannot get a resolver socket rx buffer size: $!";
447 1 50       59 if ($bufsiz >= 32*1024) {
448 1         5 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 1         5 1;
459 1 50       2 } 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 1         4 $self->{sock} = $sock;
465 1         13 $self->{sock_as_vec} = $self->fhs_to_vec($self->{sock});
466 1         8 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 13     13 0 22 my ($self) = @_;
475 13 100       56 $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 13     13 1 41 my ($self, $domain, $type, $class) = @_;
532              
533 13 50       29 return if $self->{no_resolver};
534              
535             # construct a PTR query if it looks like an IPv4 address
536 13 50 33     74 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 13 50       32 $type = 'A' if !defined $type; # a Net::DNS::Packet default
544 13 50       39 $class = 'IN' if !defined $class; # a Net::DNS::Packet default
545              
546 13         19 my $packet;
547             eval {
548              
549 13 50       42 if (utf8::is_utf8($domain)) { # since Perl 5.8.1
550 0         0 info("dns: new_dns_packet: domain is utf8 flagged: %s", $domain);
551             }
552              
553 13         121 $domain =~ s/\.*\z/./s;
554 13 50       104 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 13 50       43 if ($self->{conf}->{dns_options}->{dns0x20}) {
565 0         0 $domain = dnsext_dns0x20($domain);
566             } else {
567 13         32 $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 13 0       33 $domain =~ s{ ( [\000-\037\177-\377\\] ) }
  0         0  
575             { $1 eq '\\' ? "\\$1" : sprintf("\\%03d",ord($1)) }xgse;
576 13         107  
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 13         1818 # time, $domain, $type, $packet->id);
582 13 50       27 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             warn sprintf(
590             "dns: new_dns_packet (domain=%s type=%s class=%s) failed: %s\n",
591             $domain, $type, $class, $eval_stat);
592             };
593 13 50       31  
594             if ($packet) {
595 13         48 # RD flag needs to be set explicitly since Net::DNS 1.01, Bug 7223
596             $packet->header->rd(1);
597              
598 13         360 # my $udp_payload_size = $self->{res}->udppacketsize;
599 13 50 33     69 my $udp_payload_size = $self->{conf}->{dns_options}->{edns};
600             if ($udp_payload_size && $udp_payload_size > 512) {
601 13 50       61 # dbg("dns: adding EDNS ext, UDP payload size %d", $udp_payload_size);
602 13         34 if ($packet->UNIVERSAL::can('edns')) { # available since Net::DNS 0.69
603             $packet->edns->size($udp_payload_size);
604 0         0 } else { # legacy mechanism
605             my $optrr = Net::DNS::RR->new(Type => 'OPT', Name => '', TTL => 0,
606 0         0 Class => $udp_payload_size);
607             $packet->push('additional', $optrr);
608             }
609             }
610             }
611 13         8413  
612             return $packet;
613             }
614              
615             # Internal function used only in this file
616             ## compute a unique ID for a packet to match the query to the reply
617             ## It must use only data that is returned unchanged by the nameserver.
618             ## Argument is a Net::DNS::Packet that has a non-empty question section,
619             ## return is an (opaque) string that can be used as a hash key
620 26     26   50 sub _packet_id {
621 26         68 my ($self, $packet) = @_;
622 26         154 my $header = $packet->header;
623 26         199 my $id = $header->id;
624             my @questions = $packet->question;
625 26 50       174  
626             @questions <= 1
627             or warn "dns: packet has multiple questions: " . $packet->string . "\n";
628 26 50       58  
629             if ($questions[0]) {
630             # Bug 6232: Net::DNS::Packet::new is not consistent in keeping data in
631             # sections of a packet either as original bytes or presentation-encoded:
632             # creating a query packet as above in new_dns_packet() keeps label in
633             # non-encoded form, yet on parsing an answer packet, its query section
634             # is converted to presentation form by Net::DNS::Question::parse calling
635             # Net::DNS::Packet::dn_expand and Net::DNS::wire2presentation in turn.
636             # Let's undo the effect of the wire2presentation routine here to make
637             # sure the query section of an answer packet matches the query section
638             # in our packet as formed by new_dns_packet():
639 26         91 #
640 26 50       680 my($class,$type,$qname) = decode_dns_question_entry($questions[0]);
641 26         126 $qname =~ tr/A-Z/a-z/ if !$self->{conf}->{dns_options}->{dns0x20};
642             return join('/', $id, $class, $type, $qname);
643              
644             } else {
645             # Odd, this should not happen, a DNS servers is supposed to retain
646             # a question section in its reply. There is a bug in Net::DNS 0.72
647             # and earlier where a signal (e.g. a timeout alarm) during decoding
648             # of a reply packet produces a seemingly valid packet object, but
649             # with missing sections - see [rt.cpan.org #83451] .
650             #
651             # Better support it; just return the (safe) ID part, along with
652             # a text token indicating that the packet had no question part.
653 0         0 #
654             return $id . "/NO_QUESTION_IN_PACKET";
655             }
656             }
657              
658             ###########################################################################
659              
660             =item $id = $res->bgsend($domain, $type, $class, $cb)
661              
662             Quite similar to C<Net::DNS::Resolver::bgsend>, except that when a reply
663             packet eventually arrives, and C<poll_responses> is called, the callback
664             sub reference C<$cb> will be called.
665              
666             Note that C<$type> and C<$class> may be C<undef>, in which case they
667             will default to C<A> and C<IN>, respectively.
668              
669             The callback sub will be called with three arguments -- the packet that was
670             delivered, and an id string that fingerprints the query packet and the expected
671             reply. The third argument is a timestamp (Unix time, floating point), captured
672             at the time the packet was collected. It is expected that a closure callback
673             be used, like so:
674              
675             my $id = $self->{resolver}->bgsend($domain, $type, undef, sub {
676             my ($reply, $reply_id, $timestamp) = @_;
677             $self->got_a_reply ($reply, $reply_id);
678             });
679              
680             The callback can ignore the reply as an invalid packet sent to the listening
681             port if the reply id does not match the return value from bgsend.
682              
683             =cut
684              
685 13     13 1 39 sub bgsend {
686 13 50       39 my ($self, $domain, $type, $class, $cb) = @_;
687             return if $self->{no_resolver};
688 13         34  
689             $self->{send_timed_out} = 0;
690 13         46  
691 13 50       35 my $pkt = $self->new_dns_packet($domain, $type, $class);
692             return if !$pkt; # just bail out, new_dns_packet already reported a failure
693 13         40  
694 13         59 my @ns_addr_port = $self->available_nameservers();
695 13         22 dbg("dns: bgsend, DNS servers: %s", join(', ',@ns_addr_port));
696             my $n_servers = scalar @ns_addr_port;
697 13         22  
698 13         38 my $ok;
699 13         37 for (my $attempts=1; $attempts <= $n_servers; $attempts++) {
700             dbg("dns: attempt %d/%d, trying connect/sendto to %s",
701 13         57 $attempts, $n_servers, $ns_addr_port[0]);
702 13 50 33     91 $self->connect_sock_if_reqd();
703 13         3930 if ($self->{sock} && defined($self->{sock}->send($pkt->data, 0))) {
  13         25  
704             $ok = 1; last;
705 0 0       0 } else { # any other DNS servers in a list to try?
706             my $msg = !$self->{sock} ? "unable to connect to $ns_addr_port[0]"
707 0         0 : "sendto() to $ns_addr_port[0] failed: $!";
708 0 0       0 $self->finish_socket();
709 0         0 if ($attempts >= $n_servers) {
710 0         0 warn "dns: $msg, no more alternatives\n";
711             last;
712             }
713 0         0 # try with a next DNS server, rotate the list left
714 0         0 warn "dns: $msg, failing over to $ns_addr_port[1]\n";
715 0         0 push(@ns_addr_port, shift(@ns_addr_port));
716             $self->available_nameservers(@ns_addr_port);
717             }
718 13 50       42 }
719 13         46 return if !$ok;
720 13         58 my $id = $self->_packet_id($pkt);
721 13         39 dbg("dns: providing a callback for id: $id");
722 13         134 $self->{id_to_callback}->{$id} = $cb;
723             return $id;
724             }
725              
726             ###########################################################################
727              
728             =item $id = $res->bgread()
729              
730             Similar to C<Net::DNS::Resolver::bgread>. Reads a DNS packet from
731             a supplied socket, decodes it, and returns a Net::DNS::Packet object
732             if successful. Dies on error.
733              
734             =cut
735              
736 13     13 1 22 sub bgread {
737 13         28 my ($self) = @_;
738 13         69 my $sock = $self->{sock};
739 13 50       166 my $packetsize = $self->{res}->udppacketsize;
740 13         40 $packetsize = 512 if $packetsize < 512; # just in case
741 13         75 my $data = '';
742 13 50       386 my $peeraddr = $sock->recv($data, $packetsize+256); # with some size margin for troubleshooting
743 13         56 defined $peeraddr or die "bgread: recv() failed: $!";
744 13 50       574 my $peerhost = $sock->peerhost;
745 13         46 $data ne '' or die "bgread: received empty packet from $peerhost";
746 13         55 dbg("dns: bgread: received %d bytes from %s", length($data), $peerhost);
747 13 50       3485 my($answerpkt, $decoded_length) = Net::DNS::Packet->new(\$data);
748 13         88 $answerpkt or die "bgread: decoding DNS packet failed: $@";
749 13 50 33     202 $answerpkt->answerfrom($peerhost);
      33        
750 0         0 if (defined $decoded_length && $decoded_length ne "" && $decoded_length != length($data)) {
751             warn sprintf("bgread: received a %d bytes packet from %s, decoded %d bytes\n",
752             length($data), $peerhost, $decoded_length);
753 13         78 }
754             return $answerpkt;
755             }
756              
757             ###########################################################################
758              
759             =item $nfound = $res->poll_responses()
760              
761             See if there are any C<bgsend> reply packets ready, and return
762             the number of such packets delivered to their callbacks.
763              
764             =cut
765              
766 5     5 1 16 sub poll_responses {
767 5 50       18 my ($self, $timeout) = @_;
768 5 50       17 return if $self->{no_resolver};
769 5         8 return if !$self->{sock};
770             my $cnt = 0;
771 5         13  
772 5         6 my $rin = $self->{sock_as_vec};
773             my $rout;
774 5         8  
775 18         32 for (;;) {
776             my ($nfound, $timeleft, $eval_stat);
777 18         24 eval { # use eval to catch alarm signal
778 18 100 66     84 my $timer; # collects timestamp when variable goes out of scope
779 1         5 if (!defined($timeout) || $timeout > 0)
780 18         50 { $timer = $self->{main}->time_method("poll_dns_idle") }
781 18         1974 $! = 0;
782 18         75 ($nfound, $timeleft) = select($rout=$rin, undef, undef, $timeout);
783 18 50       32 1;
784 0 0       0 } or do {
  0         0  
785             $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
786 18 50 33     103 };
    50          
    100          
787             if (defined $eval_stat) {
788 0 0       0 # most likely due to an alarm signal, resignal if so
789 0         0 die "dns: (2) $eval_stat\n" if $eval_stat =~ /__alarm__ignore__\(.*\)/s;
790 0         0 warn "dns: select aborted: $eval_stat\n";
791             return;
792 0 0       0 } elsif (!defined $nfound || $nfound < 0) {
  0         0  
793 0         0 if ($!) { warn "dns: select failed: $!\n" }
794 0         0 else { info("dns: select interrupted") } # shouldn't happen
795             return;
796 5 50       28 } elsif (!$nfound) {
  0 50       0  
797 0         0 if (!defined $timeout) { warn("dns: select returned empty-handed\n") }
798 5         23 elsif ($timeout > 0) { dbg("dns: select timed out %.3f s", $timeout) }
799             return;
800             }
801 13         44  
802 13         26 my $now = time;
803 13 50       34 $timeout = 0; # next time around collect whatever is available, then exit
804             last if $nfound == 0;
805 13         42  
806             my $packet;
807             # Bug 7265, use our own bgread() below
808             # $packet = $self->{res}->bgread($self->{sock});
809 13         42 eval {
810 13 50       22 $packet = $self->bgread(); # Bug 7265, use our own bgread()
811 0         0 } or do {
812 0 0       0 undef $packet;
  0         0  
813             my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
814 0 0       0 # resignal if alarm went off
815 0         0 die $eval_stat if $eval_stat =~ /__alarm__ignore__\(.*\)/s;
816             info("dns: bad dns reply: %s", $eval_stat);
817             };
818 13 50       34  
819             if (!$packet) {
820             # error already reported above
821             # my $dns_err = $self->{res}->errorstring;
822             # die "dns (3) $dns_err\n" if $dns_err =~ /__alarm__ignore__\(.*\)/s;
823             # info("dns: bad dns reply: $dns_err");
824 13         47 } else {
825 13 50       80 my $header = $packet->header;
826 0         0 if (!$header) {
827             info("dns: dns reply is missing a header section");
828 13         51 } else {
829 13         1297 my $rcode = $header->rcode;
830 13         110 my $packet_id = $header->id;
831             my $id = $self->_packet_id($packet);
832 13 100       39  
833             if ($rcode eq 'NOERROR') { # success
834 10         37 # NOERROR, may or may not have answer records
835             dbg("dns: dns reply %s is OK, %d answer records",
836 10 50       28 $packet_id, $header->ancount);
837 0   0     0 if ($header->tc) { # truncation flag turned on
838 0 0       0 my $edns = $self->{conf}->{dns_options}->{edns} || 512;
839             info("dns: reply to %s truncated (%s), %d answer records", $id,
840             $edns == 512 ? "EDNS off" : "EDNS $edns bytes",
841             $header->ancount);
842             }
843             } else {
844             # some failure, e.g. NXDOMAIN, SERVFAIL, FORMERR, REFUSED, ...
845 3         16 # btw, one reason for SERVFAIL is an RR signature failure in DNSSEC
846             dbg("dns: dns reply to %s: %s", $id, $rcode);
847             }
848              
849             # A hash lookup: the id must match exactly (case-sensitively).
850             # The domain name part of the id was lowercased if dns0x20 is off,
851             # and case-randomized when dns0x20 option is on.
852 13         181 #
853             my $cb = delete $self->{id_to_callback}->{$id};
854 13 50       27  
855 13         47 if ($cb) {
856 13         134 $cb->($packet, $id, $now);
857             $cnt++;
858 0 0 0     0 } else { # no match, report the problem
859             if ($rcode eq 'REFUSED' || $id =~ m{^\d+/NO_QUESTION_IN_PACKET\z}) {
860             # the failure was already reported above
861 0 0       0 } else {
862             info("dns: no callback for id %s, ignored; packet: %s",
863             $id, $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 406 sub bgabort {
896 192         641 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 0     0 1 0 sub send {
915 0 0       0 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 0         0 #
924             utf8::encode($name);
925 0         0  
926 0         0 my $retrans = $self->{retrans};
927 0         0 my $retries = $self->{retry};
928 0         0 my $timeout = $retrans;
929 0         0 my $answerpkt;
930 0   0     0 my $answerpkt_avail = 0;
931             for (my $i = 0;
932             (($i < $retries) && !defined($answerpkt));
933             ++$i, $retrans *= 2, $timeout = $retrans) {
934 0 0       0  
935             $timeout = 1 if ($timeout < 1);
936             # note nifty use of a closure here. I love closures ;)
937 0     0   0 my $id = $self->bgsend($name, $type, $class, sub {
938 0         0 my ($reply, $reply_id, $timestamp) = @_;
  0         0  
939 0         0 $answerpkt = $reply; $answerpkt_avail = 1;
940             });
941 0 0       0  
942             last if !defined $id; # perhaps a restricted zone or a serious failure
943 0         0  
944 0         0 my $now = time;
945             my $deadline = $now + $timeout;
946 0         0  
947 0 0       0 while (!$answerpkt_avail) {
  0         0  
  0         0  
948 0         0 if ($now >= $deadline) { $self->{send_timed_out} = 1; last }
949 0         0 $self->poll_responses(1);
950             $now = time;
951             }
952 0         0 }
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 41     41 1 117 sub finish_socket {
985 41 100       210 my ($self) = @_;
986             if ($self->{sock}) {
987 1 50       19 $self->{sock}->close()
988 1         80 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 40     40 1 116 sub finish {
1002 40         249 my ($self) = @_;
1003 40         80 $self->finish_socket();
  40         702  
1004             %{$self} = ();
1005             }
1006              
1007             ###########################################################################
1008             # non-public methods.
1009              
1010             # should move to Util.pm (TODO)
1011 1     1 0 6 sub fhs_to_vec {
1012 1         9 my ($self, @fhlist) = @_;
1013 1         12 my $rin = '';
1014 1         5 foreach my $sock (@fhlist) {
1015 1 50       4 my $fno = fileno($sock);
1016 0         0 if (!defined $fno) {
1017             warn "dns: oops! fileno now undef for $sock";
1018 1         6 } else {
1019             vec ($rin, $fno, 1) = 1;
1020             }
1021 1         12 }
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