File Coverage

blib/lib/Net/Abuse/Utils/Spamhaus.pm
Criterion Covered Total %
statement 8 58 13.7
branch 0 32 0.0
condition 0 4 0.0
subroutine 3 6 50.0
pod 2 2 100.0
total 13 102 12.7


line stmt bran cond sub pod time code
1             package Net::Abuse::Utils::Spamhaus;
2              
3 1     1   13371 use 5.008008;
  1         3  
4 1     1   3 use strict;
  1         1  
  1         15  
5 1     1   3 use warnings;
  1         5  
  1         608  
6              
7             our $VERSION = '0.09';
8             $VERSION = eval $VERSION; # see L
9              
10             =head1 NAME
11              
12             Net::Abuse::Utils::Spamhaus - Perl extension for checking data against the spamhaus blacklists
13              
14             =head1 SYNOPSIS
15              
16             use Net::Abuse::Utils::Spamhaus qw(check_fqdn check_ip);
17             my $addr = '222.186.44.110';
18             my $ret = check_ip($addr);
19              
20             $addr = 'test';
21             $ret = check_fqdn($addr);
22              
23             foreach (@$ret){
24             warn $_->{'assessment'}.': '.$_->{'description'}.' -- '.$_->{'id'};
25             }
26              
27             =head1 DESCRIPTION
28              
29             =head2 EXPORT
30              
31             check_ip, check_fqdn
32             =cut
33              
34             require Exporter;
35             our @ISA = qw(Exporter);
36              
37             # Items to export into callers namespace by default. Note: do not export
38             # names by default without a very good reason. Use EXPORT_OK instead.
39             # Do not simply export all your public functions/methods/constants.
40              
41             # This allows declaration use Net::Abuse::Utils::Spamhaus ':all';
42             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
43             # will save memory.
44             our %EXPORT_TAGS = ( 'all' => [ qw(
45             check_ip check_fqdn
46             ) ] );
47              
48             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
49              
50             our @EXPORT = qw(
51             check_ip check_fqdn
52             );
53              
54             # Preloaded methods go here.
55             # http://www.spamhaus.org/zen/
56             my $ip_codes = {
57             '127.0.0.2' => {
58             assessment => 'spam',
59             description => 'Direct UBE sources, spam operations & spam services',
60             },
61             '127.0.0.3' => {
62             assessment => 'spam',
63             description => 'Direct snowshoe spam sources detected via automation',
64             },
65             '127.0.0.4' => {
66             assessment => 'exploit',
67             description => 'CBL + customised NJABL. 3rd party exploits (proxies, trojans, etc.)',
68             },
69             '127.0.0.5' => {
70             assessment => 'exploit',
71             description => 'CBL + customised NJABL. 3rd party exploits (proxies, trojans, etc.)',
72             },
73             '127.0.0.6' => {
74             assessment => 'exploit',
75             description => 'CBL + customised NJABL. 3rd party exploits (proxies, trojans, etc.)',
76             },
77             '127.0.0.7' => {
78             assessment => 'exploit',
79             description => 'CBL + customised NJABL. 3rd party exploits (proxies, trojans, etc.)',
80             },
81             '127.0.0.9' => {
82             assessment => 'suspicious',
83             description => 'hijacked prefix',
84             },
85             '127.0.0.10' => {
86             assessment => 'spam',
87             description => 'End-user Non-MTA IP addresses set by ISP outbound mail policy',
88             },
89             '127.0.0.11' => {
90             assessment => 'spam',
91             description => 'End-user Non-MTA IP addresses set by ISP outbound mail policy',
92             },
93             };
94              
95             # http://www.spamhaus.org/faq/section/Spamhaus%20DBL
96             my $fqdn_codes = {
97             '127.0.1.2' => {
98             assessment => 'suspicious',
99             description => 'spammed domain',
100             },
101             '127.0.1.3' => {
102             assessment => 'suspicious',
103             description => 'spammed redirector / url shortener',
104             },
105             '127.0.1.4' => {
106             assessment => 'phishing',
107             description => 'phishing domain',
108             },
109             '127.0.1.5' => {
110             assessment => 'malware',
111             description => 'malware domain',
112             },
113             '127.0.1.6' => {
114             assessment => 'botnet',
115             description => 'Botnet C&C domain',
116             },
117             '127.0.1.102' => {
118             assessment => 'suspicious',
119             description => 'abused legit spam',
120             },
121             '127.0.1.103' => {
122             assessment => 'suspicious',
123             description => 'abused legit spammed redirector',
124             },
125             '127.0.1.104' => {
126             assessment => 'phishing',
127             description => 'abused legit phish',
128             },
129             '127.0.1.105' => {
130             assessment => 'malware',
131             description => 'abused legit malware',
132             },
133             '127.0.1.106' => {
134             assessment => 'botnet',
135             description => 'abused legit botnet',
136             },
137             '127.0.1.255' => {
138             description => 'BANNED',
139             },
140             };
141              
142             sub _return_rr {
143 0     0     my $lookup = shift;
144 0   0       my $type = shift || 'A';
145 0           my $timeout = shift;
146            
147             # little more thread friendly
148 0           require Net::DNS::Resolver;
149 0           my $r = Net::DNS::Resolver->new(recursive => 0);
150            
151 0 0         if($timeout){
152 0           $r->udp_timeout($timeout);
153 0           $r->tcp_timeout($timeout);
154             }
155            
156              
157 0           my $pkt = $r->send($lookup);
158 0 0         return unless($pkt);
159 0           my @rdata = $pkt->answer();
160 0 0         return unless(@rdata);
161 0           return (\@rdata);
162             }
163             =head2 FUNCTIONS
164              
165             =over
166            
167             =item check_fqdn()
168              
169             accepts: a fully qualified domain name (ex: example.com)
170             returns: an ARRAYREF of HASHREF's based on the spamhaus dbl
171              
172             =cut
173              
174             sub check_fqdn {
175 0     0 1   my $addr = shift;
176 0   0       my $timeout = shift || 10;
177              
178 0           my $lookup = $addr.'.dbl.spamhaus.org';
179 0           my $rdata = _return_rr($lookup,undef,$timeout);
180 0 0         return unless($rdata);
181            
182 0           my @array;
183 0           foreach (@$rdata){
184 0 0         next unless($_->address());
185 0 0         next unless($_->type() eq 'A');
186 0           my $code = $fqdn_codes->{$_->address()};
187 0 0         unless($code){
188 0           warn 'unknown return code: '.$_->address().' library ('.$VERSION.') needs updating, contact module author ('.$lookup.')';
189 0 0         $code->{'description'} = 'unknown' unless($code->{'description'});
190 0 0         $code->{'assessment'} = 'unknown' unless($code->{'assessment'});
191             }
192              
193 0 0         if($code->{'description'} =~ /BANNED/){
194 0           warn 'BANNED received from spamhaus, you should contact them and work it out';
195 0           return;
196             }
197             push(@array,{
198             id => 'http://www.spamhaus.org/query/dbl?domain='.$addr,
199             assessment => $code->{'assessment'},
200 0           description => $code->{'description'},
201             });
202             }
203 0           return(\@array);
204             }
205              
206             =item check_ip()
207              
208             accepts: a properly formatted ipv4 address (ex: 1.1.1.1)
209             returns: an ARRAY REF of HASHREF's based on feedback from the spamhaus zen list
210              
211             =cut
212              
213             sub check_ip {
214 0     0 1   my $addr = shift;
215 0           my $timeout = shift;
216            
217 0           my @bits = split(/\./,$addr);
218 0           my $lookup = join('.',reverse(@bits));
219 0           $lookup .= '.zen.spamhaus.org';
220              
221 0           my $rdata = _return_rr($lookup,undef,$timeout);
222 0 0         return unless($rdata);
223            
224 0           my $array;
225 0           foreach (@$rdata){
226 0 0         next unless($_->type() eq 'A');
227 0           my $code = $ip_codes->{$_->address()};
228            
229 0 0         unless($code){
230 0           warn 'unknown return code: '.$_->address().' library ('.$VERSION.') needs updating, contact module author ('.$lookup.')';
231 0 0         $code->{'description'} = 'unknown' unless($code->{'description'});
232 0 0         $code->{'assessment'} = 'unknown' unless($code->{'assessment'});
233             }
234              
235             # these aren't really malicious assessments, skip them
236             # see http://www.spamhaus.org/faq/answers.lasso?section=Spamhaus%20PBL#183
237 0 0         next if($_->address() =~ /\.(10|11)$/);
238             push(@$array,{
239             assessment => $code->{'assessment'},
240 0           description => $code->{'description'},
241             id => 'http://www.spamhaus.org/query/bl?ip='.$addr,
242             });
243             }
244 0           return($array);
245             }
246            
247             1;
248             __END__