index.php
<?php
//Mail functions. If you view the source, it's attached below this file.
require('_Mail.php');
//Set here if source code viewing is allowed
define('ALLOW_SOURCE',TRUE);
//Permit API usage
define('ALLOW_API',TRUE);
//Characters used to replace sensitive information in the source. Any valid HTML.
//To be extra boring, just use 8 asterisks
define('SOURCE_REPLACE',
'<span style="color:#ff0000;">█</span>'.
'<span style="color:#ff7f00;">█</span>'.
'<span style="color:#ffbf00;">█</span>'.
'<span style="color:#ffff00;">█</span>'.
'<span style="color:#00ff00;">█</span>'.
'<span style="color:#00ff80;">█</span>'.
'<span style="color:#00ffff;">█</span>'.
'<span style="color:#0000ff;">█</span>');
//define('SOURCE_REPLACE','********');
/*//Remove this box to prevent remote sites from using this*/
/**/header('Access-Control-Allow-Origin: *'); //
/**/header('Access-Control-Allow-Methods: GET'); //
/**//////////////////////////////////////////////////////////
$addr=($_POST && isset($_POST['addr']))?$_POST['addr']:'';
//Source view
if(ALLOW_SOURCE && isset($_GET['source'])){
//HTML frame START
echo '<html><head><title>Mail Checker Source</title></head><body style="font-family:Sans-Serif;font-size:large"><h1>index.php</h1>';
//This file
highlight_file(__FILE__);
//Mail include file
echo '<h1>_Mail.php</h1>';
//Replace some values we don't want to show
//Be aware that this doesn't works reliably if the values contain backslashes that are not literal.
echo
str_replace(MAIL_BLACKLIST_FILE,SOURCE_REPLACE,
str_replace(MAIL_DYNAMIC_MX ,SOURCE_REPLACE,
str_replace(MAIL_MX_BLACKLIST ,SOURCE_REPLACE,
highlight_file('_Mail.php', TRUE))));
//HTML frame END
echo '</body></html>';
exit(0);
}
//GET API Request
if(ALLOW_API && isset($_GET['addr'])){
$addr=$_GET['addr'];
//Ensure param is a string and properly set
if(is_string($addr) && strlen($addr)>0)
{
//Strip local part if existing, we only care for the domain name
$addr=mail_getHost($addr);
//Basic response format
$res=array(
'success'=> FALSE,
'addr' => $addr,
'code' => -1,
'desc' => 'unknown error'
);
//Validate basic E-Mail format
if(mail_validate("example@$addr")){
//Validate E-Mail domain
switch(mail_accepted("example@$addr")){
//Address is OK
case ERR_MAIL_SUCCESS:
$res['success']=TRUE;
$res['code']=ERR_MAIL_SUCCESS;
$res['desc']='accepted';
break;
//Address seems invalid
case ERR_MAIL_INVALID:
$res['success']=TRUE;
$res['code']=ERR_MAIL_INVALID;
$res['desc']='invalid_format';
break;
//Address has valid format but no E-Mail servers
case ERR_MAIL_NOSERVER:
$res['success']=TRUE;
$res['code']=ERR_MAIL_NOSERVER;
$res['desc']='no_mx';
break;
//Address found in our blacklist file
case ERR_MAIL_BLACKLIST:
$res['success']=TRUE;
$res['code']=ERR_MAIL_BLACKLIST;
$res['desc']='blacklist';
break;
//Address is a dynamic throwaway domain
case ERR_MAIL_THROWAWAY:
$res['success']=TRUE;
$res['code']=ERR_MAIL_THROWAWAY;
$res['desc']='throwaway';
break;
}
}
else{
$res['success']=FALSE;
$res['desc']='Unable to parse address';
}
}
else{
//Address was not specified
$res=array('success'=> FALSE);
}
//Answer is JSON
header('Content-Type: application/json');
//Cache this response because it's unlikely to change
header('Expires: ' . gmdate('D, d M Y H:i:s T',strtotime('+1 year')));
echo json_encode($res);
exit(0);
}
?><!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="pragma" content="no-cache" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>E-Mail Test</title>
<!-- We only need the css of bootstrap. the JS is not necessary -->
<link rel="stylesheet" type="text/css" href="/bootstrap4/bootstrap.min.css" />
<!-- This script is not required. It merely completes the e-mail address if needed -->
<script defer type="text/javascript" src="script.js"></script>
</head>
<body>
<div class="container">
<h1>E-Mail Test</h1>
<p>
This site performs a few tests on the address to see if it is valid.
All tests are performed without connecting to a mail server or 3rd party service.<br />
This test also checks against common throwaway services.
This will not check if the address actually exists,
only if the conditions for it existing are met.
This means you can enter anything in front of the <kbd>@</kbd>
</p>
<form method="post" class="form-inline">
<div class="form-group">
<label class="control-label">
E-Mail Address:
<input
type="email" name="addr" class="form-control" placeholder="E-Mail Address"
value="<?=htmlspecialchars($addr); ?>" />
</label>
<input type="submit" class="btn btn-primary" value="Check" />
</div>
</form>
<br />
<?php if($addr){
if(mail_validate($addr)){
switch(mail_accepted($addr)){
case ERR_MAIL_SUCCESS:
echo '<div class="alert alert-success">E-Mail Address is valid and we would perform online tests now. This means attempting to send an E-Mail and tell, if an error happened (SMTP Code >499)</div>';
break;
case ERR_MAIL_INVALID:
echo '<div class="alert alert-danger">E-Mail Address is invalid (for example invalid Domain name)</div>';
break;
case ERR_MAIL_NOSERVER:
echo '<div class="alert alert-danger">E-Mail Address is invalid. The domain has no E-Mail servers</div>';
break;
case ERR_MAIL_BLACKLIST:
echo '<div class="alert alert-warning">E-Mail Address is valid but blacklisted on our system (Blacklist).</div>';
break;
case ERR_MAIL_THROWAWAY:
echo '<div class="alert alert-warning">E-Mail Address is valid but blacklisted on our system (Dynamic throwaway detection).</div>';
break;
}
}
else{
echo '<div class="alert alert-danger">E-Mail Address is invalid (wrong format)</div>';
}
}
elseif(ALLOW_API){
?>
<h2>API</h2>
<p>
You can use our JSON API to validate addresses.
Example: <code>https://<?=$_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];?>?addr=test@example.com</code>.<br />
You can also only supply a domain name because our validation doesn't requires the local part.<br />
The call above is identical to <code>https://<?=$_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];?>?addr=example.com</code>.
</p>
<p>
The answer is delivered as a JSON object with these values:
</p>
<ul>
<li>
<b>success</b>: boolean, true or false depending if validation was attempted.
If false, your address is likely invalid or you messed up the request.
In this case, no other properties will be present.
</li>
<li><b>addr</b>: string, the address we validated</li>
<li><b>code</b>: int, result of validation. See below for a list of codes</li>
<li><b>desc</b>: string, brief description of the code</li>
</ul>
Codes are as follows:
<ul>
<li><b><?=ERR_MAIL_SUCCESS;?></b>: The address is accepted</li>
<li><b><?=ERR_MAIL_INVALID;?></b>: The format is invalid</li>
<li><b><?=ERR_MAIL_NOSERVER;?></b>: This address has no mailservers (no MX servers)</li>
<li><b><?=ERR_MAIL_BLACKLIST;?></b>: This address is blacklisted (hardcoded list)</li>
<li><b><?=ERR_MAIL_THROWAWAY;?></b>: This address is blacklisted (dynamically generated server list)</li>
</ul>
<p>
The dynamic list (Code 4) is for services that constantly buy different domains to evade detection.
<?php if(MAIL_AUTO_LIST_UPDATE){ ?><br />
Note: This instance has <code>MAIL_AUTO_LIST_UPDATE</code> enabled.
This means you will encounter code 4 at most once for a certain host part,
after that you will receive code 3.
<?php } ?>
</p>
<p>
Addresses without an MX server (code 2) are usually invalid.
The standard says that if no MX servers are to be found,
the host part in the mail address itself should be treated as MX server and used for mail delivery.
Most E-mail provider will reject messages from addresses that lack an MX,
so it's unlikely you encounter this code for valid addresses.
</p>
<h2>User Agent</h2>
<p>
When using our API, we expect a proper User-Agent header.
Headers that are generic or mimic browsers might get your IP blocked.
Use a descriptive agent with a help link.<br />
Example: <code>Mail-Checker/1.1 +https://example.com/path/to/help</code>.
</p>
<?php } ?>
<i class="pull-right">
<?php if(ALLOW_SOURCE){
echo "<a href=\"?source\">View Source</a> |";
}?>
<a href="/">Service Directory</a>
</i>
</div>
</body>
</html>
_Mail.php
<?php
//Don't do any sort of string concatenation or the censoring for source view will no longer work.
//You can ignore this warning if you don't plan to publish the source.
//These MX servers are blacklisted on our system.
//Usage: Used when some MX servers accept mails from an ever changing list of domain names.
//Format: domain1.com,domain2.com,...
define('MAIL_MX_BLACKLIST','████████');
//The MX servers of these domains are blacklisted on our system.
//Always compare the mail domain yourself if you can, because MAIL_DYNAMIC_MX is slower.
//Usage: Used if a provider hosts an unknown number of spam domains but they all point to the same MX server.
// In that case listing a single domain name here will block all other domain names with the same MX servers.
//Format: domain1.com,domain2.com,...
define('MAIL_DYNAMIC_MX','████████');
//File with the hardcoded domain blacklist.
//This file should contain one domain on each line.
define('MAIL_BLACKLIST_FILE','████████');
//If enabled, it automatically adds addresses to the blacklist.
//To be added it has to be caught by MAIL_DYNAMIC_MX or MAIL_MX_BLACKLIST.
//Leaving this enabled is recommended as it speeds up processing,
//because the blacklist is much faster than the dynamic detection.
//Disable if the list is read-only.
define('MAIL_AUTO_LIST_UPDATE',TRUE);
//No changes below needed
//Mail is valid
define('ERR_MAIL_SUCCESS', 0);
//Mail is invalid (format)
define('ERR_MAIL_INVALID', 1);
//Mail domain has no MX
define('ERR_MAIL_NOSERVER', 2);
//Mail is listed in blacklist file
define('ERR_MAIL_BLACKLIST',3);
//Mail is blacklisted because of MAIL_DYNAMIC_MX or MAIL_MX_BLACKLIST
define('ERR_MAIL_THROWAWAY',4);
//Holds the blacklist. Do not manually change
$mail_Blacklist=NULL;
//Formats an e-mail address
//Removes "plus addressing" which is often used to pretend to be multiple people.
function mail_format($addr){
if(mail_validate($addr)){
$seg1=substr($addr,0,strrpos($addr,'@'));
$seg2=substr($addr,strrpos($addr,'@')+1);
if(strpos($seg1,'+')!==FALSE){
$seg1=substr($seg1,0,strpos($seg1,'+'));
//Add the end quote if a start quote is there.
if(strlen($seg1)>0 && $seg1[0]==='"'){
$seg1.='"';
}
}
$addr="$seg1@$seg2";
return mail_validate($addr)?$addr:NULL;
}
return NULL;
}
//Reads the blacklist (if not done already)
function mail_readBlacklist()
{
global $mail_Blacklist;
if($mail_Blacklist===NULL)
{
if(file_exists(MAIL_BLACKLIST_FILE))
{
//Read blacklist file and trim potential whitespace from all entries
//In other words, CRLF vs LF will have no effect
$mail_Blacklist=explode("\n",trim(file_get_contents(MAIL_BLACKLIST_FILE)));
$mail_Blacklist=array_map('trim',$mail_Blacklist);
}
else
{
//You can also just set it to an empty array instead.
error_log('Not found: ' . MAIL_BLACKLIST_FILE);
throw new Exception('Mail blacklist not found at location indicated by MAIL_BLACKLIST_FILE');
}
}
return $mail_Blacklist;
}
//Gets the host part of an E-mail address
function mail_getHost($addr)
{
$host=$addr;
if(strpos($host,'@')!==FALSE)
{
return substr($host,strrpos($host,'@')+1);
}
return $host;
}
//Performs basic E-mail format validation
function mail_validate($addr){
return filter_var($addr,FILTER_VALIDATE_EMAIL)!==FALSE;
}
//Checks if the given E-mail address is accepted
//This is the only function you want to use for validation.
function mail_accepted($addr)
{
$host=mail_getHost($addr);
$ips=mail_getMailIp($host);
if($ips!==FALSE)
{
if(!mail_validate($addr)){
return ERR_MAIL_INVALID;
}
if(count($ips)===0){
return ERR_MAIL_NOSERVER;
}
if(mail_isBlacklisted($addr)){
return ERR_MAIL_BLACKLIST;
}
if(mail_isThrowaway($addr)){
return ERR_MAIL_THROWAWAY;
}
return ERR_MAIL_SUCCESS;
}
return ERR_MAIL_NOSERVER;
}
//Checks if the address is blacklisted
function mail_isBlacklisted($addr)
{
return in_array(mail_getHost($addr),mail_readBlacklist());
}
//Checks if the given address is a throwaway (test by MX)
function mail_isThrowaway($addr)
{
$host=mail_getHost($addr);
if($ips=mail_getMailIp($host))
{
$dyn_blacklist=array_merge(mail_getBlacklistedMxAddr(),mail_getDynamicBlacklist());
foreach($dyn_blacklist as $ip)
{
if(in_array($ip,$ips))
{
if(MAIL_AUTO_LIST_UPDATE && !mail_isBlacklisted($addr)){
$blacklist=mail_readBlacklist();
$blacklist[]=$host;
sort($blacklist,SORT_STRING|SORT_FLAG_CASE);
@file_put_contents(MAIL_BLACKLIST_FILE,implode(PHP_EOL,$blacklist));
}
return TRUE;
}
}
}
return FALSE;
}
//Gets MX server IP addresses
function mail_getMailIp($host)
{
if($hosts=mail_getMx($host))
{
$addrs=array();
foreach($hosts as $entry)
{
if($addr=mail_getIpAddr($entry))
{
$addrs=array_merge($addrs,$addr);
}
}
return $addrs;
}
return FALSE;
}
//Gets MX servers from a domain
function mail_getDynamicBlacklist()
{
$hosts=explode(',',MAIL_DYNAMIC_MX);
$addrs=array();
foreach($hosts as $host)
{
if($addr=mail_getMailIp($host))
{
$addrs=array_merge($addrs,$addr);
}
}
return $addrs;
}
//Gets blacklisted IP addresses
function mail_getBlacklistedMxAddr()
{
$addrs=array();
$hosts=explode(',',MAIL_MX_BLACKLIST);
foreach($hosts as $host)
{
if($addr=mail_getIpAddr($host))
{
$addrs=array_merge($addrs,$addr);
}
}
return $addrs;
}
//Checks if a DNS name has at least a single record of the specified type
function mail_hasRecord($hostOrAddr,$type='MX')
{
return checkdnsrr(mail_getHost($hostOrAddr),$type);
}
//Gets all MX records of a domain name
function mail_getMx($host)
{
if(!mail_hasRecord($host)){return FALSE;}
$results=dns_get_record($host,DNS_MX);
$hosts=array();
foreach($results as $result)
{
switch($result['type'])
{
case 'MX':
$hosts[]=$result['target'];
break;
}
}
return count($hosts)>0?$hosts:FALSE;
}
//Gets all IP addresses from a domain name
function mail_getIpAddr($host)
{
if(!mail_hasRecord($host,'A') && !mail_hasRecord($host,'AAAA')){return FALSE;}
$results=dns_get_record($host,DNS_A|DNS_AAAA);
$ips=array();
foreach($results as $result)
{
switch($result['type'])
{
case 'A':
$ips[]=$result['ip'];
break;
case 'AAAA':
$ips[]=$result['ipv6'];
break;
}
}
return count($ips)>0?$ips:FALSE;
}
?>