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