File Coverage

blib/lib/CCCP/LiveMX.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package CCCP::LiveMX;
2              
3 1     1   1318 use strict;
  1         126  
  1         42  
4 1     1   6 use warnings;
  1         2  
  1         57  
5              
6             our $VERSION = '0.01';
7              
8             # from Mail::CheckUser, I use only $Mail::CheckUser::NXDOMAIN
9 1     1   410 use Mail::CheckUser qw();
  0            
  0            
10             use Net::DNS;
11             use Net::Ping 2.24;
12              
13             =head1 NAME
14              
15             CCCP::LiveMX
16              
17             =head1 DESCRIPTION
18              
19             Getting a ip-list of living MX-records for hostname
20              
21             =head1 SYNOPSIS
22              
23             use CCCP::LiveMX;
24            
25             my $lmx = CCCP::LiveMX->check_host('example.org');
26             if ($lmx->success) {
27             my @live_ip = $lmx->live_ip;
28             } else {
29             print $lmx->error,"\n";
30             my @not_ping_ip = $lmx->not_ping;
31             my @not_ask_ip = $lmx->not_ask;
32             }
33            
34             =head1 PACKAGE VARIABLES
35              
36             =head2 $CCCP::LiveMX::timeout
37              
38             Timeout for ping, resolve and another.
39             By default 5 sec.
40              
41             =head1 METHODS
42              
43             =cut
44              
45             $CCCP::LiveMX::timeout = 5;
46              
47             my $resolver;
48             my $ping;
49              
50             =head2 check_host($host_name)
51              
52             Checking MX records for C<$host_name> and return instance.
53              
54             =cut
55             sub check_host {
56             my ($class, $host) = @_;
57            
58             my $self = bless {
59             error => undef,
60             mx => {}
61             }, $class;
62            
63             # Net::DNS::Resolver as a singletone
64             $resolver ||= Net::DNS::Resolver->new();
65            
66             # Net::Ping as a singletone
67             unless ($ping) {
68             $ping = Net::Ping->new("syn", $CCCP::LiveMX::timeout);
69             $ping->{port_num} = getservbyname("smtp", "tcp");
70             $ping->service_check(1);
71             };
72            
73             # getting mx-records
74             $resolver->udp_timeout($CCCP::LiveMX::timeout);
75             my @mx = mx($resolver, $host);
76             unless (@mx) {
77             # if the mx record is not found, try to check the hostname as a mail-server
78             $resolver->udp_timeout($CCCP::LiveMX::timeout);
79             my $res = $resolver->search($host, 'A');
80             if ($res) {
81             my $ip;
82             foreach my $rr ($res->answer) {
83             if ($rr->type eq "A") {
84             $ip = $rr->address;
85             last;
86             } elsif ($rr->type eq "CNAME") {
87             $ip = $rr->cname;
88             } else {
89             # should never happen!
90             $ip = "";
91             }
92             }
93             if ($Mail::CheckUser::NXDOMAIN->{lc $ip}) {
94             $self->error('Wildcard gTLD: '.$host.' ('.(lc $ip || '').')');
95             return $self;
96             }
97             $self->_mx_servers($host,0);
98             } else {
99             $self->error('DNS failure: '.$host.' ('.$resolver->errorstring.')');
100             return $self;
101             }
102             } else {
103             # if there is a mx-records, they have always been ordered by "preference"
104             $self->_mx_servers(map {$_->exchange,$_->preference} @mx);
105             };
106            
107             foreach my $mserver ($self->_mx_servers) {
108             if ($Mail::CheckUser::NXDOMAIN->{lc $mserver}) {
109             $self->error('Wildcard gTLD: '.$host.' ('.(lc $mserver || '').')');
110             return $self;
111             };
112             # getting ip from server-name
113             my $ip;
114             if ($mserver !~ /^\d+\.\d+\.\d+\.\d+$/) {
115             # resolve server-name if we do not have ip
116             $resolver->udp_timeout($CCCP::LiveMX::timeout);
117             if (my $ans = $resolver->query($mserver)) {
118             foreach my $rr_a ($ans->answer) {
119             if ($rr_a->type eq "A") {
120             $ip = $rr_a->address;
121             $self->_mx_ip($mserver,$ip);
122             last;
123             }
124             }
125             }
126             } else {
127             $ip = $mserver;
128             $self->_mx_ip($mserver,$ip);
129             }
130             next unless $ip;
131            
132             # try ping
133             my $succ_ping = $ping->ping($ip) ? 1 : 0;
134             $self->_mx_ping($mserver, $succ_ping);
135             next unless $succ_ping;
136            
137             # try to get an answer
138             my $succ_ask = $ping->ack($ip) ? 1 : 0;
139             $self->_mx_ask($mserver,$succ_ask);
140             next unless $succ_ask;
141             }
142            
143             unless (@{$self->live_ip}) {
144             $self->error('Not found live ip for: '.$host);
145             }
146            
147             return $self;
148             }
149              
150             =head2 success()
151              
152             Return status of check (bool)
153              
154             =cut
155             sub success {
156             my $self = shift;
157            
158             return $self->error ? 0 : 1;
159             }
160              
161             =head2 live_ip()
162              
163             Return list avaliable ip for host, sorted by "preference" mx-records
164              
165             =cut
166             sub live_ip {
167             my $self = shift;
168             if ($self->{live_ip}) {
169             return wantarray ? @{$self->{live_ip}} : $self->{live_ip}
170             } else {
171             $self->{live_ip} = [$self->_mx_ask];
172             return $self->live_ip;
173             };
174             }
175              
176             =head2 not_ping()
177              
178             Return list ip for host, that not ping
179              
180             =cut
181             sub not_ping {
182             my $self = shift;
183             my @ret = map {$self->{mx}->{$_}->{ip}} grep {!$self->{mx}->{$_}->{ping}} keys %{$self->{mx}};
184             return wantarray ? @ret : [@ret];
185             }
186              
187             =head2 not_ask()
188              
189             Return list ip for host, that ping but not ask
190              
191             =cut
192             sub not_ask {
193             my $self = shift;
194             my @ret = map {$self->{mx}->{$_}->{ip}} grep {!$self->{mx}->{$_}->{ask}} keys %{$self->{mx}};
195             return wantarray ? @ret : [@ret];
196             }
197              
198             =head2 error()
199              
200             Return error(string) or undef
201              
202             =cut
203             sub error {
204             my $self = shift;
205             if (@_) {
206             $self->{error} = shift;
207             return;
208             } else {
209             return $self->{error};
210             };
211             }
212              
213             # добавляем сервак с весом в список известных нам
214             sub _mx_servers {
215             my $self = shift;
216             if (@_) {
217             my %hash = @_;
218             while (my ($mx_name, $order) = each %hash) {
219             $self->{mx}->{$mx_name}->{order} = $order unless exists $self->{mx}->{$mx_name}->{order};
220             }
221             } else {
222             return keys %{$self->{mx}};
223             };
224             }
225              
226             # добавляем ip для сервака
227             sub _mx_ip {
228             my $self = shift;
229             if (@_) {
230             my %hash = @_;
231             while (my ($mx_name, $ip) = each %hash) {
232             $self->{mx}->{$mx_name}->{ip} = $ip unless exists $self->{mx}->{$mx_name}->{ip};
233             }
234             return;
235             } else {
236             return map {$self->{mx}->{$_}->{ip}} grep {$self->{mx}->{$_}->{ip}} keys %{$self->{mx}};
237             };
238             }
239              
240             # добавляем стутус пинга
241             sub _mx_ping {
242             my $self = shift;
243             my $mx_name = shift;
244             if ($mx_name and @_) {
245             $self->{mx}->{$mx_name}->{ping} = $_[0];
246             return;
247             } else {
248             return map {$self->{mx}->{$_}->{ip}} grep {$self->{mx}->{$_}->{ping}} keys %{$self->{mx}};
249             };
250             }
251              
252             # респонсим
253             sub _mx_ask {
254             my $self = shift;
255             my $mx_name = shift;
256             if ($mx_name and @_) {
257             $self->{mx}->{$mx_name}->{ask} = $_[0];
258             return;
259             } else {
260             return map {$self->{mx}->{$_}->{ip}} sort {$self->{mx}->{$a}->{order} <=> $self->{mx}->{$b}->{order}} grep {$self->{mx}->{$_}->{ask}} keys %{$self->{mx}};
261             };
262             }
263              
264             =head1 DEPENDS ON
265              
266             =over 4
267              
268             =item *
269              
270             L (used only package variables)
271              
272             =item *
273              
274             L
275              
276             =item *
277              
278             L
279              
280             =back
281              
282             =head1 AUTHOR
283              
284             mr.Rico
285              
286             =cut
287              
288             1;
289             __END__