File Coverage

blib/lib/Mail/Milter/Authentication/Resolver.pm
Criterion Covered Total %
statement 12 65 18.4
branch 0 18 0.0
condition 0 9 0.0
subroutine 4 10 40.0
pod 4 5 80.0
total 20 107 18.6


line stmt bran cond sub pod time code
1             package Mail::Milter::Authentication::Resolver;
2 99     99   549 use strict;
  99         201  
  99         2408  
3 99     99   404 use warnings;
  99         195  
  99         3308  
4             our $VERSION = '20191206'; # VERSION
5 99     99   416 use base 'Net::DNS::Resolver';
  99         198  
  99         12294  
6 99     99   553 use Scalar::Util qw{ weaken };
  99         209  
  99         53895  
7              
8              
9             {
10             sub new { ## no critic
11 0     0 1   my $class = shift;
12 0           my %args = @_;
13 0           my $self = $class->SUPER::new( @_ );
14 0           weaken($args{_handler});
15 0           $self->{ _handler } = $args{_handler};
16 0           $self->{ _timedout } = {};
17 0           return $self;
18             }
19             }
20              
21             sub clear_error_cache {
22 0     0 0   my $self = shift;
23 0           $self->{ _timedout } = {};
24 0           return;
25             }
26              
27             sub _do { ## no critic
28 0     0     my $self = shift;
29 0           my $what = shift;
30              
31 0           my $handler = $self->{_handler};
32 0           my $config = $handler->config();
33 0           my $timeout = $config->{'dns_timeout'};
34              
35 0           my $return;
36              
37 0           my $org_domain = $_[0];
38 0 0         if ( $handler->is_handler_loaded( 'DMARC' ) ) {
39 0           my $dmarc_object = $handler->get_handler('DMARC')->get_dmarc_object();
40 0           $org_domain = eval{ $dmarc_object->get_organizational_domain( $org_domain ) };
  0            
41 0           $handler->handle_exception( $@ );
42             }
43              
44             # If we have a 'cached' timeout for this org domain then return
45 0 0         if ( $self->{ _timedout }->{ $org_domain } ) {
46 0           my $domain = $_[0];
47 0           my $query = $_[1];
48 0           $handler->log_error( "Lookup $query $domain aborted due to previous DNS Lookup timeout on $org_domain" );
49 0           $self->errorstring('query timed out');
50 0           return;
51             }
52              
53 0           eval {
54 0           $handler->set_handler_alarm( ( $timeout + 0.1 ) * 1000000 ); # 0.1 seconds over that passed to Net::DNS::Resolver
55 0 0         $return = $self->SUPER::send( @_ ) if $what eq 'send';
56 0 0         $return = $self->SUPER::query( @_ ) if $what eq 'query';
57 0 0         $return = $self->SUPER::search( @_ ) if $what eq 'search';
58 0           $handler->reset_alarm();
59             };
60              
61 0 0         if ( my $error = $@ ) {
62 0           $handler->reset_alarm();
63 0           my $type = $handler->is_exception_type( $error );
64 0 0 0       if ( $type && $type eq 'Timeout' ) {
65             # We have a timeout, is it global or is it ours?
66 0 0         if ( $handler->get_time_remaining() > 0 ) {
67             # We have time left, but the lookup timed out
68             # Log this and move on!
69 0           $handler->log_error( 'DNS Lookup timeout not caught by Net::DNS::Resolver' );
70 0           $self->{ _timedout }->{ $org_domain } = 1;
71 0           $self->errorstring('query timed out');
72 0           return;
73             }
74             }
75 0           $handler->handle_exception( $error );
76             }
77              
78             # Timeouts or SERVFAIL are unlikely to recover within the lifetime of this transaction,
79             # when we encounter them, don't lookup this org domain again.
80 0 0 0       if ( ( $self->errorstring =~ /timeout/i ) || ( $self->errorstring eq 'query timed out' ) || ( $self->errorstring eq 'SERVFAIL' ) ) {
      0        
81 0           $self->{ _timedout }->{ $org_domain } = 1;
82             }
83              
84 0           return $return;
85             }
86              
87             sub query { ## no critic
88 0     0 1   my $self = shift;
89 0           return $self->_do( 'query', @_ );
90             }
91              
92             sub search { ## no critic
93 0     0 1   my $self = shift;
94 0           return $self->_do( 'search', @_ );
95             }
96              
97             sub send { ## no critic
98 0     0 1   my $self = shift;
99 0           return $self->_do( 'send', @_ );
100             }
101              
102             1;
103              
104             __END__
105              
106             =pod
107              
108             =encoding UTF-8
109              
110             =head1 NAME
111              
112             Mail::Milter::Authentication::Resolver
113              
114             =head1 VERSION
115              
116             version 20191206
117              
118             =head1 DESCRIPTION
119              
120             Subclass for Net::DNS::Resolver, Versions of Net::DNS::Resolver from 1.03 up (to at least
121             1.18 at time of writing) do not timeout as expected. This introduces a wrapper timeout around
122             the query, send, and search calls which will fire 0.1 seconds after the timeout value passed
123             to Net::DNS::Resolver
124              
125             =head1 AUTHOR
126              
127             Marc Bradshaw <marc@marcbradshaw.net>
128              
129             =head1 COPYRIGHT AND LICENSE
130              
131             This software is copyright (c) 2018 by Marc Bradshaw.
132              
133             This is free software; you can redistribute it and/or modify it under
134             the same terms as the Perl 5 programming language system itself.
135              
136             =cut