File Coverage

blib/lib/Mail/RBL.pm
Criterion Covered Total %
statement 63 68 92.6
branch 14 24 58.3
condition 5 11 45.4
subroutine 12 12 100.0
pod 3 3 100.0
total 97 118 82.2


line stmt bran cond sub pod time code
1             package Mail::RBL;
2              
3             require 5.005_62;
4 2     2   3662 use Carp;
  2         4  
  2         181  
5 2     2   7417 use Socket;
  2         6598  
  2         1911  
6 2     2   17 use strict;
  2         15  
  2         79  
7 2     2   10 use warnings;
  2         4  
  2         71  
8 2     2   5087 use Net::DNS;
  2         316601  
  2         282  
9 2     2   13951 use NetAddr::IP ':aton';
  2         248524  
  2         15  
10              
11             # $Id: RBL.pm,v 1.10 2007/04/25 04:22:04 lem Exp $
12              
13             our $VERSION = do { sprintf " %d.%02d", (q$Revision: 1.10 $ =~ /\d+/g) };
14              
15             =pod
16              
17             =head1 NAME
18              
19             Mail::RBL - Perl extension to access RBL-style host verification services
20              
21             =head1 SYNOPSIS
22              
23             use Mail::RBL;
24              
25             my $list = new Mail::RBL('list.org');
26              
27             # You can also specify a resolver to use with Net::DNS::Resolver
28              
29             my $list = new Mail::RBL('list.org', $res);
30              
31             if ($list->check($host)) {
32             print "$host is in the list";
33             }
34              
35             my ($ip_result, $optional_info_txt) = $list->check($host);
36             # $optional_info_txt will be undef if the list does not provide TXT
37             # RRs along with the A RRs.
38              
39             print "The list says ", ($list->check($host))[1], " in its TXT RR\n";
40              
41             my ($ip_result, $optional_info_txt) = $list->check_rhsbl($hostname);
42              
43             =head1 DESCRIPTION
44              
45             This module eases the task of checking if a given host is in the
46             list. The methods available are described below:
47              
48             =over
49              
50             =item C<-E<gt>new(suffix, resolver)>
51              
52             Creates a list handle. The C<suffix> parameter is mandatory and
53             specifies which suffix to append to the queries. If left unspecified,
54             defaults to C<bl.spamcop.net>.
55              
56             An optional DNS resolver can be specified. An object of the
57             Net::DNS::Resolver(3) class is expected.
58              
59             =cut
60              
61             sub new {
62 20     20 1 59807 my $type = shift;
63 20   50     294 my $class = ref($type) || $type || "Mail::RBL";
64 20         34 my $suffix = shift;
65 20   66     154 my $res = shift || Net::DNS::Resolver->new;
66            
67 20 50       1575 my $self = {
68             suffix => defined $suffix ? $suffix : 'bl.spamcop.net',
69             res => $res,
70             };
71              
72 20         78 bless $self, $class;
73             }
74              
75             =pod
76              
77             =item C<-E<gt>check($host)>
78              
79             C<$host> can be either a hostname or an IP address. In the case of an
80             IP Address. In the case of a hostname, all the IP addresses will be
81             looked up and checked against the list. If B<any> of the addresses is
82             in the list, the host will be considered in the list as a whole.
83              
84             Returns either a C<NetAddr::IP> object as returned by the RBL itself,
85             or C<undef> in case the RBL does not supply an answer. This is
86             important because many lists inject some semantics on the DNS response
87             value, which now can be recovered easily with the program that uses
88             this module.
89              
90             In array context, any IP addresses are returned, followed by any TXT
91             RR (or undef if none). If no match is found, an empty list is returned
92             instead. In scalar context, only the first IP address (or undef) is
93             returned.
94              
95             =back
96              
97             =cut
98              
99             sub check ($$)
100             {
101 14     14 1 8176 my $self = shift;
102 14         32 my $host = shift;
103              
104 14 50       45 croak "Must call ->check() with a host to check"
105             unless length $host;
106              
107 14         54 foreach my $addr (_inverted_addresses($host)) {
108 14 100       52 if (my $val = $self->_do_check($addr))
109             {
110 4 100       2809 if (wantarray)
111             {
112 2         9 return ($val, $self->_do_txt($addr));
113             }
114             else
115             {
116 2         24 return $val;
117             }
118             }
119             }
120              
121 10         95 return;
122             }
123              
124             =pod
125              
126             =item C<-E<gt>check_rhsbl($host)>
127              
128             Analogous to C<-E<gt>check()>, but queries RHSBLs instead of IP-based
129             lists. This is useful for using lists such as some of
130             B<http://www.rfc-ignorant.org/>.
131              
132             Results and return values are the same as C<-E<gt>check()>.
133              
134             =cut
135              
136             sub check_rhsbl ($$)
137             {
138 32     32 1 8022 my $self = shift;
139 32         238 my $host = shift;
140              
141 32 50       121 croak "Must call ->check_rhsbl() with a host to check"
142             unless length $host;
143              
144 32 50       125 if (my $val = $self->_do_check($host))
145             {
146 0 0       0 if (wantarray)
147             {
148 0         0 return ($val, $self->_do_txt($host));
149             }
150             else
151             {
152 0         0 return $val;
153             }
154             }
155              
156 32         13777 return;
157             }
158              
159             sub _do_txt {
160 2     2   6 my $self = shift;
161 2         5 my $host = shift;
162              
163 2         4 my $res = $self->{res};
164 2         17 my $q = $res->query($host . '.' . $self->{suffix}, "TXT");
165 2         11116 my @txt = ();
166              
167 2 50       9 if ($q)
168             {
169 2         30 for my $rr ($q->answer)
170             {
171 2 50 33     20 next unless $rr->class eq 'IN' and $rr->type eq 'TXT';
172 2         70 push @txt, $rr->rdatastr;
173             }
174             }
175              
176 2         311 return @txt;
177             }
178              
179             sub _do_check {
180 46     46   93 my $self = shift;
181 46         78 my $host = shift;
182              
183 46         114 my $res = $self->{res};
184 46         337 my $q = $res->query($host . '.' . $self->{suffix}, "A");
185              
186 46 100       11313041 if ($q)
187             {
188 4         19 for my $rr ($q->answer)
189             {
190 4 50 33     40 next unless $rr->class eq 'IN' and $rr->type eq 'A';
191 4         167 return NetAddr::IP->new($rr->address);
192             }
193             }
194 42         267 return;
195             }
196              
197             sub _inverted_addresses {
198 14     14   24 my $host = shift;
199 14         19 my @addresses;
200             my @ret;
201              
202 14 50       99 if ($host =~ /^\d+\.\d+\.\d+\.\d+$/) {
203 14         86 push @ret, join('.', reverse split(/\./, $host));
204             }
205             else {
206 0         0 @addresses = (gethostbyname($host))[4];
207             }
208            
209 14         833 foreach my $addr (@addresses) {
210 0         0 push @ret, join('.', reverse unpack('C4', $addr));
211             }
212            
213 14         39 return @ret;
214             }
215              
216             1;
217             __END__
218              
219             =pod
220              
221             =head1 HISTORY
222              
223             $Log: RBL.pm,v $
224             Revision 1.10 2007/04/25 04:22:04 lem
225             Finished adding support for the custom resolver code - Implementation
226             was incomplete
227              
228             Revision 1.9 2006/12/08 00:01:14 lem
229             Get version straight from the CVS revision.
230              
231             Revision 1.8 2006/12/07 23:58:07 lem
232             Allow the user to provide a Net::DNS::Resolver object to perform DNS
233             resolution - This allows finer control over how the queries are
234             performed. Suggested by Eric Langheinrich.
235              
236              
237             =over
238              
239             =item 1.00
240              
241             Original version.
242              
243             =item 1.01
244              
245             Minor bug fixes. Cleaned up MS-DOS line endings. Changed test cases
246             (more and better tests). Now requires Test::More. More useful return
247             values. Improved docs. First crypto-signed distribution of this
248             module.
249              
250             =back
251              
252             =head1 AUTHOR
253              
254             Luis E. Munoz <luismunoz@cpan.org>
255              
256             =head1 SEE ALSO
257              
258             Net::DNS::Resolver(3), perl(1).
259              
260             =cut