Skip to content

Instantly share code, notes, and snippets.

@gubi
Last active December 23, 2015 17:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gubi/6667718 to your computer and use it in GitHub Desktop.
Save gubi/6667718 to your computer and use it in GitHub Desktop.
This function tries to verify an email address using several techniques, depending on the configuration, and returns its smtp host. Based on a work from http://www.tienhuis.nl/php-email-address-validation-with-verify-probe and improved by me ;)
<?php
header("Content-type: text/plain");
require_once("email_verify_source.php");
$email_to_verify = (!isset($_GET["smtp_host"]) ? "mail_to_check@test_domain.com" : $_GET["smtp_host"]);
$static_mail_address_for_test = "your_email@example.com";
$static_host_for_test = "example.com";
// Example from http://www.tienhuis.nl/files/email_verify_example.php
print validateEmail($email_to_verify, true, true, $static_mail_address_for_test, $static_host_for_test);
?>
<?php
/*
Taken from http://www.tienhuis.nl/files/email_verify_source.php
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
$Id: VerifyEmailAddress.php 58 2012-11-06 15:58:20Z visser $
Email address verification with SMTP probes
Dick Visser <dick@tienhuis.nl>
INTRODUCTION
This function tries to verify an email address using several techniques,
depending on the configuration.
Arguments that are needed:
$email (string)
The address you are trying to verify
$domainCheck (boolean)
Check if any DNS MX records exist for domain part
$verify (boolean)
Use SMTP verify probes to see if the address is deliverable.
$probe_address (string)
This is the email address that is used as FROM address in outgoing
probes. Make sure this address exists so that in the event that the
other side does probing too this will work.
$helo_address (string)
This should be the hostname of the machine that runs this site.
$return_errors (boolean)
By default, no errors are returned. This means that the function will evaluate
to TRUE if no errors are found, and false in case of errors. It is not possible
to return those errors, because returning something would be a TRUE.
When $return_errors is set, the function will return FALSE if the address
passes the tests. If it does not validate, an array with errors is returned.
A global variable $debug can be set to display all the steps.
EXAMPLES
Use more options to get better checking.
Check only by syntax: validateEmail('dick@tienhuis.nl')
Check syntax + DNS MX records: validateEmail('dick@tienhuis.nl', true);
Check syntax + DNS records + SMTP probe:
validateEmail('dick@tienhuis.nl', true, true, 'postmaster@tienhuis.nl', 'outkast.tienhuis.nl');
WARNING
This function works for now, but it may well break in the future.
*/
function getdomain($url) {
$url = strtolower($url);
$slds = "\.co\.uk|\.me\.uk|\.net\.uk|\.org\.uk|\.sch\.uk|\.ac\.uk|\.gov\.uk|\.nhs\.uk|\.police\.uk|\.mod\.uk|\.asn\.au|\.com\.au|\.net\.au|\.id\.au|\.org\.au|\.edu\.au|\.gov\.au|\.csiro\.au";
preg_match("/^(http:\/\/|https:\/\/|)[a-zA-Z-]([^\/]+)/i", $url, $matches);
$host = $matches[0];
if (preg_match("/$slds$/", $host, $matches)) {
preg_match ("/[^\.\/]+\.[^\.\/]+\.[^\.\/]+$/", $host, $matches);
} else {
preg_match ("/[^\.\/]+\.[^\.\/]+$/", $host, $matches);
}
return "{$matches[0]}";
}
function validateEmail($email, $domainCheck = false, $verify = false, $probe_address = "", $helo_address = "", $return_errors = false) {
global $debug;
//$debug = true;
/*
Time-outs.
Be sure to have set PHP's "max_execution_time" to some value greater than the total time of the
communication. This means of course also (a fair amount) larger than any of the next two values.
Of course, large timeouts will cause the verification to take alot of time. There are servers
that have configured such large delays that they are unsuitable for verifcation with a web page.
TCP connect timeout (seconds). Some servers deliberately wait a while before responding (tarpitting).
*/
$tcp_connect_timeout = 18000;
/*
Some server (exim) can be configured to wait before acknowledging. If you issues the next command
too soon, it will drop the SMTP conversation with stuff like: "554 SMTP synchronization error".
*/
$smtp_timeout = 6000;
if($debug) {echo "<pre>";}
# Check email syntax with regex
if (preg_match('/^([a-zA-Z0-9\'\._\+-]+)\@((\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,7}|[0-9]{1,3})(\]?))$/', $email, $matches)) {
$user = $matches[1];
$domain = getdomain($matches[2]);
/*
Some MTAs do not like people ringing their door bell too much.
If this is the case, sender verification will get your IP address
blacklisted. This is a problem when your web server is also a
mail server. If you MTA is Postfix, you can whitelist addresses
so that they are not verified any more:
in Postfix, see http://www.postfix.org/ADDRESS_VERIFICATION_README.html#sender_always
This list should be taken into account by this script, otherwise
your IP address will get blacklisted anyway.
The code below ONLY works when the access list is of type 'pcre', see
http://www.postfix.org/pcre_table.5.html for how to construct this.
*/
$whitelist= '/etc/postfix/sender_access';
if($exluded = explode("\n", @file_get_contents($whitelist))) {
$regexes=array();
foreach ($exluded as $line) {
preg_match('/^(\/.*?\/)\s+OK/', $line, $matches);
if(@$matches[1]) {
array_push($regexes, $matches[1]);
}
}
foreach ($regexes as $regex) {
preg_match($regex, "$user@$domain", $m);
if(@$m[0]) {
if($debug) { echo "$whitelist contains matching whitelist regex '".$regex."'\n"; }
if($return_errors) {
# Give back details about the error(s).
# Return FALSE if there are no errors.
if(isset($error)) return htmlentities($error); else return false;
} else {
# 'Old' behaviour, simple to understand
if(isset($error)) return false; else return true;
}
}
}
}
# Check availability of MX/A records
if ($domainCheck) {
if(function_exists('checkdnsrr')) {
# Construct array of available mailservers
getmxrr($domain, $mxhosts, $mxweight);
if(count($mxhosts) > 0) {
for($i=0;$i<count($mxhosts);$i++){
$mxs[$mxhosts[$i]] = $mxweight[$i];
}
asort($mxs);
$mailers = array_keys($mxs);
# No MX found, use A
} elseif(checkdnsrr($domain, 'A')) {
$mailers[0] = gethostbyname($domain);
} else {
$mailers=array();
}
} else {
# DNS functions do not exist - we are probably on Win32.
# This means we have to resort to other means, like the Net_DNS PEAR class.
# For more info see http://pear.php.net
# For this you also need to enable the mhash module (lib_mhash.dll).
# Another way of doing this is by using a wrapper for Win32 dns functions like
# the one descrieb at http://px.sklar.com/code.html/id=1304
require_once 'Net/DNS.php';
$resolver = new Net_DNS_Resolver();
# Workaround for bug in Net_DNS, you have to explicitly tell the name servers
#
# *********** CHANGE THIS TO YOUR OWN NAME SERVERS **************
$resolver->nameservers = array ('217.149.196.6', '217.149.192.6');
$mx_response = $resolver->query($domain, 'MX');
$a_response = $resolver->query($domain, 'A');
if ($mx_response) {
foreach ($mx_response->answer as $rr) {
$mxs[$rr->exchange] = $rr->preference;
}
asort($mxs);
$mailers = array_keys($mxs);
} elseif($a_response) {
$mailers[0] = gethostbyname($domain);
} else {
$mailers = array();
}
}
$total = count($mailers);
# Query each mailserver
if($total > 0 && $verify) {
# Check if mailers accept mail
for($n=0; $n < $total; $n++) {
# Check if socket can be opened
if($debug) { echo "Checking server $mailers[$n]...\n";}
$errno = 0;
$errstr = 0;
# Try to open up TCP socket
if($sock = @fsockopen($mailers[$n], 25, $errno , $errstr, $tcp_connect_timeout)) {
$response = fread($sock,8192);
if($debug) {echo "Opening up socket to $mailers[$n]... Succes!\n";}
stream_set_timeout($sock, $smtp_timeout);
$meta = stream_get_meta_data($sock);
if($debug) { echo "$mailers[$n] replied: $response\n";}
$cmds = array(
"HELO $helo_address",
"MAIL FROM: <$probe_address>",
"RCPT TO:<$email>",
"QUIT",
);
# Hard error on connect -> break out
# Error means 'any reply that does not start with 2xx '
if(!$meta['timed_out'] && !preg_match('/^2\d\d[ -]/', $response)) {
$error = "Error: $mailers[$n] said: $response\n";
break;
}
foreach($cmds as $cmd) {
$before = microtime(true);
fputs($sock, "$cmd\r\n");
$response = fread($sock, 4096);
$t = 1000*(microtime(true)-$before);
if($debug) {echo htmlentities("$cmd\n$response") . "(" . sprintf('%.2f', $t) . " ms)\n";}
if(!$meta['timed_out'] && preg_match('/^5\d\d[ -]/', $response)) {
$error = "Unverified address: $mailers[$n] said: $response";
break 2;
}
}
fclose($sock);
if($debug) { echo "Succesful communication with $mailers[$n], no hard errors, assuming OK";}
break;
} elseif($n == $total-1) {
$error = "None of the mailservers listed for $domain could be contacted";
}
}
} elseif($total <= 0) {
$error = "No usable DNS records found for domain '$domain'";
}
}
} else {
$error = 'Address syntax not correct';
}
if($debug) { echo "</pre>";}
if($return_errors) {
# Give back details about the error(s).
# Return FALSE if there are no errors.
if(isset($error)) return nl2br(htmlentities($error)); else return false;
} else {
# 'Old' behaviour, simple to understand
if(isset($error)) return false; else return $mailers[$n];
}
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment