Commit | Line | Data |
---|---|---|
ba787c2d | 1 | #! /usr/bin/perl -w |
0bd5c08c | 2 | # |
ba787c2d | 3 | # checkmail Version 0.6.1 by Thomas Hochstein |
431fbb12 TH |
4 | # |
5 | # This script tries to verify the deliverability of (a) mail address(es). | |
6 | # | |
422d9071 | 7 | # Copyright (c) 2002-2011 Thomas Hochstein <thh@inter.net> |
0bd5c08c | 8 | # |
431fbb12 TH |
9 | # It can be redistributed and/or modified under the same terms under |
10 | # which Perl itself is published. | |
11 | ||
240c1264 | 12 | our $VERSION = "0.6.2"; |
431fbb12 TH |
13 | |
14 | ################################# Configuration ################################ | |
15 | # Please fill in a working configuration! | |
16 | my %config=( | |
17 | # value used for HELO/EHLO - a valid hostname you own | |
18 | helo => 'testhost.domain.example', | |
19 | # value used for MAIL FROM: - a valid address under your control | |
9fc0e927 | 20 | from => 'mailtest@testhost.domain.example' |
431fbb12 TH |
21 | ); |
22 | ||
23 | ################################### Modules #################################### | |
24 | use strict; | |
25 | use File::Basename; | |
0bd5c08c | 26 | use Getopt::Std; |
fd1fe450 | 27 | use Mail::Address; |
0bd5c08c TH |
28 | use Net::DNS; |
29 | use Net::SMTP; | |
30 | ||
431fbb12 TH |
31 | ################################# Main program ################################# |
32 | ||
33 | $Getopt::Std::STANDARD_HELP_VERSION = 1; | |
34 | my $myself = basename($0); | |
35 | ||
36 | # read commandline options | |
0bd5c08c | 37 | my %options; |
2121a934 | 38 | getopts('Vhqlrf:m:s:e:', \%options); |
431fbb12 TH |
39 | |
40 | # -V: display version | |
41 | if ($options{'V'}) { | |
240c1264 | 42 | print "$myself v $VERSION\nCopyright (c) 2010-2016 Thomas Hochstein <thh\@inter.net>\n"; |
431fbb12 TH |
43 | print "This program is free software; you may redistribute it and/or modify it under the same terms as Perl itself.\n"; |
44 | exit(100); | |
45 | }; | |
46 | ||
47 | # -h: feed myself to perldoc | |
48 | if ($options{'h'}) { | |
49 | exec('perldoc', $0); | |
50 | exit(100); | |
51 | }; | |
52 | ||
53 | # display usage information if neither -f nor an address are present | |
54 | if (!$options{'f'} and !$ARGV[0]) { | |
2121a934 | 55 | print "Usage: $myself [-hqlr] [-m <host>] [-s <from>] [-e <EHLO>] <address>|-f <file>\n"; |
431fbb12 TH |
56 | print "Options: -V display copyright and version\n"; |
57 | print " -h show documentation\n"; | |
58 | print " -q quiet (no output, just exit with 0/1/2/3)\n"; | |
59 | print " -l extended logging\n"; | |
60 | print " -r test random address to verify verification\n"; | |
61 | print " -m <host> no DNS lookup, just test this host\n"; | |
2121a934 TH |
62 | print " -s <from> override configured value for MAIL FROM\n"; |
63 | print " -e <EHLO> override configured value for EHLO\n"; | |
431fbb12 TH |
64 | print " <address> mail address to check\n\n"; |
65 | print " -f <file> parse file (one address per line)\n"; | |
66 | exit(100); | |
0bd5c08c TH |
67 | }; |
68 | ||
2121a934 | 69 | # -s / -e: override configuration |
240c1264 | 70 | $config{'from'} = $options{'s'} if defined($options{'s'}); |
2121a934 TH |
71 | $config{'helo'} = $options{'e'} if $options{'e'}; |
72 | ||
431fbb12 TH |
73 | # -f: open file and read addresses to @adresses |
74 | my @addresses; | |
0bd5c08c TH |
75 | if ($options{'f'}) { |
76 | if (-e $options{'f'}) { | |
431fbb12 | 77 | open FILE, "<$options{'f'}" or die("$myself ERROR: Could not open file $options{'f'} for reading: $!"); |
0bd5c08c | 78 | } else { |
431fbb12 | 79 | die("$myself ERROR: File $options{'f'} does not exist!\n"); |
0bd5c08c | 80 | }; |
0bd5c08c TH |
81 | while(<FILE>) { |
82 | chomp; | |
431fbb12 | 83 | push(@addresses,$_); |
0bd5c08c TH |
84 | }; |
85 | close FILE; | |
431fbb12 TH |
86 | # fill @adresses with single address to check |
87 | } else { | |
88 | push(@addresses,$ARGV[0]); | |
89 | }; | |
90 | ||
91 | # loop over each address and test it | |
92 | my (%targets,$curstat,$status,$log,$message); | |
93 | foreach (@addresses) { | |
94 | my $address = $_; | |
ea5d225a | 95 | # regexp taken from http://www.regular-expressions.info/email.html |
5327436f TH |
96 | # with escaping of "/" added two times and "*" changed to "+" |
97 | # in localpart, second alternative | |
98 | if ($address !~ /^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f]+)")@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i) { | |
ea5d225a | 99 | printf(" > Address <%s> is syntactically INVALID.\n",$address) if !($options{'q'}); |
431fbb12 | 100 | $curstat = 2; |
ea5d225a TH |
101 | } else { |
102 | my $domain = Mail::Address->new('',$address)->host; | |
103 | printf(" * Testing %s ...\n",$address) if !($options{'q'}); | |
104 | $log .= "\n===== BEGIN $address =====\n"; | |
105 | # get list of target hosts or take host forced via -m | |
106 | if (!$options{'m'}) { | |
107 | %targets = %{gettargets($domain,\$log)}; | |
108 | } else { | |
109 | $message = sprintf("Connection to %s forced by -m.\n",$options{'m'}); | |
110 | $log .= $message; | |
111 | print " $message" if !($options{'q'}); | |
112 | # just one target host with preference 0 | |
113 | $targets{$options{'m'}} = 0; | |
114 | }; | |
115 | if (%targets) { | |
116 | $curstat = checkaddress($address,\%targets,\$log); | |
117 | } else { | |
118 | $curstat = 2; | |
119 | $message = 'DNS lookup failure'; | |
120 | printf(" > Address is INVALID (%s).\n",$message) if !($options{'q'}); | |
121 | $log .= $message . '.'; | |
122 | }; | |
123 | $log .= "====== END $address ======\n"; | |
431fbb12 | 124 | }; |
431fbb12 | 125 | $status = $curstat if (!defined($status) or $curstat > $status); |
0bd5c08c TH |
126 | }; |
127 | ||
128 | print $log if ($options{'l'}); | |
129 | ||
130 | # status 0: valid / batch processing | |
431fbb12 TH |
131 | # 1: connection failed or temporary failure |
132 | # 2: invalid | |
133 | # 3: cannot verify | |
134 | #D print "\n-> EXIT $status\n"; | |
0bd5c08c TH |
135 | exit($status); |
136 | ||
431fbb12 TH |
137 | ################################## gettargets ################################## |
138 | # get mail exchanger(s) or A record(s) for a domain | |
139 | # IN : $domain: domain to query the DNS for | |
140 | # OUT: \%targets: reference to a hash containing a list of target hosts | |
141 | sub gettargets { | |
142 | my ($domain,$logr) = @_; | |
143 | # resolver objekt | |
144 | my $resolver = Net::DNS::Resolver->new(udp_timeout => 15, tcp_timeout => 15); | |
0bd5c08c | 145 | |
431fbb12 TH |
146 | my %targets; |
147 | # get MX record(s) as a list sorted by preference | |
148 | if (my @mxrr = mx($resolver,$domain)) { | |
149 | print_dns_result($domain,'MX',scalar(@mxrr),undef,$logr); | |
150 | foreach my $rr (@mxrr) { | |
151 | $targets{$rr->exchange} = $rr->preference; | |
152 | $$logr .= sprintf("(%d) %s\n",$rr->preference,$rr->exchange); | |
153 | }; | |
154 | # no MX record found; log and try A record(s) | |
155 | } else { | |
156 | print_dns_result($domain,'MX',undef,$resolver->errorstring,$logr); | |
157 | print(" Falling back to A record ...\n") if !($options{'q'}); | |
2378aae8 TH |
158 | # get A record(s) |
159 | # may get CNAMEs instead ... | |
431fbb12 | 160 | if (my $query = $resolver->query($domain,'A','IN')) { |
2378aae8 TH |
161 | # save number of answers in a counter |
162 | my $acount = $query->header->ancount; | |
431fbb12 | 163 | foreach my $rr ($query->answer) { |
2378aae8 TH |
164 | if ($rr->type ne 'A') { |
165 | # decrease counter if it's not an A record | |
166 | $acount--; | |
167 | # report CNAMEs and don't add them to target list | |
168 | if ($rr->type eq 'CNAME') { | |
169 | printf (" ~ '%s' is a CNAME for '%s' and will be resolved accordingly. \n",$rr->name,$rr->cname) if !($options{'q'}); | |
170 | $$logr .= sprintf("CNAME resolved: %s -> %s\n",$rr->name,$rr->cname); | |
171 | } | |
172 | next; | |
173 | } | |
431fbb12 TH |
174 | $targets{$rr->address} = 0; |
175 | $$logr .= sprintf("- %s\n",$rr->address); | |
176 | }; | |
2378aae8 | 177 | print_dns_result($domain,'A',$acount,undef,$logr); |
431fbb12 TH |
178 | # no A record found either; log and fail |
179 | } else { | |
180 | print_dns_result($domain,'A',undef,$resolver->errorstring,$logr); | |
181 | printf(" %s has neither MX nor A records - mail cannot be delivered.\n",$domain) if !($options{'q'}); | |
182 | }; | |
0bd5c08c | 183 | }; |
431fbb12 TH |
184 | return \%targets; |
185 | }; | |
186 | ||
187 | ################################# checkaddress ################################# | |
188 | # test address for deliverability | |
189 | # IN : $address: adress to be tested | |
190 | # \%targets: reference to a hash containing a list of MX hosts | |
191 | # \$log : reference to the log (to be printed out via -l) | |
192 | # OUT: --- | |
193 | # \$log will be changed | |
194 | sub checkaddress { | |
195 | my ($address,$targetsr,$logr) = @_; | |
196 | my %targets = %{$targetsr}; | |
197 | my $status; | |
198 | # walk %targets in order of preference | |
199 | foreach my $host (sort { $targets{$a} <=> $targets{$b} } keys %targets) { | |
200 | printf(" / Trying %s (%s) with %s\n",$host,$targets{$host} || 'A',$address) if !($options{'q'}); | |
201 | $$logr .= sprintf("%s:\n%s\n",$host,"-" x (length($host)+1)); | |
202 | $status = checksmtp($address,$host,$logr); | |
203 | last if ($status != 1); | |
0bd5c08c | 204 | }; |
431fbb12 | 205 | return $status; |
0bd5c08c TH |
206 | }; |
207 | ||
431fbb12 TH |
208 | ################################### checksmtp ################################## |
209 | # connect to a remote machine on port 25 and test deliverability of a mail | |
210 | # address by doing the SMTP dialog until RCPT TO stage | |
211 | # IN : $address: address to test | |
212 | # $target : target host | |
213 | # \$log : reference to the log (to be printed out via -l) | |
214 | # OUT: .........: reference to a hash containing a list of target hosts | |
215 | # \$log will be changed | |
0bd5c08c | 216 | sub checksmtp { |
431fbb12 TH |
217 | my ($address,$target,$logr) = @_; |
218 | my ($status); | |
219 | # start SMTP connection | |
220 | if (my $smtp = Net::SMTP->new($target,Hello => $config{'helo'},Timeout => 30)) { | |
221 | $$logr .= $smtp->banner; # Net::SMTP doesn't seem to support multiline greetings. | |
222 | $$logr .= "EHLO $config{'helo'}\n"; | |
223 | log_smtp_reply($logr,$smtp->code,$smtp->message); | |
224 | $smtp->mail($config{'from'}); | |
225 | $$logr .= "MAIL FROM:<$config{'from'}>\n"; | |
226 | log_smtp_reply($logr,$smtp->code,$smtp->message); | |
227 | # test address | |
228 | my ($success,$code,@message) = try_rcpt_to(\$smtp,$address,$logr); | |
229 | # connection failure? | |
230 | if ($success < 0) { | |
f18dc26f | 231 | $status = connection_failed(@message); |
431fbb12 TH |
232 | # delivery attempt was successful? |
233 | } elsif ($success) { | |
234 | # -r: try random address (which should be guaranteed to be invalid) | |
235 | if ($options{'r'}) { | |
fd1fe450 | 236 | my ($success,$code,@message) = try_rcpt_to(\$smtp,create_rand_addr(Mail::Address->new('',$address)->host),$logr); |
431fbb12 TH |
237 | # connection failure? |
238 | if ($success < 0) { | |
f18dc26f | 239 | $status = connection_failed(@message); |
e8c5b7b2 TH |
240 | # reset status - the address has been checked and _is_ valid! |
241 | $status = 3; | |
242 | print " > Address verification currently impossible. You'll have to try again or send a test mail ...\n" if !($options{'q'}); | |
431fbb12 TH |
243 | # verification impossible? |
244 | } elsif ($success) { | |
245 | $status = 3; | |
382c5a59 | 246 | print " > Address verification impossible. You'll have to send a test mail ...\n" if !($options{'q'}); |
431fbb12 TH |
247 | } |
248 | } | |
249 | # if -r is not set or status was not set to 3: valid address | |
250 | if (!defined($status)) { | |
251 | $status = 0; | |
252 | print " > Address is valid.\n" if !($options{'q'}); | |
0bd5c08c | 253 | }; |
431fbb12 TH |
254 | # delivery attempt failed? |
255 | } else { | |
256 | $status = 2; | |
257 | print " > Address is INVALID:\n" if !($options{'q'}); | |
258 | print ' ' . join(' ',@message) if !($options{'q'}); | |
259 | } | |
260 | # terminate SMTP connection | |
261 | $smtp->quit; | |
262 | $$logr .= "QUIT\n"; | |
263 | log_smtp_reply($logr,$smtp->code,$smtp->message); | |
0bd5c08c | 264 | } else { |
431fbb12 TH |
265 | # SMTP connection failed / timeout |
266 | $status = connection_failed(); | |
267 | $$logr .= "---Connection failure---\n"; | |
0bd5c08c | 268 | }; |
431fbb12 TH |
269 | return $status; |
270 | } | |
271 | ||
9fc0e927 TH |
272 | ############################### create_rand_addr ############################### |
273 | # create a random mail address | |
274 | # IN : $domain: the domain part | |
275 | # OUT: $address: the address | |
276 | sub create_rand_addr { | |
277 | my($domain)=@_; | |
278 | my $allowed = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789-+_='; | |
279 | my $address = ''; | |
280 | while (length($address) < 15) { | |
281 | $address .= substr($allowed, (int(rand(length($allowed)))),1); | |
282 | }; | |
283 | return ($address.'@'.$domain); | |
284 | }; | |
285 | ||
431fbb12 TH |
286 | ################################ parse_dns_reply ############################### |
287 | # parse DNS response codes and return code and description | |
288 | # IN : $response: a DNS response code | |
289 | # OUT: "$response ($desciption)" | |
290 | sub parse_dns_reply { | |
291 | my($response)=@_; | |
292 | my %dnsrespcodes = (NOERROR => 'empty response', | |
293 | NXDOMAIN => 'non-existent domain', | |
294 | SERVFAIL => 'DNS server failure', | |
295 | REFUSED => 'DNS query refused', | |
296 | FORMERR => 'format error', | |
297 | NOTIMP => 'not implemented'); | |
298 | if(defined($dnsrespcodes{$response})) { | |
299 | return sprintf('%s (%s)',$response,$dnsrespcodes{$response}); | |
300 | } else { | |
301 | return $response; | |
302 | }; | |
303 | }; | |
304 | ||
305 | ############################### print_dns_result ############################### | |
306 | # print and log result of DNS query | |
307 | # IN : $domain: domain the DNS was queried for | |
308 | # $type : record type (MX, A, ...) | |
309 | # $count : number of records found | |
310 | # $error : DNS response code | |
311 | # \$log : reference to the log (to be printed out via -l) | |
312 | # OUT: --- | |
313 | # \$log will be changed | |
314 | sub print_dns_result { | |
315 | my ($domain,$type,$count,$error,$logr) = @_; | |
316 | if (defined($count)) { | |
317 | printf(" %d %s record(s) found for %s\n",$count,$type,$domain) if !($options{'q'}); | |
318 | $$logr .= sprintf("%s DNS record(s):\n",$type); | |
319 | } else { | |
320 | printf(" No %s records found for %s: %s\n",$type,$domain,parse_dns_reply($error)) if !($options{'q'}); | |
321 | $$logr .= sprintf("No %s records found: %s\n",$type,parse_dns_reply($error)); | |
322 | }; | |
323 | return; | |
0bd5c08c TH |
324 | }; |
325 | ||
431fbb12 TH |
326 | ################################## try_rcpt_to ################################# |
327 | # send RCPT TO and return replies | |
328 | # IN : \$smtp : a reference to an SMTP object | |
329 | # $recipient: a mail address | |
330 | # \$log : reference to the log (to be printed out via -l) | |
f18dc26f | 331 | # OUT: $success: exit code (0 for false, 1 for true, -1 for tempfail) |
431fbb12 TH |
332 | # $code : SMTP status code |
333 | # $message: SMTP status message | |
334 | # \$log will be changed | |
335 | sub try_rcpt_to { | |
336 | my($smtpr,$recipient,$logr)=@_; | |
337 | $$logr .= sprintf("RCPT TO:<%s>\n",$recipient); | |
f18dc26f TH |
338 | my $success; |
339 | $$smtpr->to($recipient); | |
431fbb12 TH |
340 | if ($$smtpr->code) { |
341 | log_smtp_reply($logr,$$smtpr->code,$$smtpr->message); | |
f18dc26f | 342 | $success = analyze_smtp_reply($$smtpr->code,$$smtpr->message); |
431fbb12 TH |
343 | } else { |
344 | $success = -1; | |
345 | $$logr .= "---Connection failure---\n"; | |
346 | }; | |
347 | return ($success,$$smtpr->code,$$smtpr->message); | |
348 | }; | |
349 | ||
350 | ################################ log_smtp_reply ################################ | |
351 | # log result of SMTP command | |
352 | # IN : \$log : reference to the log (to be printed out via -l) | |
353 | # $code : SMTP status code | |
354 | # @message : SMTP status message | |
355 | # OUT: --- | |
356 | # \$log will be changed | |
357 | sub log_smtp_reply { | |
358 | my($logr,$code,@message)=@_; | |
359 | $$logr .= sprintf('%s %s',$code,join('- ',@message)); | |
360 | return; | |
0bd5c08c TH |
361 | } |
362 | ||
f18dc26f TH |
363 | ############################### analyze_smtp_reply ############################## |
364 | # analyze SMTP response codes and messages | |
365 | # IN : $code : SMTP status code | |
366 | # @message : SMTP status message | |
367 | # OUT: exit code (0 for false, 1 for true, -1 for tempfail) | |
368 | sub analyze_smtp_reply { | |
369 | my($code,@message)=@_; | |
370 | my $type = substr($code, 0, 1); | |
371 | if ($type == 2) { | |
372 | return 1; | |
373 | } elsif ($type == 5) { | |
374 | return 0; | |
375 | } elsif ($type == 4) { | |
376 | return -1; | |
377 | }; | |
378 | return -1; | |
379 | } | |
380 | ||
431fbb12 TH |
381 | ############################## connection_failed ############################### |
382 | # print failure message and return status 1 | |
f18dc26f | 383 | # IN : @message : SMTP status message |
431fbb12 TH |
384 | # OUT: 1 |
385 | sub connection_failed { | |
f18dc26f TH |
386 | my(@message)=@_; |
387 | print " ! Connection failed or other temporary failure.\n" if !($options{'q'}); | |
388 | printf(" %s\n",join(' ',@message)) if @message; | |
431fbb12 TH |
389 | return 1; |
390 | } | |
32301d53 TH |
391 | |
392 | __END__ | |
393 | ||
394 | ################################ Documentation ################################# | |
395 | ||
396 | =head1 NAME | |
397 | ||
398 | checkmail - check deliverability of a mail address | |
399 | ||
400 | =head1 SYNOPSIS | |
401 | ||
2121a934 | 402 | B<checkmail> [B<-Vhqlr>] [B<-m> I<host>] [-s I<sender>] [-e I<EHLO>] I<address>|B<-f> I<file> |
32301d53 TH |
403 | |
404 | =head1 REQUIREMENTS | |
405 | ||
406 | =over 2 | |
407 | ||
408 | =item - | |
409 | ||
410 | Perl 5.8 or later | |
411 | ||
412 | =item - | |
413 | ||
414 | File::Basename | |
415 | ||
416 | =item - | |
417 | ||
418 | Getopt::Std | |
419 | ||
420 | =item - | |
421 | ||
fd1fe450 TH |
422 | Mail::Address I<(CPAN)> |
423 | ||
424 | =item - | |
425 | ||
32301d53 TH |
426 | Net::DNS I<(CPAN)> |
427 | ||
428 | =item - | |
429 | ||
430 | Net::SMTP | |
431 | ||
432 | =back | |
433 | ||
434 | Furthermore you'll need a working DNS installation. | |
435 | ||
436 | =head1 DESCRIPTION | |
437 | ||
438 | checkmail checks the vailidity / deliverability of a mail address. | |
439 | You may submit just one address as the last argument or a file | |
440 | containing one address on each line using the B<-f> option. | |
441 | ||
442 | =head2 Configuration | |
443 | ||
444 | For the time being, all configuration is done in the script. You have | |
445 | to set the following elements of the %config hash: | |
446 | ||
447 | =over 4 | |
448 | ||
449 | =item B<$config{'helo'}> | |
450 | ||
451 | The hostname to be used for I<HELO> or I<EHLO> in the SMTP dialog. | |
452 | ||
453 | =item B<$config{'from'}> | |
454 | ||
455 | The sender address to be used for I<MAIL FROM> while testing. | |
240c1264 | 456 | May be empty ('') to set '<>' as MAIL FROM. |
32301d53 | 457 | |
32301d53 TH |
458 | =back |
459 | ||
2121a934 TH |
460 | You may override that configuration by using the B<-e> and B<-s> |
461 | command line options. | |
462 | ||
32301d53 TH |
463 | =head2 Usage |
464 | ||
465 | After configuring the script you may run your first test with | |
466 | ||
467 | checkmail user@example.org | |
468 | ||
ea5d225a TH |
469 | B<checkmail> will check the address for syntactic validity. If the |
470 | address is valid, it will try to determine the mail exchanger(s) (MX) | |
32301d53 TH |
471 | responsible for I<example.org> by querying the DNS for the respective |
472 | MX records and then try to connect via SMTP (on port 25) to each of | |
473 | them in order of precedence (if necessary). It will run through the | |
474 | SMTP dialog until just before the I<DATA> stage, i.e. doing I<EHLO>, | |
475 | I<MAIL FROM> and I<RCPT TO>. If no MX is defined, B<checkmail> will | |
476 | fall back to the I<example.org> host itself, provided there is at | |
2378aae8 TH |
477 | least one A record defined in the DNS. CNAMEs will be accepted and |
478 | resolved here. If there are neither MX nor A records for | |
479 | I<example.org>, mail is not deliverable and B<checkmail> will fail | |
480 | accordingly. If no host can be reached, B<checkmail> will fail, | |
481 | too. Finally B<checkmail> will fail if mail to the given recipient | |
482 | is not accepted by the respective host. | |
32301d53 TH |
483 | |
484 | If B<checkmail> fails, you'll not be able to deliver mail to that | |
485 | address - at least not using the configured sender address and from | |
486 | the host you're testing from. However, the opposite is not true: a | |
487 | mail you send may still not be delivered even if a test via | |
488 | B<checkmail> succeeds. The receiving entity may reject your mail after | |
489 | the I<DATA> stage, due to content checking or without any special | |
490 | reason, or it may even drop, filter or bounce your mail after finally | |
491 | accepting it. There is no way to be sure a mail will be accepted short | |
492 | of sending a real mail to the address in question. | |
493 | ||
494 | You may, however, try to detect hosts that will happily accept any and | |
495 | all recipient in the SMTP dialog and just reject your mail later on, | |
496 | for example to defeat exactly the kind of check you try to do. | |
497 | B<checkmail> will do that by submitting a recipient address that is | |
498 | known to be invalid; if that address is accepted, too, you'll know | |
499 | that you can't reliably check the validity of any address on that | |
500 | host. You can force that check by using the B<-r> option. | |
501 | ||
502 | If you don't want to see just the results of your test, you can get a | |
503 | B<complete log> of the SMTP dialog by using the B<-l> option. That may be | |
504 | helpful to test for temporary failure conditions. | |
505 | ||
506 | On the other hand you may use the B<-q> option to suppress all output; | |
507 | B<checkmail> will then terminate with one of the following B<exit | |
508 | status>: | |
509 | ||
510 | =over 4 | |
511 | ||
512 | =item B<0> | |
513 | ||
514 | address(es) seem/seems to be valid | |
515 | ||
516 | =item B<1> | |
517 | ||
518 | temporary error (connection failure or temporary failure) | |
519 | ||
520 | =item B<2> | |
521 | ||
522 | address is invalid | |
523 | ||
524 | =item B<3> | |
525 | ||
526 | address cannot reliably be checked (test using B<-r> failed) | |
527 | ||
528 | =back | |
529 | ||
530 | You can do B<batch processing> using B<-f> and submitting a file with | |
531 | one address on each line. In that case the exit status is set to the | |
532 | highest value generated by testing all addresses, i.e. it is set to | |
533 | B<0> if and only if no adress failed, but to B<2> if even one address | |
534 | failed and to B<3> if even one addresses couldn't reliably be checked. | |
535 | ||
536 | And finally you can B<suppress DNS lookups> for MX and A records and | |
537 | just force B<checkmail> to connect to a particular host using the | |
538 | B<-m> option. | |
539 | ||
540 | B<Please note:> You shouldn't try to validate addresses while working | |
541 | from a dial-up or blacklisted host. If in doubt, use the B<-l> option | |
542 | to have a closer look on the SMTP dialog yourself. | |
543 | ||
9518c394 TH |
544 | B<Please note:> To avoid shell expansion on addresses you submit to |
545 | B<checkmail>, use B<batch processing>. | |
546 | ||
32301d53 TH |
547 | =head1 OPTIONS |
548 | ||
549 | =over 3 | |
550 | ||
551 | =item B<-V> (version) | |
552 | ||
553 | Print out version and copyright information on B<checkmail> and exit. | |
554 | ||
555 | =item B<-h> (help) | |
556 | ||
557 | Print this man page and exit. | |
558 | ||
559 | =item B<-q> (quit) | |
560 | ||
561 | Suppress output and just terminate with a specific exit status. | |
562 | ||
563 | =item B<-l> (log) | |
564 | ||
565 | Log and print out the whole SMTP dialog. | |
566 | ||
567 | =item B<-r> (random address) | |
568 | ||
9fc0e927 TH |
569 | Also try a reliably invalid address to catch hosts that try undermine |
570 | address verification. | |
32301d53 TH |
571 | |
572 | =item B<-m> I<host> (MX to use) | |
573 | ||
574 | Force a connection to I<host> to check deliverability to that | |
575 | particular host irrespective of DNS entries. For example: | |
576 | ||
577 | checkmail -m test.host.example user@domain.example | |
578 | ||
2121a934 TH |
579 | =item B<-s> I<sender> (value for MAIL FROM) |
580 | ||
581 | Override configuration and use I<sender> for MAIL FROM. | |
582 | ||
583 | =item B<-e> I<EHLO> (value for EHLO) | |
584 | ||
585 | Override configuration and use I<EHLO> for EHLO. | |
586 | ||
32301d53 TH |
587 | =item B<-f> I<file> (file) |
588 | ||
589 | Process all addresses from I<file> (one on each line). | |
590 | ||
591 | =back | |
592 | ||
593 | =head1 INSTALLATION | |
594 | ||
595 | Just copy checkmail to some directory and get started. | |
596 | ||
597 | You can run your first test with | |
598 | ||
599 | checkmail user@example.org | |
600 | ||
601 | =head1 ENVIRONMENT | |
602 | ||
603 | See documentation of I<Net::DNS::Resolver>. | |
604 | ||
605 | =head1 FILES | |
606 | ||
607 | =over 4 | |
608 | ||
609 | =item F<checkmail.pl> | |
610 | ||
611 | The script itself. | |
612 | ||
613 | =back | |
614 | ||
615 | =head1 BUGS | |
616 | ||
617 | Please report any bugs or feature request to the author or use the | |
618 | bug tracker at L<http://bugs.th-h.de/>! | |
619 | ||
620 | =head1 SEE ALSO | |
621 | ||
622 | L<http://th-h.de/download/scripts.php> will have the current | |
623 | version of this program. | |
624 | ||
625 | This program is maintained using the Git version control system. You | |
626 | may clone L<git://code.th-h.de/mail/checkmail.git> to check out the | |
627 | current development tree or browse it on the web via | |
628 | L<http://code.th-h.de/?p=mail/checkmail.git>. | |
629 | ||
630 | =head1 AUTHOR | |
631 | ||
632 | Thomas Hochstein <thh@inter.net> | |
633 | ||
634 | =head1 COPYRIGHT AND LICENSE | |
635 | ||
636 | Copyright (c) 2002-2010 Thomas Hochstein <thh@inter.net> | |
637 | ||
638 | This program is free software; you may redistribute it and/or modify it | |
639 | under the same terms as Perl itself. | |
640 | ||
641 | =cut |