Twig, Tests, AES,

This commit is contained in:
2021-12-08 16:53:43 +01:00
parent 25d47f7528
commit 918244125c
74 changed files with 5350 additions and 1515 deletions

View File

@@ -1,4 +1,5 @@
<?php
/**
* PHPMailer RFC821 SMTP email transport class.
* PHP Version 5.5.
@@ -9,7 +10,7 @@
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2019 Marcus Bointon
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
@@ -34,7 +35,7 @@ class SMTP
*
* @var string
*/
const VERSION = '6.1.4';
const VERSION = '6.5.1';
/**
* SMTP line break constant.
@@ -185,6 +186,7 @@ class SMTP
'Amazon_SES' => '/[\d]{3} Ok (.*)/',
'SendGrid' => '/[\d]{3} Ok: queued as (.*)/',
'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/',
'Haraka' => '/[\d]{3} Message Queued \((.*)\)/',
];
/**
@@ -311,17 +313,11 @@ class SMTP
*/
public function connect($host, $port = null, $timeout = 30, $options = [])
{
static $streamok;
//This is enabled by default since 5.0.0 but some providers disable it
//Check this once and cache the result
if (null === $streamok) {
$streamok = function_exists('stream_socket_client');
}
// Clear errors to avoid confusion
//Clear errors to avoid confusion
$this->setError('');
// Make sure we are __not__ connected
//Make sure we are __not__ connected
if ($this->connected()) {
// Already connected, generate error
//Already connected, generate error
$this->setError('Already connected to a server');
return false;
@@ -329,18 +325,66 @@ class SMTP
if (empty($port)) {
$port = self::DEFAULT_PORT;
}
// Connect to the SMTP server
//Connect to the SMTP server
$this->edebug(
"Connection: opening to $host:$port, timeout=$timeout, options=" .
(count($options) > 0 ? var_export($options, true) : 'array()'),
self::DEBUG_CONNECTION
);
$this->smtp_conn = $this->getSMTPConnection($host, $port, $timeout, $options);
if ($this->smtp_conn === false) {
//Error info already set inside `getSMTPConnection()`
return false;
}
$this->edebug('Connection: opened', self::DEBUG_CONNECTION);
//Get any announcement
$this->last_reply = $this->get_lines();
$this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
$responseCode = (int)substr($this->last_reply, 0, 3);
if ($responseCode === 220) {
return true;
}
//Anything other than a 220 response means something went wrong
//RFC 5321 says the server will wait for us to send a QUIT in response to a 554 error
//https://tools.ietf.org/html/rfc5321#section-3.1
if ($responseCode === 554) {
$this->quit();
}
//This will handle 421 responses which may not wait for a QUIT (e.g. if the server is being shut down)
$this->edebug('Connection: closing due to error', self::DEBUG_CONNECTION);
$this->close();
return false;
}
/**
* Create connection to the SMTP server.
*
* @param string $host SMTP server IP or host name
* @param int $port The port number to connect to
* @param int $timeout How long to wait for the connection to open
* @param array $options An array of options for stream_context_create()
*
* @return false|resource
*/
protected function getSMTPConnection($host, $port = null, $timeout = 30, $options = [])
{
static $streamok;
//This is enabled by default since 5.0.0 but some providers disable it
//Check this once and cache the result
if (null === $streamok) {
$streamok = function_exists('stream_socket_client');
}
$errno = 0;
$errstr = '';
if ($streamok) {
$socket_context = stream_context_create($options);
set_error_handler([$this, 'errorHandler']);
$this->smtp_conn = stream_socket_client(
$connection = stream_socket_client(
$host . ':' . $port,
$errno,
$errstr,
@@ -348,7 +392,6 @@ class SMTP
STREAM_CLIENT_CONNECT,
$socket_context
);
restore_error_handler();
} else {
//Fall back to fsockopen which should work in more places, but is missing some features
$this->edebug(
@@ -356,17 +399,18 @@ class SMTP
self::DEBUG_CONNECTION
);
set_error_handler([$this, 'errorHandler']);
$this->smtp_conn = fsockopen(
$connection = fsockopen(
$host,
$port,
$errno,
$errstr,
$timeout
);
restore_error_handler();
}
// Verify we connected properly
if (!is_resource($this->smtp_conn)) {
restore_error_handler();
//Verify we connected properly
if (!is_resource($connection)) {
$this->setError(
'Failed to connect to server',
'',
@@ -381,22 +425,19 @@ class SMTP
return false;
}
$this->edebug('Connection: opened', self::DEBUG_CONNECTION);
// SMTP server can take longer to respond, give longer timeout for first read
// Windows does not have support for this timeout function
//SMTP server can take longer to respond, give longer timeout for first read
//Windows does not have support for this timeout function
if (strpos(PHP_OS, 'WIN') !== 0) {
$max = (int) ini_get('max_execution_time');
// Don't bother if unlimited
if (0 !== $max && $timeout > $max) {
$max = (int)ini_get('max_execution_time');
//Don't bother if unlimited, or if set_time_limit is disabled
if (0 !== $max && $timeout > $max && strpos(ini_get('disable_functions'), 'set_time_limit') === false) {
@set_time_limit($timeout);
}
stream_set_timeout($this->smtp_conn, $timeout, 0);
stream_set_timeout($connection, $timeout, 0);
}
// Get any announcement
$announce = $this->get_lines();
$this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
return true;
return $connection;
}
/**
@@ -420,7 +461,7 @@ class SMTP
$crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
}
// Begin encrypted connection
//Begin encrypted connection
set_error_handler([$this, 'errorHandler']);
$crypto_ok = stream_socket_enable_crypto(
$this->smtp_conn,
@@ -458,11 +499,11 @@ class SMTP
}
if (array_key_exists('EHLO', $this->server_caps)) {
// SMTP extensions are available; try to find a proper authentication method
//SMTP extensions are available; try to find a proper authentication method
if (!array_key_exists('AUTH', $this->server_caps)) {
$this->setError('Authentication is not allowed at this stage');
// 'at this stage' means that auth may be allowed after the stage changes
// e.g. after STARTTLS
//'at this stage' means that auth may be allowed after the stage changes
//e.g. after STARTTLS
return false;
}
@@ -506,22 +547,25 @@ class SMTP
}
switch ($authtype) {
case 'PLAIN':
// Start authentication
//Start authentication
if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
return false;
}
// Send encoded username and password
if (!$this->sendCommand(
'User & Password',
base64_encode("\0" . $username . "\0" . $password),
235
)
//Send encoded username and password
if (
//Format from https://tools.ietf.org/html/rfc4616#section-2
//We skip the first field (it's forgery), so the string starts with a null byte
!$this->sendCommand(
'User & Password',
base64_encode("\0" . $username . "\0" . $password),
235
)
) {
return false;
}
break;
case 'LOGIN':
// Start authentication
//Start authentication
if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
return false;
}
@@ -533,17 +577,17 @@ class SMTP
}
break;
case 'CRAM-MD5':
// Start authentication
//Start authentication
if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
return false;
}
// Get the challenge
//Get the challenge
$challenge = base64_decode(substr($this->last_reply, 4));
// Build the response
//Build the response
$response = $username . ' ' . $this->hmac($challenge, $password);
// send encoded credentials
//send encoded credentials
return $this->sendCommand('Username', base64_encode($response), 235);
case 'XOAUTH2':
//The OAuth instance must be set up prior to requesting auth.
@@ -552,7 +596,7 @@ class SMTP
}
$oauth = $OAuth->getOauth64();
// Start authentication
//Start authentication
if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
return false;
}
@@ -582,15 +626,15 @@ class SMTP
return hash_hmac('md5', $data, $key);
}
// The following borrowed from
// http://php.net/manual/en/function.mhash.php#27225
//The following borrowed from
//http://php.net/manual/en/function.mhash.php#27225
// RFC 2104 HMAC implementation for php.
// Creates an md5 HMAC.
// Eliminates the need to install mhash to compute a HMAC
// by Lance Rushing
//RFC 2104 HMAC implementation for php.
//Creates an md5 HMAC.
//Eliminates the need to install mhash to compute a HMAC
//by Lance Rushing
$bytelen = 64; // byte length for md5
$bytelen = 64; //byte length for md5
if (strlen($key) > $bytelen) {
$key = pack('H*', md5($key));
}
@@ -613,7 +657,7 @@ class SMTP
if (is_resource($this->smtp_conn)) {
$sock_status = stream_get_meta_data($this->smtp_conn);
if ($sock_status['eof']) {
// The socket is valid but we are not connected
//The socket is valid but we are not connected
$this->edebug(
'SMTP NOTICE: EOF caught while checking if connected',
self::DEBUG_CLIENT
@@ -623,7 +667,7 @@ class SMTP
return false;
}
return true; // everything looks good
return true; //everything looks good
}
return false;
@@ -641,7 +685,7 @@ class SMTP
$this->server_caps = null;
$this->helo_rply = null;
if (is_resource($this->smtp_conn)) {
// close the connection and cleanup
//Close the connection and cleanup
fclose($this->smtp_conn);
$this->smtp_conn = null; //Makes for cleaner serialization
$this->edebug('Connection: closed', self::DEBUG_CONNECTION);
@@ -651,7 +695,7 @@ class SMTP
/**
* Send an SMTP DATA command.
* Issues a data command and sends the msg_data to the server,
* finializing the mail transaction. $msg_data is the message
* finalizing the mail transaction. $msg_data is the message
* that is to be send with the headers. Each header needs to be
* on a single line followed by a <CRLF> with the message headers
* and the message body being separated by an additional <CRLF>.
@@ -676,7 +720,7 @@ class SMTP
* NOTE: this does not count towards line-length limit.
*/
// Normalize line breaks before exploding
//Normalize line breaks before exploding
$lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data));
/* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
@@ -722,7 +766,8 @@ class SMTP
//Send the lines to the server
foreach ($lines_out as $line_out) {
//RFC2821 section 4.5.2
//Dot-stuffing as per RFC5321 section 4.5.2
//https://tools.ietf.org/html/rfc5321#section-4.5.2
if (!empty($line_out) && $line_out[0] === '.') {
$line_out = '.' . $line_out;
}
@@ -756,7 +801,16 @@ class SMTP
public function hello($host = '')
{
//Try extended hello first (RFC 2821)
return $this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host);
if ($this->sendHello('EHLO', $host)) {
return true;
}
//Some servers shut down the SMTP service here (RFC 5321)
if (substr($this->helo_rply, 0, 3) == '421') {
return false;
}
return $this->sendHello('HELO', $host);
}
/**
@@ -946,12 +1000,12 @@ class SMTP
$this->client_send($commandstring . static::LE, $command);
$this->last_reply = $this->get_lines();
// Fetch SMTP code and possible error code explanation
//Fetch SMTP code and possible error code explanation
$matches = [];
if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) {
$code = (int) $matches[1];
$code_ex = (count($matches) > 2 ? $matches[2] : null);
// Cut off error code from each response line
//Cut off error code from each response line
$detail = preg_replace(
"/{$code}[ -]" .
($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m',
@@ -959,7 +1013,7 @@ class SMTP
$this->last_reply
);
} else {
// Fall back to simple parsing if regex fails
//Fall back to simple parsing if regex fails
$code = (int) substr($this->last_reply, 0, 3);
$code_ex = null;
$detail = substr($this->last_reply, 4);
@@ -1058,8 +1112,10 @@ class SMTP
{
//If SMTP transcripts are left enabled, or debug output is posted online
//it can leak credentials, so hide credentials in all but lowest level
if (self::DEBUG_LOWLEVEL > $this->do_debug &&
in_array($command, ['User & Password', 'Username', 'Password'], true)) {
if (
self::DEBUG_LOWLEVEL > $this->do_debug &&
in_array($command, ['User & Password', 'Username', 'Password'], true)
) {
$this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT);
} else {
$this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT);
@@ -1113,7 +1169,7 @@ class SMTP
if (!$this->server_caps) {
$this->setError('No HELO/EHLO was sent');
return;
return null;
}
if (!array_key_exists($name, $this->server_caps)) {
@@ -1125,7 +1181,7 @@ class SMTP
}
$this->setError('HELO handshake was used; No information about server extensions available');
return;
return null;
}
return $this->server_caps[$name];
@@ -1152,7 +1208,7 @@ class SMTP
*/
protected function get_lines()
{
// If the connection is bad, give up straight away
//If the connection is bad, give up straight away
if (!is_resource($this->smtp_conn)) {
return '';
}
@@ -1166,33 +1222,61 @@ class SMTP
$selW = null;
while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
//Must pass vars in here as params are by reference
if (!stream_select($selR, $selW, $selW, $this->Timelimit)) {
//solution for signals inspired by https://github.com/symfony/symfony/pull/6540
set_error_handler([$this, 'errorHandler']);
$n = stream_select($selR, $selW, $selW, $this->Timelimit);
restore_error_handler();
if ($n === false) {
$message = $this->getError()['detail'];
$this->edebug(
'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
'SMTP -> get_lines(): select failed (' . $message . ')',
self::DEBUG_LOWLEVEL
);
//stream_select returns false when the `select` system call is interrupted
//by an incoming signal, try the select again
if (stripos($message, 'interrupted system call') !== false) {
$this->edebug(
'SMTP -> get_lines(): retrying stream_select',
self::DEBUG_LOWLEVEL
);
$this->setError('');
continue;
}
break;
}
if (!$n) {
$this->edebug(
'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . ' sec)',
self::DEBUG_LOWLEVEL
);
break;
}
//Deliberate noise suppression - errors are handled afterwards
$str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH);
$this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL);
$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 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
//Timed-out? Log and break
$info = stream_get_meta_data($this->smtp_conn);
if ($info['timed_out']) {
$this->edebug(
'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . ' sec)',
self::DEBUG_LOWLEVEL
);
break;
}
// Now check if reads took too long
//Now check if reads took too long
if ($endtime && time() > $endtime) {
$this->edebug(
'SMTP -> get_lines(): timelimit reached (' .
@@ -1344,6 +1428,7 @@ class SMTP
} else {
$this->last_smtp_transaction_id = false;
foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
$matches = [];
if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
$this->last_smtp_transaction_id = trim($matches[1]);
break;