Skip to content

Instantly share code, notes, and snippets.

@ArrayIterator
Created February 26, 2020 07:26
Show Gist options
  • Save ArrayIterator/689a4fac96c35513457cd3b948fdd5a1 to your computer and use it in GitHub Desktop.
Save ArrayIterator/689a4fac96c35513457cd3b948fdd5a1 to your computer and use it in GitHub Desktop.
Verify Email Existences Via Port 25
<?php
namespace ArrayIterator\Email;
/**
* Class ArrayEmailVerifier
* @package ArrayIterator\Email
*/
class ArrayEmailVerifier
{
protected $debugCLI = false;
protected $error;
const TYPE_COMMAND = 'COMMAND';
const TYPE_RESPONSE = 'RESPONSE';
const ERROR_CONNECT = 60;
const ERROR_UKNOWN = 30;
const EMAIL_EXIST = 250;
const EMAIL_INVALID = 550;
/**
* ArrayEmailVerify constructor.
* @param bool $debugCLI
*/
public function __construct(bool $debugCLI = true)
{
$this->debugCLI = $debugCLI;
}
/**
* @param string $data
* @param string $type
*/
protected function debug(string $data, string $type)
{
if (!$this->debugCLI) {
return;
}
$ex = explode('.', (string) microtime(true));
$ex = isset($ex[1]) ? $ex[1] : '0';
while (strlen($ex) < 6) {
$ex .= '0';
}
$date = gmdate('Y-m-d H:i:s.').$ex;
$data = trim(str_replace(["\r"], '', $data));
printf(
"[%s][%s] %s\n",
$date,
strtoupper($type),
trim($data)
);
}
/**
* @param resource $stream
* @return array
*/
protected function getLine($stream) : array
{
$data = '';
$timeout = 5;
$limit = 10;
stream_set_timeout($stream, $timeout);
$endTime = time() + $limit;
$selR = [$stream];
$selW = null;
$break = false;
while (is_resource($stream) && !feof($stream)) {
//Must pass vars in here as params are by reference
if (!stream_select($selR, $selW, $selW, $limit)) {
break;
}
//Deliberate noise suppression - errors are handled afterwards
$str = @fgets($stream, 512);
$data .= $str;
// If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
// or 4th character is a space or a line break char, we are done reading, break the loop.
// String array access is a significant micro-optimisation over strlen
if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") {
break;
}
// Timed-out? Log and break
$info = stream_get_meta_data($stream);
if ($info['timed_out']) {
break;
}
// Now check if reads took too long
if ($endTime && time() > $endTime) {
break;
}
}
if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $data, $matches)) {
$code = (int) $matches[1];
$code_ex = (count($matches) > 2 ? $matches[2] : null);
// Cut off error code from each response line
$detail = preg_replace(
"/{$code}[ -]" .
($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m',
'',
$data
);
} else {
// Fall back to simple parsing if regex fails
$code = (int) substr($data, 0, 3);
$code_ex = null;
$detail = substr($data, 4);
}
return [
'code' => $code,
'code_ex' => $code_ex,
'data' => $data,
];
}
/**
* @return mixed
*/
public function getError()
{
return $this->error;
}
private function sendCommand($stream, $command, array &$history)
{
$this->debug(
sprintf('SENDING %s', $command),
self::TYPE_COMMAND
);
$command = rtrim($command);
fwrite($stream, "{$command}\r\n");
$line = $this->getLine($stream);
$count = 3;
while ($count-- > 0 && ! $line['data']) {
$line = $this->getLine($stream);
}
$this->debug(
$line['data'],
self::TYPE_RESPONSE
);
$history[] = $line['data'];
return $line;
}
public function verify(string $email, string $host)
{
$response = [
'status' => self::ERROR_UKNOWN,
'exist' => null,
'email' => $email,
'host' => $host,
'reply' => [],
'history' => []
];
$line =& $response['reply'];
$history =& $response['history'];
$smtpHost = "{$host}:25";
$this->debug(
sprintf('CONNECTING %s', $smtpHost),
'CONNECTING'
);
$stream = @stream_socket_client($smtpHost, $errno, $errMsg, 5, STREAM_CLIENT_CONNECT);
if (!$stream) {
$this->error = $errMsg;
$this->debug(
sprintf('CONNECTING FAILED : %s', $errMsg),
self::TYPE_RESPONSE
);
return self::ERROR_CONNECT;
}
$line = $this->getLine($stream);
$count = 3;
while ($count-- > 0 && ! $line['data']) {
$line = $this->getLine($stream);
}
if (!$line['data']) {
$this->debug(
sprintf('COULD NOT GET RESPONSE FROM SERVER : %s', $smtpHost),
self::TYPE_RESPONSE
);
$response['status'] = self::ERROR_UKNOWN;
return $response;
}
$history[] = $line['data'];
$this->debug($line['data'], self::TYPE_RESPONSE);
$line = $this->sendCommand($stream, "EHLO $host", $history);
$history[] = $line['data'];
if ($line['code'] !== 250) {
$response['status'] = $line['code'];
$this->sendCommand($stream, "HELO $host", $history);
}
if ($line['code'] !== 250) {
$response['status'] = $line['code'];
$this->sendCommand($stream, "QUIT", $history);
return $response;
}
$line = $this->sendCommand($stream, "MAIL FROM: <$email>", $history);
if ($line['code'] !== 250) {
$response['status'] = $line['code'];
$this->sendCommand($stream, "QUIT", $history);
return $response;
}
$line = $this->sendCommand($stream, "RCPT TO: <$email>", $history);
$code = $line['code'];
if ($code === 500 && $line['code_ex'] && preg_match('~Reject|Spam|5\.7~i', $line['code_ex'])) {
$code = 450;
}
$response['exist'] = in_array($code, [250, 251, 450, 451, 452, 541]);
$response['status'] = $code;
$this->sendCommand($stream, "QUIT", $history);
return $response;
}
}
<?php
/**
* usage: php cli.php [email protected]
*/
use ArrayIterator\Email\ArrayEmailVerifier;
if (PHP_SAPI !== 'cli') {
printf('Application only run on CLI Mode, currently %s', PHP_SAPI);
exit();
}
$array = new ArrayEmailVerifier(true);
$email = $argv[1]??null;
if (!$email) {
exit("PLEASE ENTER EMAIL ADDRESS\n");
}
$oldEmail = $email;
$email = filter_var($email, FILTER_VALIDATE_EMAIL);
if (!$email) {
exit(sprintf("EMAIL %s is not valid\n", $oldEmail));
}
$domain = explode('@', $email)[1]?? null;
if (!$domain || !preg_match('~\.[^.]+$~', $domain)) {
exit(sprintf("Domain for email %s is not valid\n", $email));
}
$limit = (int) ini_get('max_execution_time');
set_time_limit(5);
@getmxrr($domain, $mx, $weight);
set_time_limit($limit);
if (empty($mx)) {
printf("Could not get mx for email %s\n", $email);
exit();
}
$mx = array_combine($mx, $weight);
asort($mx);
$line = null;
foreach ($mx as $host => $weight) {
$data = $array->verify($email, $host);
if ($data) {
$line = $data;
break;
}
}
if (!$line) {
printf('Could not get data from email %s', $oldEmail);
exit;
}
print_r($line);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment