File Coverage

blib/lib/Mail/VRFY.pm
Criterion Covered Total %
statement 88 164 53.6
branch 31 104 29.8
condition 3 10 30.0
subroutine 9 10 90.0
pod 1 3 33.3
total 132 291 45.3


line stmt bran cond sub pod time code
1             # Mail::VRFY.pm
2             # $Id: VRFY.pm,v 1.01 2014/05/21 21:09:18 jkister Exp $
3             # Copyright (c) 2004-2014 Jeremy Kister.
4             # Released under Perl's Artistic License.
5              
6             $Mail::VRFY::VERSION = "1.01";
7              
8             =head1 NAME
9              
10             Mail::VRFY - Utility to verify an email address
11              
12             =head1 SYNOPSIS
13              
14             use Mail::VRFY;
15              
16             my $code = Mail::VRFY::CheckAddress($emailaddress);
17              
18             my $code = Mail::VRFY::CheckAddress(addr => $emailaddress,
19             method => 'extended',
20             timeout => 12,
21             debug => 0);
22              
23             my $english = Mail::VRFY::English($code);
24              
25            
26             =head1 DESCRIPTION
27              
28             C was derived from Pete Fritchman's L.
29             Lots of code has been plucked. This package attempts to be
30             completely compatibile with Mail::Verify.
31              
32             C provides a C function for verifying email
33             addresses. Lots can be checked, according to the C option,
34             as described below.
35              
36             C differs from L in that:
37              
38             A. More granular control over what kind of checks to run
39             (via the method option).
40              
41             B. Email address syntax checking is much more stringent.
42              
43             C. After making a socket to an authoritative SMTP server,
44             we can start a SMTP conversation, to ensure the
45             mailserver does not give a failure on RCPT TO.
46              
47             D. More return codes.
48              
49             =head1 CONSTRUCTOR
50              
51             =over 4
52              
53             =item CheckAddress( [ADDR] [,OPTIONS] );
54              
55             If C is not given, then it may instead be passed as the C
56             option described below.
57              
58             C are passed in a hash like fashion, using key and value
59             pairs. Possible options are:
60              
61             B - The email address to check
62              
63             B - Which method of checking should be used:
64              
65             syntax - check syntax of email address only (no network testing).
66              
67             compat - check syntax, DNS, and MX connectivity (i.e. Mail::Verify)
68              
69             extended - compat + talk SMTP to see if server will reject RCPT TO
70              
71             B - Number of seconds to wait for data from remote host (Default: 12).
72             this is a per-operation timeout, meaning there is a separate timeout on
73             a DNS query, and each smtp conversation.
74              
75             B - Print debugging info to STDERR (0=Off, 1=On).
76              
77             =back
78              
79             =head1 RETURN VALUE
80              
81             Here are a list of return codes and what they mean:
82              
83             =over 4
84              
85             =item 0 The email address appears to be valid.
86              
87             =item 1 No email address was supplied.
88              
89             =item 2 There is a syntactical error in the email address.
90              
91             =item 3 There are no MX or A DNS records for the host in question.
92              
93             =item 4 There are no SMTP servers accepting connections.
94              
95             =item 5 All SMTP servers are misbehaving and wont accept mail.
96              
97             =item 6 All the SMTP servers temporarily refused mail.
98              
99             =item 7 One SMTP server permanently refused mail to this address.
100              
101             This module provides an English sub that will convert the code to
102             English for you.
103              
104             =back
105              
106             =head1 EXAMPLES
107              
108             use Mail::VRFY;
109             my $email = shift;
110             unless(defined($email)){
111             print "email address to be tested: ";
112             chop($email=);
113             }
114             my $code = Mail::VRFY::CheckAddress($email);
115             my $english = Mail::VRFY::English($code);
116             if($code){
117             print "Invalid email address: $english (code: $code)\n";
118             }else{
119             print "$english\n";
120             }
121              
122             =head1 CAVEATS
123              
124             A SMTP server can reject RCPT TO at SMTP time, or it can accept all
125             recipients, and send bounces later. All other things being equal,
126             Mail::VRFY will not detect the invalid email address in the latter case.
127              
128             Greylisters will cause you pain; look out for return code 6. Some
129             users will want to deem email addresses returning code 6 invalid,
130             others valid, and others will set up a queing mechanism to try again
131             later.
132              
133             =head1 RESTRICTIONS
134              
135             Email address syntax checking does not conform to RFC2822, however, it
136             will work fine on email addresses as we usually think of them.
137             (do you really want:
138              
139             "Foo, Bar"
140              
141             to be considered valid ?)
142              
143             =head1 AUTHOR
144              
145             Jeremy Kister : http://jeremy.kister.net./
146              
147             =cut
148              
149             package Mail::VRFY;
150              
151 1     1   657 use strict;
  1         2  
  1         33  
152 1     1   1327 use IO::Socket::INET;
  1         30634  
  1         10  
153 1     1   2678 use IO::Select;
  1         2181  
  1         57  
154 1     1   1045 use Net::DNS;
  1         84998  
  1         128  
