line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Net::SMTP::Verify; |
2
|
|
|
|
|
|
|
|
3
|
3
|
|
|
3
|
|
12224
|
use Moose; |
|
3
|
|
|
|
|
950652
|
|
|
3
|
|
|
|
|
65
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
our $VERSION = '1.03'; # VERSION |
6
|
|
|
|
|
|
|
# ABSTRACT: verify SMTP recipient addresses |
7
|
|
|
|
|
|
|
|
8
|
3
|
|
|
3
|
|
27667
|
use Net::SMTP::Verify::ResultSet; |
|
3
|
|
|
|
|
11
|
|
|
3
|
|
|
|
|
112
|
|
9
|
|
|
|
|
|
|
|
10
|
3
|
|
|
3
|
|
1103
|
use Net::DNS::Resolver; |
|
3
|
|
|
|
|
85086
|
|
|
3
|
|
|
|
|
106
|
|
11
|
3
|
|
|
3
|
|
2977
|
use Net::SMTP; |
|
3
|
|
|
|
|
257375
|
|
|
3
|
|
|
|
|
200
|
|
12
|
3
|
|
|
3
|
|
24
|
use Net::Cmd qw( CMD_OK ); |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
178
|
|
13
|
3
|
|
|
3
|
|
1535
|
use Sys::Hostname; |
|
3
|
|
|
|
|
2413
|
|
|
3
|
|
|
|
|
179
|
|
14
|
3
|
|
|
3
|
|
2507
|
use Digest::SHA qw(sha224_hex); |
|
3
|
|
|
|
|
8767
|
|
|
3
|
|
|
|
|
6048
|
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
has 'host' => ( is => 'rw', isa => 'Maybe[Str]' ); |
18
|
|
|
|
|
|
|
has 'port' => ( is => 'rw', isa => 'Int', default => 25 ); |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
has 'helo_name' => ( |
21
|
|
|
|
|
|
|
is => 'rw', isa => 'Str', lazy => 1, |
22
|
|
|
|
|
|
|
default => sub { Sys::Hostname::hostname }, |
23
|
|
|
|
|
|
|
); |
24
|
|
|
|
|
|
|
has 'timeout' => ( is => 'rw', isa => 'Int', default => 30 ); |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
has 'resolver' => ( |
27
|
|
|
|
|
|
|
is => 'rw', isa => 'Net::DNS::Resolver', lazy => 1, |
28
|
|
|
|
|
|
|
default => sub { |
29
|
|
|
|
|
|
|
Net::DNS::Resolver->new( |
30
|
|
|
|
|
|
|
dnssec => 1, |
31
|
|
|
|
|
|
|
adflag => 1, |
32
|
|
|
|
|
|
|
); |
33
|
|
|
|
|
|
|
}, |
34
|
|
|
|
|
|
|
); |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
has 'tlsa' => ( is => 'rw', isa => 'Bool', default => 0 ); |
37
|
|
|
|
|
|
|
has 'openpgpkey' => ( is => 'rw', isa => 'Bool', default => 0 ); |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
has 'logging_callback' => ( |
40
|
|
|
|
|
|
|
is => 'rw', isa => 'CodeRef', lazy => 1, |
41
|
|
|
|
|
|
|
traits => [ 'Code' ], |
42
|
|
|
|
|
|
|
handles => { |
43
|
|
|
|
|
|
|
log => 'execute', |
44
|
|
|
|
|
|
|
}, |
45
|
|
|
|
|
|
|
default => sub { sub {} }, |
46
|
|
|
|
|
|
|
); |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
has 'debug' => ( is => 'ro', isa => 'Bool', default => 0 ); |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
sub BUILD { |
51
|
3
|
|
|
3
|
0
|
13913
|
my $self = shift; |
52
|
3
|
50
|
|
|
|
110
|
if( $self->debug ) { |
53
|
|
|
|
|
|
|
$self->logging_callback( sub { |
54
|
0
|
|
|
0
|
|
0
|
print STDERR shift."\n"; |
55
|
0
|
|
|
|
|
0
|
} ); |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
} |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
has '_known_hosts' => ( |
60
|
|
|
|
|
|
|
is => 'ro', isa => 'ArrayRef', lazy => 1, |
61
|
|
|
|
|
|
|
default => sub { [] }, |
62
|
|
|
|
|
|
|
traits => [ 'Array' ], |
63
|
|
|
|
|
|
|
handles => { |
64
|
|
|
|
|
|
|
'_reset_known_hosts' => 'clear', |
65
|
|
|
|
|
|
|
'_add_known_host' => 'push', |
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
); |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
sub _is_known_host { |
70
|
1
|
|
|
1
|
|
84
|
my ( $self, $host ) = @_; |
71
|
1
|
50
|
|
|
|
3
|
if( grep { $_ eq $host } @{$self->_known_hosts} ) { |
|
0
|
|
|
|
|
0
|
|
|
1
|
|
|
|
|
44
|
|
72
|
0
|
|
|
|
|
0
|
return 1; |
73
|
|
|
|
|
|
|
} |
74
|
1
|
|
|
|
|
3
|
return 0; |
75
|
|
|
|
|
|
|
} |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
sub resolve { |
79
|
5
|
|
|
5
|
1
|
527
|
my ( $self, $domain ) = @_; |
80
|
|
|
|
|
|
|
|
81
|
5
|
100
|
|
|
|
193
|
if( defined $self->host ) { |
82
|
4
|
|
|
|
|
128
|
return $self->host; |
83
|
|
|
|
|
|
|
} else { |
84
|
1
|
|
|
|
|
48
|
$self->log('looking up MX for '.$domain.'...'); |
85
|
1
|
|
|
|
|
36
|
my $reply = $self->resolver->query( $domain, 'MX' ); |
86
|
1
|
50
|
|
|
|
9574
|
if( $reply->answer ) { |
87
|
1
|
|
|
|
|
13
|
my @mx = grep { $_->type eq 'MX' } $reply->answer; |
|
1
|
|
|
|
|
11
|
|
88
|
1
|
|
|
|
|
15
|
@mx = sort { $a->preference <=> $b->preference } @mx; |
|
0
|
|
|
|
|
0
|
|
89
|
1
|
|
|
|
|
3
|
my @known_hosts = grep { $self->_is_known_host($_->exchange) } @mx; |
|
1
|
|
|
|
|
5
|
|
90
|
|
|
|
|
|
|
|
91
|
1
|
|
|
|
|
2
|
my $ex; |
92
|
1
|
50
|
|
|
|
5
|
if( @known_hosts ) { |
93
|
0
|
|
|
|
|
0
|
$ex = $known_hosts[0]->exchange; |
94
|
|
|
|
|
|
|
} else { |
95
|
1
|
|
|
|
|
4
|
$ex = $mx[0]->exchange; |
96
|
1
|
|
|
|
|
61
|
$self->_add_known_host( $ex ); |
97
|
|
|
|
|
|
|
} |
98
|
1
|
50
|
|
|
|
49
|
$self->log('found '.scalar(@mx).' records. using: '.$ex. |
99
|
|
|
|
|
|
|
( @known_hosts ? ' (reuse)' : '') ); |
100
|
1
|
|
|
|
|
19
|
return $ex; |
101
|
|
|
|
|
|
|
} |
102
|
0
|
|
|
|
|
0
|
$self->log('looking up AAAA,A for '.$domain.'...'); |
103
|
0
|
|
|
|
|
0
|
$reply = $self->resolver->query( $domain, 'AAAA', 'A' ); |
104
|
0
|
0
|
|
|
|
0
|
if( my @rr = $reply->answer ) { |
105
|
0
|
|
|
|
|
0
|
$self->log('found '.scalar(@rr).' address records'); |
106
|
0
|
|
|
|
|
0
|
return $domain; |
107
|
|
|
|
|
|
|
} |
108
|
0
|
|
|
|
|
0
|
$self->log('unable to resolve domain '.$domain); |
109
|
0
|
|
|
|
|
0
|
return; # lookup failed |
110
|
|
|
|
|
|
|
} |
111
|
|
|
|
|
|
|
|
112
|
0
|
|
|
|
|
0
|
die('unknown mode: '.$self->mode); |
113
|
0
|
|
|
|
|
0
|
return; |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
sub check_tlsa { |
118
|
1
|
|
|
1
|
1
|
537
|
my ( $self, $host, $port ) = @_; |
119
|
1
|
50
|
|
|
|
8
|
if( ! defined $port ) { |
120
|
0
|
|
|
|
|
0
|
$port = 25; |
121
|
|
|
|
|
|
|
} |
122
|
1
|
|
|
|
|
6
|
my $tlsa_name = '_'.$port.'._tcp.'.$host; |
123
|
1
|
|
|
|
|
50
|
$self->log('looking up TLSA for '.$tlsa_name.'...'); |
124
|
1
|
|
|
|
|
34
|
my $reply = $self->resolver->send( $tlsa_name, 'TLSA' ); |
125
|
|
|
|
|
|
|
|
126
|
1
|
50
|
|
|
|
86040
|
if( ! $reply->header->ad ) { |
127
|
0
|
|
|
|
|
0
|
$self->log('no adflag set in response'); |
128
|
0
|
|
|
|
|
0
|
return 0; |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
|
131
|
1
|
50
|
|
|
|
23
|
if( ! $reply->answer ) { |
132
|
0
|
|
|
|
|
0
|
$self->log('no TLSA record published'); |
133
|
0
|
|
|
|
|
0
|
return 0; |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
1
|
|
|
|
|
48
|
return 1; |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
sub check_openpgpkey { |
140
|
0
|
|
|
0
|
0
|
0
|
my ( $self, $rs, @rcpts ) = @_; |
141
|
|
|
|
|
|
|
|
142
|
0
|
|
|
|
|
0
|
foreach my $rcpt ( @rcpts ) { |
143
|
0
|
|
|
|
|
0
|
my ( $local, $domain ) = split('@', $rcpt, 2); |
144
|
0
|
|
|
|
|
0
|
my $name = join('.', sha224_hex($local), '_openpgpkey', $domain); |
145
|
0
|
|
|
|
|
0
|
$self->log('looking up OPENPGPKEY: '.$name.'...'); |
146
|
0
|
|
|
|
|
0
|
my $reply = $self->resolver->send( $name, 'TYPE61' ); |
147
|
0
|
0
|
|
|
|
0
|
if( ! $reply->header->ad ) { |
|
|
0
|
|
|
|
|
|
148
|
0
|
|
|
|
|
0
|
$self->log('no adflag set in response'); |
149
|
0
|
|
|
|
|
0
|
$rs->set( $rcpt, 'has_openpgpkey', 0 ); |
150
|
|
|
|
|
|
|
} elsif( ! $reply->answer ) { |
151
|
0
|
|
|
|
|
0
|
$self->log('no OPENPGPKEY record found'); |
152
|
0
|
|
|
|
|
0
|
$rs->set( $rcpt, 'has_openpgpkey', 0 ); |
153
|
|
|
|
|
|
|
} else { |
154
|
0
|
|
|
|
|
0
|
$self->log('OPENPGPKEY record found'); |
155
|
0
|
|
|
|
|
0
|
$rs->set( $rcpt, 'has_openpgpkey', 1 ); |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
} |
158
|
|
|
|
|
|
|
|
159
|
0
|
|
|
|
|
0
|
return; |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
sub check_smtp { |
163
|
4
|
|
|
4
|
0
|
11
|
my ( $self, $rs, $host, $size, $sender, @rcpts ) = @_; |
164
|
|
|
|
|
|
|
|
165
|
4
|
|
|
|
|
190
|
$self->log('connecting to '.$host.'...'); |
166
|
4
|
|
|
|
|
1029
|
my $smtp = Net::SMTP->new( $host, |
167
|
|
|
|
|
|
|
Port => $self->port, |
168
|
|
|
|
|
|
|
Hello => $self->helo_name, |
169
|
|
|
|
|
|
|
Timeout => $self->timeout, |
170
|
|
|
|
|
|
|
); |
171
|
4
|
50
|
|
|
|
257811
|
if( ! defined $smtp ) { |
172
|
0
|
|
|
|
|
0
|
$self->log('connection failed: '.$@); |
173
|
0
|
|
|
|
|
0
|
$rs->set( \@rcpts, 'error', 'connection failed: '.$@ ); |
174
|
0
|
|
|
|
|
0
|
return; |
175
|
|
|
|
|
|
|
} |
176
|
|
|
|
|
|
|
|
177
|
4
|
50
|
|
|
|
32
|
$rs->set( \@rcpts, 'has_starttls', |
178
|
|
|
|
|
|
|
defined $smtp->supports('STARTTLS') ? 1 : 0 ); |
179
|
|
|
|
|
|
|
|
180
|
4
|
100
|
|
|
|
30
|
if( defined $smtp->supports('PIPELINING') ) { |
181
|
3
|
|
|
|
|
81
|
$self->check_smtp_addresses_pipelining( $rs, $smtp, $size, $sender, @rcpts ); |
182
|
|
|
|
|
|
|
} else { |
183
|
1
|
|
|
|
|
26
|
$self->check_smtp_addresses( $rs, $smtp, $size, $sender, @rcpts ); |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
4
|
|
|
|
|
229
|
$self->log('sending QUIT...'); |
187
|
4
|
|
|
|
|
677
|
$smtp->quit; |
188
|
4
|
|
|
|
|
5758
|
return; |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
sub check_smtp_addresses { |
192
|
1
|
|
|
1
|
0
|
6
|
my ( $self, $rs, $smtp, $size, $sender, @rcpts ) = @_; |
193
|
1
|
|
|
|
|
77
|
$self->log('sending MAIL '.$sender.'...'); |
194
|
1
|
50
|
33
|
|
|
379
|
my $mail_ok = $smtp->mail( $sender, |
195
|
|
|
|
|
|
|
defined $size && $smtp->supports('SIZE') ? ( Size => $size ):() |
196
|
|
|
|
|
|
|
); |
197
|
1
|
|
|
|
|
1250
|
my $msg = $smtp->message; chomp($msg); |
|
1
|
|
|
|
|
22
|
|
198
|
1
|
|
|
|
|
129
|
$self->log('server said: '.$msg); |
199
|
1
|
50
|
|
|
|
274
|
if( ! $mail_ok ) { |
200
|
0
|
|
|
|
|
0
|
$rs->set( \@rcpts, 'smtp_message', $msg ); |
201
|
0
|
|
|
|
|
0
|
$rs->set( \@rcpts, 'smtp_code', $smtp->code ); |
202
|
0
|
|
|
|
|
0
|
return; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
1
|
|
|
|
|
6
|
foreach my $rcpt ( @rcpts ) { |
206
|
3
|
|
|
|
|
198
|
$self->log('sending RCPT '.$rcpt.'...'); |
207
|
3
|
|
|
|
|
543
|
my $rcpt_ok = $smtp->recipient( $rcpt ); |
208
|
3
|
|
|
|
|
2043
|
my $msg = $smtp->message; chomp( $msg ); |
|
3
|
|
|
|
|
35
|
|
209
|
3
|
|
|
|
|
184
|
$self->log( 'server said: '.$msg ); |
210
|
3
|
|
|
|
|
508
|
$rs->set( $rcpt, 'smtp_message', $msg ); |
211
|
3
|
|
|
|
|
80
|
$rs->set( $rcpt, 'smtp_code', $smtp->code ); |
212
|
|
|
|
|
|
|
} |
213
|
1
|
|
|
|
|
4
|
return; |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
has 'rcpt_bulk_size' => ( is => 'ro', isa => 'Int', default => 10 ); |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
sub check_smtp_addresses_pipelining { |
219
|
3
|
|
|
3
|
0
|
19
|
my ( $self, $rs, $smtp, $size, $sender, @rcpts ) = @_; |
220
|
3
|
|
|
|
|
10
|
my $mail_sent = 0; |
221
|
|
|
|
|
|
|
|
222
|
3
|
|
|
|
|
172
|
while( my @bulk_rcpts = splice(@rcpts, 0, $self->rcpt_bulk_size) ) { |
223
|
3
|
|
|
|
|
202
|
$self->log('sending pipelined bulk...'); |
224
|
3
|
|
|
|
|
916
|
my $bulk = ''; |
225
|
3
|
50
|
|
|
|
15
|
if( ! $mail_sent ) { |
226
|
3
|
50
|
33
|
|
|
87
|
$bulk .= 'MAIL FROM: <'.$sender.'>' |
227
|
|
|
|
|
|
|
.( defined $size && $smtp->supports('SIZE') ? ' SIZE='.$size : '' ) |
228
|
|
|
|
|
|
|
."\n" |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
$bulk .= join("\n", |
231
|
3
|
|
|
|
|
75
|
map { 'RCPT TO: <'.$_.'>' } @bulk_rcpts, |
|
9
|
|
|
|
|
52
|
|
232
|
|
|
|
|
|
|
)."\n"; |
233
|
|
|
|
|
|
|
|
234
|
3
|
|
|
|
|
99
|
$smtp->datasend( $bulk ); |
235
|
|
|
|
|
|
|
|
236
|
3
|
50
|
|
|
|
3906
|
if( ! $mail_sent ) { |
237
|
3
|
|
|
|
|
23
|
my $resp = $smtp->response; |
238
|
3
|
|
|
|
|
546
|
my $msg = $smtp->message; chomp( $msg ); |
|
3
|
|
|
|
|
51
|
|
239
|
3
|
|
|
|
|
253
|
$self->log("server response to MAIL: ".$msg ); |
240
|
3
|
100
|
|
|
|
642
|
if( $resp != CMD_OK ) { |
241
|
1
|
|
|
|
|
11
|
$rs->set( [ @bulk_rcpts, @rcpts ], 'smtp_code', $smtp->code ); |
242
|
1
|
|
|
|
|
6
|
$rs->set( [ @bulk_rcpts, @rcpts ], 'smtp_message', $msg ); |
243
|
1
|
|
|
|
|
4
|
return; |
244
|
|
|
|
|
|
|
} |
245
|
2
|
|
|
|
|
5
|
$mail_sent = 1; |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
|
248
|
2
|
|
|
|
|
22
|
foreach my $rcpt ( @bulk_rcpts ) { |
249
|
6
|
|
|
|
|
33
|
$smtp->response; |
250
|
6
|
|
|
|
|
75130
|
my $msg = $smtp->message; chomp( $msg ); |
|
6
|
|
|
|
|
96
|
|
251
|
6
|
|
|
|
|
486
|
$self->log("server response to RCPT $rcpt: ".$msg ); |
252
|
6
|
|
|
|
|
1432
|
$rs->set( $rcpt, 'smtp_code', $smtp->code ); |
253
|
6
|
|
|
|
|
39
|
$rs->set( $rcpt, 'smtp_message', $msg ); |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
} |
256
|
2
|
|
|
|
|
11
|
return; |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
sub check { |
261
|
4
|
|
|
4
|
1
|
852
|
my ( $self, $size, $sender, @rcpts ) = @_; |
262
|
4
|
|
|
|
|
68
|
my $rs = Net::SMTP::Verify::ResultSet->new; |
263
|
|
|
|
|
|
|
|
264
|
4
|
|
|
|
|
1425
|
my $by_domain = {}; |
265
|
4
|
|
|
|
|
22
|
foreach my $rcpt ( @rcpts ) { |
266
|
12
|
|
|
|
|
61
|
my ( $user, $domain ) = split('@', $rcpt, 2); |
267
|
12
|
100
|
|
|
|
70
|
if( ! defined $by_domain->{$domain} ) { |
268
|
4
|
|
|
|
|
19
|
$by_domain->{$domain} = []; |
269
|
|
|
|
|
|
|
} |
270
|
12
|
|
|
|
|
16
|
push( @{$by_domain->{$domain}}, $rcpt ); |
|
12
|
|
|
|
|
45
|
|
271
|
|
|
|
|
|
|
} |
272
|
|
|
|
|
|
|
|
273
|
4
|
|
|
|
|
7
|
my $by_host = {}; |
274
|
4
|
|
|
|
|
206
|
$self->_reset_known_hosts; |
275
|
4
|
|
|
|
|
17
|
foreach my $domain ( keys %$by_domain ) { |
276
|
4
|
|
|
|
|
43
|
my $host = $self->resolve( $domain ); |
277
|
4
|
50
|
|
|
|
21
|
if( ! defined $host ) { |
278
|
0
|
|
|
|
|
0
|
$rs->set( $by_domain->{$domain}, |
279
|
|
|
|
|
|
|
'error', 'unable to lookup '.$domain ); |
280
|
0
|
|
|
|
|
0
|
return; |
281
|
|
|
|
|
|
|
} |
282
|
4
|
50
|
|
|
|
16
|
if( ! defined $by_host->{$host} ) { |
283
|
4
|
|
|
|
|
11
|
$by_host->{$host} = []; |
284
|
|
|
|
|
|
|
} |
285
|
4
|
|
|
|
|
8
|
push( @{$by_host->{$host}}, @{$by_domain->{$domain}} ); |
|
4
|
|
|
|
|
11
|
|
|
4
|
|
|
|
|
36
|
|
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
|
288
|
4
|
|
|
|
|
13
|
foreach my $host ( keys %$by_host ) { |
289
|
4
|
50
|
|
|
|
161
|
if( $self->tlsa ) { |
290
|
0
|
|
|
|
|
0
|
$rs->set( $by_host->{$host}, |
291
|
|
|
|
|
|
|
'has_tlsa', $self->check_tlsa( $host ) ); |
292
|
|
|
|
|
|
|
} |
293
|
4
|
|
|
|
|
10
|
$self->check_smtp( $rs, $host, $size, $sender, @{$by_host->{$host}} ); |
|
4
|
|
|
|
|
34
|
|
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
|
296
|
4
|
50
|
|
|
|
255
|
if( $self->openpgpkey ) { |
297
|
0
|
|
|
|
|
0
|
$self->check_openpgpkey( $rs, @rcpts ); |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
|
300
|
4
|
|
|
|
|
78
|
return $rs; |
301
|
|
|
|
|
|
|
} |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
1; |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
__END__ |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
=pod |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
=encoding UTF-8 |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
=head1 NAME |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
Net::SMTP::Verify - verify SMTP recipient addresses |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
=head1 VERSION |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
version 1.03 |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
=head1 SYNOPSIS |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
use Net::SMTP::Verify; |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
my $v = Net::SMTP::Verify->new; |
324
|
|
|
|
|
|
|
my $resultset = $v->check( |
325
|
|
|
|
|
|
|
100000, # size |
326
|
|
|
|
|
|
|
'karl@senderdomain.de', # sender |
327
|
|
|
|
|
|
|
'rcpt1@rcptdomain.de', # 1 or more recipients... |
328
|
|
|
|
|
|
|
'rcpt2@rcptdomain.de', |
329
|
|
|
|
|
|
|
'rcpt3@rcptdomain.de', |
330
|
|
|
|
|
|
|
); |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
# check overall status |
333
|
|
|
|
|
|
|
$resultset->is_all_success; |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
# check a single result |
336
|
|
|
|
|
|
|
$resultset->rcpt('rcpt1@rcptdomain.de')->is_success; |
337
|
|
|
|
|
|
|
$resultset->rcpt('rcpt1@rcptdomain.de')->smtp_code; |
338
|
|
|
|
|
|
|
$resultset->rcpt('rcpt1@rcptdomain.de')->smtp_message; |
339
|
|
|
|
|
|
|
$resultset->rcpt('rcpt1@rcptdomain.de')->has_starttls; |
340
|
|
|
|
|
|
|
$resultset->rcpt('rcpt1@rcptdomain.de')->has_tlsa; |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
# more ways to retrieve results by status... |
343
|
|
|
|
|
|
|
$resultset->successfull_rcpts; |
344
|
|
|
|
|
|
|
$resultset->error_rcpts; |
345
|
|
|
|
|
|
|
$resultset->temp_error_rcpts; |
346
|
|
|
|
|
|
|
$resultset->perm_error_rcpts; |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
=head1 DESCRIPTION |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
This class implements checks for verifying SMTP addresses. |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
It implements the following checks: |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
=over |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
=item check addresses with SMTP MAIL FROM and RCPT TO commands |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
Check if the MX would accept mail for test addresses. |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
=item check of message size |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
If the mail exchanger (MX) supports the SIZE extension and a size is given the |
363
|
|
|
|
|
|
|
module will pass the message size with the MAIL FROM command. |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
This will check if the message would exceed message size limits or recipients |
366
|
|
|
|
|
|
|
quotas on the target MX. |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
=item check if MX could handle TLS connections |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
It will check if the STARTTLS extension required to enstablish encrypted TLS |
371
|
|
|
|
|
|
|
connections is supported by the target MX. |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
=item check if TLSA record is available |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
The module could check if a TLSA record has been published for the target MX |
376
|
|
|
|
|
|
|
server. |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
If such a record has been published the target MX SSL certificate could be |
379
|
|
|
|
|
|
|
verified with DANE. |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
=back |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
=head2 host (default: undef) |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
Query this smtp server instead of the MX records. |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
=head2 port (default: 25) |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
Use a different port. |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
=head2 helo_name (default: hostname() ) |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
Use a helo_name other than the hostname of the system. |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
=head2 timeout (default: 30) |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
Use this timeout for the SMTP connection. |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
=head2 resolver (default: system resolver) |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
Use a custom Net::DNS::Resolver object. |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
The default is: |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
Net::DNS::Resolver->new( |
408
|
|
|
|
|
|
|
dnssec => 1, |
409
|
|
|
|
|
|
|
adflag => 1, |
410
|
|
|
|
|
|
|
); |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
The dnssec and adflag is required for the TLSA check. |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
=head2 tlsa (default: 0) |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
Set to 1 to activate TLSA lookup. |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
=head2 openpgpkey (default: 0) |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
Set to 1 to activate OPENPGPKEY lookup. |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
=head2 logging_callback (default: sub {}) |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
Set a callback to retrieve log messages. |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
=head2 debug (default: 0) |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
If set to 1 it will set a logging_callback method to output |
429
|
|
|
|
|
|
|
logs to STDERR. |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
=head1 METHODS |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
=head2 resolve( $domain ) |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
Tries to resolve a MX to an hostname. |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
It will choose the first record with the highest priority listed as MX. |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
When a host is MX for multiple domains it will try to reuse the same |
440
|
|
|
|
|
|
|
host for checks. |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
=head2 check_tlsa( $host, $port ) |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
Check if a TLSA record is available. |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
=head2 check( $size, $sender, $rcpt1, $rcpts...) |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
Performs check and returns a Net::SMTP::Verify::ResultSet. |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
=head1 AUTHOR |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
Markus Benning <ich@markusbenning.de> |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
This software is Copyright (c) 2015 by Markus Benning <ich@markusbenning.de>. |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
This is free software, licensed under: |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
The GNU General Public License, Version 2, June 1991 |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
=cut |