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