155 1     1   1008 use Sys::Hostname::Long;
  1         5440  
  1         2503  
156              
157 1     1 0 10 sub Version { $Mail::VRFY::VERSION }
158              
159             sub English {
160 6     6 0 29 my $code = shift;
161 6         49 my @english = ( 'Email address seems valid.',
162             'No email address supplied.',
163             'Syntax error in email address.',
164             'No MX or A DNS records for this domain.',
165             'No advertised SMTP servers are accepting mail.',
166             'All advertised SMTP servers are misbehaving and wont accept mail.',
167             'All advertised SMTP servers are temporarily refusing mail.',
168             'One Advertised SMTP server permanently refused mail.',
169             );
170 6   33     36 return $english[$code] || "Unknown code: $code";
171             }
172              
173             sub CheckAddress {
174 6     6 1 92 my %arg;
175 6 50       13 if(@_ % 2){
176 0         0 my $addr = shift;
177 0         0 %arg = @_;
178 0         0 $arg{addr} = $addr;
179             }else{
180 6         20 %arg = @_;
181             }
182 6 50       14 return 1 unless $arg{addr};
183 6 50       15 if(exists($arg{timeout})){
184 6 50       15 warn "using timeout of $arg{timeout} seconds\n" if( $arg{debug} == 1 );
185             }else{
186 0         0 $arg{timeout} = 12;
187 0 0       0 warn "using default timeout of 12 seconds\n" if( $arg{debug} == 1 );
188             }
189              
190 6 50       11 if(exists($arg{from})){
191 0 0       0 warn "using specified envelope sender address: $arg{from}\n" if( $arg{debug} == 1 );
192             }
193              
194 6         7 my ($user,$domain,@mxhosts);
195              
196             # First, we check the syntax of the email address.
197 6 50       16 if(length($arg{addr}) > 256){
198 0 0       0 warn "email address is more than 256 characters\n" if( $arg{debug} == 1 );
199 0         0 return 2;
200             }
201 6 50       36 if($arg{addr} =~ /^(([a-z0-9_\.\+\-\=\?\^\#\&]){1,64})\@((([a-z0-9\-]){1,251}\.){1,252}[a-z0-9]{2,6})$/i){
202             # http://data.iana.org/TLD/tlds-alpha-by-domain.txt says all tlds >=2 && <= 6
203             # we don't support the .XN-- insanity
204 6         13 $user = $1;
205 6         7 $domain = $3;
206 6 50       15 if(length($domain) > 255){
207 0 0       0 warn "domain in email address is more than 255 characters\n" if( $arg{debug} == 1 );
208 0         0 return 2;
209             }
210             }else{
211 0 0       0 warn "email address does not look correct\n" if( $arg{debug} == 1 );
212 0         0 return 2;
213             }
214 6 100       24 return 0 if($arg{method} eq 'syntax');
215              
216 1         2 my $dnscheck = eval {
217 1     0   18 local $SIG{ALRM} = sub { die "Timeout.\n"; };
  0         0  
218 1         18 alarm($arg{timeout});
219 1         8 my @mxrr = Net::DNS::mx( $domain );
220             # Get the A record for each MX RR
221 1         15992 foreach my $rr (@mxrr) {
222 5         222 push( @mxhosts, $rr->exchange );
223             }
224 1 50       40 unless(@mxhosts) { # check for an A record...
225 0         0 my $resolver = Net::DNS::Resolver->new();
226 0         0 my $dnsquery = $resolver->search( $domain );
227 0 0       0 return 3 unless $dnsquery;
228 0         0 foreach my $rr ($dnsquery->answer) {
229 0 0       0 next unless $rr->type eq "A";
230 0         0 push( @mxhosts, $rr->address );
231             }
232 0 0       0 return 3 unless @mxhosts;
233             }
234 1 50       41 if($arg{debug} == 1){
235 0         0 foreach( @mxhosts ) {
236 0         0 warn "\@mxhosts -> $_\n";
237             }
238             }
239             };
240 1         7 alarm(0);
241              
242 1 50       6 if($@){
243 0 0       0 warn "problem resolving in the DNS: $@\n" if( $arg{debug} == 1 );
244 0         0 return 3;
245             }
246              
247 1 50       5 return $dnscheck unless(@mxhosts);
248              
249 1         3 my $misbehave=0;
250 1         2 my $tmpfail=0;
251 1         1 my $livesmtp=0;
252 1         4 foreach my $mx (@mxhosts) {
253 1         13 my $sock = IO::Socket::INET->new(Proto=>'tcp',
254             PeerAddr=> $mx,
255             PeerPort=> 25,
256             Timeout => $arg{timeout}
257             );
258 1 50       15753 if($sock){
259 1 50       7 warn "connected to ${mx}\n" if( $arg{debug} == 1 );
260 1         2 $livesmtp=1;
261 1 50       6 if($arg{method} eq 'compat'){
262 0         0 close $sock;
263 0         0 return 0;
264             }
265              
266 1         7 my $select = IO::Select->new;
267 1         16 $select->add($sock);
268              
269 1         48 my @banner = _getlines($select,$arg{timeout});
270 1 50       5 if(@banner){
271 1 50       7 if($arg{debug} == 1){
272 0         0 print STDERR "BANNER: ";
273 0         0 for(@banner){ print STDERR " $_"; }
  0         0  
274 0         0 print STDERR "\n";
275             }
276 1 50       36 unless($banner[-1] =~ /^220\s/){
277 0         0 print $sock "QUIT\r\n"; # be nice
278 0         0 close $sock;
279 0         0 $misbehave=1;
280 0 0       0 warn "$mx misbehaving: bad banner\n" if( $arg{debug} == 1 );
281 0         0 next;
282             }
283             }else{
284 0 0       0 warn "$mx misbehaving while retrieving banner\n" if( $arg{debug} == 1 );
285 0         0 $misbehave=1;
286 0         0 next;
287             }
288              
289 1         9 my $me = hostname_long();
290 1         36315 print $sock "HELO $me\r\n";
291 1         30 my @helo = _getlines($select,$arg{timeout});
292 1 50       13 if(@helo){
293 1 50       21 if($arg{debug} == 1){
294 0         0 print STDERR "HELO: ";
295 0         0 print STDERR for(@helo);
296 0         0 print STDERR "\n";
297             }
298 1 50       23 unless($helo[-1] =~ /^250\s/){
299 0         0 print $sock "QUIT\r\n"; # be nice
300 0         0 close $sock;
301 0         0 $misbehave=1;
302 0 0       0 warn "$mx misbehaving: bad reply to HELO\n" if( $arg{debug} == 1 );
303 0         0 next;
304             }
305             }else{
306 0 0       0 warn "$mx misbehaving while retrieving helo\n" if( $arg{debug} == 1 );
307 0         0 $misbehave=1;
308 0         0 next;
309             }
310              
311 1         96 print $sock "MAIL FROM:<$arg{from}>\r\n";
312 1         10 my @mf = _getlines($select,$arg{timeout});
313 1 50       32 if(@mf){
314 1 50       13 if($arg{debug} == 1){
315 0         0 print STDERR "MAIL FROM: ";
316 0         0 print STDERR for(@mf);
317 0         0 print STDERR "\n";
318             }
319 1 50       27 unless($mf[-1] =~ /^250\s/){
320 0         0 print $sock "QUIT\r\n"; # be nice
321 0         0 close $sock;
322 0         0 $misbehave=1;
323 0 0       0 warn "$mx misbehaving: bad reply to MAIL FROM\n" if( $arg{debug} == 1 );
324 0         0 next;
325             }
326             }else{
327 0 0       0 warn "$mx misbehaving while retrieving mail from\n" if( $arg{debug} == 1 );
328 0         0 $misbehave=1;
329 0         0 next;
330             }
331              
332 1         99 print $sock "RCPT TO:<$arg{addr}>\r\n";
333 1         12 my @rt = _getlines($select,$arg{timeout});
334 1         80 print $sock "QUIT\r\n"; # be nice
335 1         63 close $sock;
336 1 50       10 if(@rt){
337 1 50       10 if($arg{debug} == 1){
338 0         0 print STDERR "RECIPIENT TO: ";
339 0         0 print STDERR for(@rt);
340 0         0 print STDERR "\n";
341             }
342 1 50       10 if($rt[-1] =~ /^250\s/){
    0          
    0          
343             # host accepted
344 1         116 return 0;
345             }elsif($rt[-1] =~ /^4\d{2}/){
346             # host temp failed, greylisters go here.
347 0         0 $tmpfail=1;
348             }elsif($rt[-1] =~ /^5\d{2}/){
349             # host rejected
350 0         0 return 7;
351             }else{
352 0         0 $misbehave=1;
353 0 0       0 warn "$mx misbehaving: bad reply to RCPT TO\n" if( $arg{debug} == 1 );
354 0         0 next;
355             }
356             }else{
357 0         0 $misbehave=1;
358 0 0       0 warn "$mx not behaving correcly while retrieving rcpt to\n" if( $arg{debug} == 1 );
359 0         0 next;
360             }
361             }
362             }
363 0 0       0 return 4 unless($livesmtp);
364 0 0 0     0 return 5 if($misbehave && !$tmpfail);
365 0 0       0 return 6 if($tmpfail);
366 0         0 return 0;
367             }
368              
369             sub _getlines {
370 4   50 4   32 my $select = shift || die "_getlines syntax error 1";
371 4   50     14 my $timeout = shift || die "_getlines syntax error 2";
372 4         8 my @lines;
373 4 50       32 if(my ($pending) = $select->can_read($timeout)){
374 4         313257 while(<$pending>){
375 4 50       59 if(/^\d+\s/){
376 4         17 chomp;
377 4         21 push @lines, $_;
378 4         11 last;
379             }else{
380 0         0 push @lines, $_;
381             }
382             }
383             }
384 4         22 return(@lines);
385             }
386              
387             1;