File Coverage

blib/lib/Plack/Middleware/DNSBL.pm
Criterion Covered Total %
statement 18 61 29.5
branch 0 22 0.0
condition 0 9 0.0
subroutine 6 13 46.1
pod 2 4 50.0
total 26 109 23.8


line stmt bran cond sub pod time code
1             package Plack::Middleware::DNSBL;
2              
3 1     1   20966 use strict;
  1         2  
  1         24  
4 1     1   4 use warnings;
  1         2  
  1         28  
5              
6 1     1   879 use parent qw(Plack::Middleware);
  1         296  
  1         5  
7              
8             our $VERSION = '0.0304';
9              
10 1     1   17012 use Carp ();
  1         2  
  1         20  
11 1     1   4 use Plack::Util::Accessor qw(cache cache_time resolver blacklists blacklisted);
  1         2  
  1         4  
12 1     1   809 use Net::DNS::Resolver;
  1         75671  
  1         600  
13              
14             sub prepare_app {
15 0     0 1   my ($self) = @_;
16 0 0         unless ($self->resolver) {
17 0           $self->resolver( Net::DNS::Resolver->new );
18             }
19              
20 0 0 0       unless ($self->blacklisted && ref $self->blacklisted eq 'CODE') {
21             $self->blacklisted(sub {
22 0     0     [ 500, [ 'Content-Type' => 'text/plain' ], [ '' ] ];
23 0           });
24             }
25              
26 0 0 0       unless ($self->blacklists && ref $self->blacklists eq 'HASH') {
27 0           Carp::carp("'blacklists' option must contain a HASHREF value");
28 0           $self->blacklists(+{ });
29             }
30              
31 0 0         unless ($self->cache_time) {
32 0           $self->cache_time('86400');
33             }
34             }
35              
36             sub query {
37 0     0 0   my ($self, $address) = @_;
38 0 0         my $response = $self->resolver->send($address)
39             or return;
40              
41             # A Record = black listed
42 0           foreach my $record ($response->answer) {
43 0 0         return 1 if $record->type eq 'A';
44             }
45             }
46              
47             sub is_blacklisted {
48 0     0 0   my ($self, $ip, $port) = @_;
49 0           my $reversed = _reverse_ip($ip);
50              
51             # Check if we have a cached response
52 0 0 0       if ($self->cache && (my $cached = $self->cache->get("dnsbl:$reversed"))) {
53 0           return @$cached;
54             }
55              
56 0           my ( $blacklisted, $blacklist );
57 0           foreach (keys %{ $self->blacklists }) {
  0            
58 0           my $address = $self->blacklists->{$_};
59              
60 0           $address =~ s/\$ip/$reversed/g;
61 0           $address =~ s/\$port/$port/g;
62              
63 0 0         if ($self->query($address)) {
64 0           $blacklist = $_;
65 0           $blacklisted = 1;
66 0           last;
67             }
68             }
69              
70             # Caches our result
71 0 0         if ($self->cache) {
72 0           $self->cache->set("dnsbl:$reversed" => [ $blacklisted, $blacklist, 1 ],
73             $self->cache_time);
74             }
75              
76 0           return ( $blacklisted, $blacklist, 0 );
77             }
78              
79             sub call {
80 0     0 1   my ($self, $env) = @_;
81 0 0         if (_is_ipv4($env->{REMOTE_ADDR})) {
82             # Check if this IP is blacklisted
83             my ($blacklisted, $blacklist, $is_cached)
84 0           = $self->is_blacklisted($env->{REMOTE_ADDR}, $env->{SERVER_PORT});
85              
86             # If it's blacklisted, call the callback and return it's return value
87 0 0         if ($blacklisted) {
88 0           $env->{DNSBL_BLACKLISTED} = 1;
89 0           $env->{DNSBL_BLACKLIST} = $blacklist;
90 0           $env->{DNSBL_IS_CACHED} = $is_cached;
91              
92 0           return $self->blacklisted->($env, $blacklist, $is_cached);
93             }
94             }
95              
96 0           return $self->app->($env);
97             }
98              
99              
100             # Helper functions
101 0     0     sub _is_ipv4 { shift =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/ }
102 0     0     sub _reverse_ip { join '.', reverse split /\./, shift }
103              
104             1;
105             __END__