1
0

first commit

This commit is contained in:
Daniel Tartavel
2021-10-14 17:58:21 +02:00
parent e5fe464cbc
commit df07673a67
327 changed files with 44407 additions and 0 deletions

View File

@ -0,0 +1,541 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Sends Messages over SMTP.
*
* @author Chris Corbyn
*/
abstract class Swift_Transport_AbstractSmtpTransport implements Swift_Transport
{
/** Input-Output buffer for sending/receiving SMTP commands and responses */
protected $buffer;
/** Connection status */
protected $started = false;
/** The domain name to use in HELO command */
protected $domain = '[127.0.0.1]';
/** The event dispatching layer */
protected $eventDispatcher;
protected $addressEncoder;
/** Whether the PIPELINING SMTP extension is enabled (RFC 2920) */
protected $pipelining = null;
/** The pipelined commands waiting for response */
protected $pipeline = [];
/** Source Ip */
protected $sourceIp;
/** Return an array of params for the Buffer */
abstract protected function getBufferParams();
/**
* Creates a new EsmtpTransport using the given I/O buffer.
*
* @param string $localDomain
*/
public function __construct(Swift_Transport_IoBuffer $buf, Swift_Events_EventDispatcher $dispatcher, $localDomain = '127.0.0.1', Swift_AddressEncoder $addressEncoder = null)
{
$this->buffer = $buf;
$this->eventDispatcher = $dispatcher;
$this->addressEncoder = $addressEncoder ?? new Swift_AddressEncoder_IdnAddressEncoder();
$this->setLocalDomain($localDomain);
}
/**
* Set the name of the local domain which Swift will identify itself as.
*
* This should be a fully-qualified domain name and should be truly the domain
* you're using.
*
* If your server does not have a domain name, use the IP address. This will
* automatically be wrapped in square brackets as described in RFC 5321,
* section 4.1.3.
*
* @param string $domain
*
* @return $this
*/
public function setLocalDomain($domain)
{
if ('[' !== substr($domain, 0, 1)) {
if (filter_var($domain, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$domain = '['.$domain.']';
} elseif (filter_var($domain, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$domain = '[IPv6:'.$domain.']';
}
}
$this->domain = $domain;
return $this;
}
/**
* Get the name of the domain Swift will identify as.
*
* If an IP address was specified, this will be returned wrapped in square
* brackets as described in RFC 5321, section 4.1.3.
*
* @return string
*/
public function getLocalDomain()
{
return $this->domain;
}
/**
* Sets the source IP.
*
* @param string $source
*/
public function setSourceIp($source)
{
$this->sourceIp = $source;
}
/**
* Returns the IP used to connect to the destination.
*
* @return string
*/
public function getSourceIp()
{
return $this->sourceIp;
}
public function setAddressEncoder(Swift_AddressEncoder $addressEncoder)
{
$this->addressEncoder = $addressEncoder;
}
public function getAddressEncoder()
{
return $this->addressEncoder;
}
/**
* Start the SMTP connection.
*/
public function start()
{
if (!$this->started) {
if ($evt = $this->eventDispatcher->createTransportChangeEvent($this)) {
$this->eventDispatcher->dispatchEvent($evt, 'beforeTransportStarted');
if ($evt->bubbleCancelled()) {
return;
}
}
try {
$this->buffer->initialize($this->getBufferParams());
} catch (Swift_TransportException $e) {
$this->throwException($e);
}
$this->readGreeting();
$this->doHeloCommand();
if ($evt) {
$this->eventDispatcher->dispatchEvent($evt, 'transportStarted');
}
$this->started = true;
}
}
/**
* Test if an SMTP connection has been established.
*
* @return bool
*/
public function isStarted()
{
return $this->started;
}
/**
* Send the given Message.
*
* Recipient/sender data will be retrieved from the Message API.
* The return value is the number of recipients who were accepted for delivery.
*
* @param string[] $failedRecipients An array of failures by-reference
*
* @return int
*/
public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
{
if (!$this->isStarted()) {
$this->start();
}
$sent = 0;
$failedRecipients = (array) $failedRecipients;
if ($evt = $this->eventDispatcher->createSendEvent($this, $message)) {
$this->eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
if ($evt->bubbleCancelled()) {
return 0;
}
}
if (!$reversePath = $this->getReversePath($message)) {
$this->throwException(new Swift_TransportException('Cannot send message without a sender address'));
}
$to = (array) $message->getTo();
$cc = (array) $message->getCc();
$bcc = (array) $message->getBcc();
$tos = array_merge($to, $cc, $bcc);
$message->setBcc([]);
try {
$sent += $this->sendTo($message, $reversePath, $tos, $failedRecipients);
} finally {
$message->setBcc($bcc);
}
if ($evt) {
if ($sent == \count($to) + \count($cc) + \count($bcc)) {
$evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
} elseif ($sent > 0) {
$evt->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE);
} else {
$evt->setResult(Swift_Events_SendEvent::RESULT_FAILED);
}
$evt->setFailedRecipients($failedRecipients);
$this->eventDispatcher->dispatchEvent($evt, 'sendPerformed');
}
$message->generateId(); //Make sure a new Message ID is used
return $sent;
}
/**
* Stop the SMTP connection.
*/
public function stop()
{
if ($this->started) {
if ($evt = $this->eventDispatcher->createTransportChangeEvent($this)) {
$this->eventDispatcher->dispatchEvent($evt, 'beforeTransportStopped');
if ($evt->bubbleCancelled()) {
return;
}
}
try {
$this->executeCommand("QUIT\r\n", [221]);
} catch (Swift_TransportException $e) {
}
try {
$this->buffer->terminate();
if ($evt) {
$this->eventDispatcher->dispatchEvent($evt, 'transportStopped');
}
} catch (Swift_TransportException $e) {
$this->throwException($e);
}
}
$this->started = false;
}
/**
* {@inheritdoc}
*/
public function ping()
{
try {
if (!$this->isStarted()) {
$this->start();
}
$this->executeCommand("NOOP\r\n", [250]);
} catch (Swift_TransportException $e) {
try {
$this->stop();
} catch (Swift_TransportException $e) {
}
return false;
}
return true;
}
/**
* Register a plugin.
*/
public function registerPlugin(Swift_Events_EventListener $plugin)
{
$this->eventDispatcher->bindEventListener($plugin);
}
/**
* Reset the current mail transaction.
*/
public function reset()
{
$this->executeCommand("RSET\r\n", [250], $failures, true);
}
/**
* Get the IoBuffer where read/writes are occurring.
*
* @return Swift_Transport_IoBuffer
*/
public function getBuffer()
{
return $this->buffer;
}
/**
* Run a command against the buffer, expecting the given response codes.
*
* If no response codes are given, the response will not be validated.
* If codes are given, an exception will be thrown on an invalid response.
* If the command is RCPT TO, and the pipeline is non-empty, no exception
* will be thrown; instead the failing address is added to $failures.
*
* @param string $command
* @param int[] $codes
* @param string[] $failures An array of failures by-reference
* @param bool $pipeline Do not wait for response
* @param string $address the address, if command is RCPT TO
*
* @return string|null The server response, or null if pipelining is enabled
*/
public function executeCommand($command, $codes = [], &$failures = null, $pipeline = false, $address = null)
{
$failures = (array) $failures;
$seq = $this->buffer->write($command);
if ($evt = $this->eventDispatcher->createCommandEvent($this, $command, $codes)) {
$this->eventDispatcher->dispatchEvent($evt, 'commandSent');
}
$this->pipeline[] = [$command, $seq, $codes, $address];
if ($pipeline && $this->pipelining) {
return null;
}
$response = null;
while ($this->pipeline) {
list($command, $seq, $codes, $address) = array_shift($this->pipeline);
$response = $this->getFullResponse($seq);
try {
$this->assertResponseCode($response, $codes);
} catch (Swift_TransportException $e) {
if ($this->pipeline && $address) {
$failures[] = $address;
} else {
$this->throwException($e);
}
}
}
return $response;
}
/** Read the opening SMTP greeting */
protected function readGreeting()
{
$this->assertResponseCode($this->getFullResponse(0), [220]);
}
/** Send the HELO welcome */
protected function doHeloCommand()
{
$this->executeCommand(
sprintf("HELO %s\r\n", $this->domain), [250]
);
}
/** Send the MAIL FROM command */
protected function doMailFromCommand($address)
{
$address = $this->addressEncoder->encodeString($address);
$this->executeCommand(
sprintf("MAIL FROM:<%s>\r\n", $address), [250], $failures, true
);
}
/** Send the RCPT TO command */
protected function doRcptToCommand($address)
{
$address = $this->addressEncoder->encodeString($address);
$this->executeCommand(
sprintf("RCPT TO:<%s>\r\n", $address), [250, 251, 252], $failures, true, $address
);
}
/** Send the DATA command */
protected function doDataCommand(&$failedRecipients)
{
$this->executeCommand("DATA\r\n", [354], $failedRecipients);
}
/** Stream the contents of the message over the buffer */
protected function streamMessage(Swift_Mime_SimpleMessage $message)
{
$this->buffer->setWriteTranslations(["\r\n." => "\r\n.."]);
try {
$message->toByteStream($this->buffer);
$this->buffer->flushBuffers();
} catch (Swift_TransportException $e) {
$this->throwException($e);
}
$this->buffer->setWriteTranslations([]);
$this->executeCommand("\r\n.\r\n", [250]);
}
/** Determine the best-use reverse path for this message */
protected function getReversePath(Swift_Mime_SimpleMessage $message)
{
$return = $message->getReturnPath();
$sender = $message->getSender();
$from = $message->getFrom();
$path = null;
if (!empty($return)) {
$path = $return;
} elseif (!empty($sender)) {
// Don't use array_keys
reset($sender); // Reset Pointer to first pos
$path = key($sender); // Get key
} elseif (!empty($from)) {
reset($from); // Reset Pointer to first pos
$path = key($from); // Get key
}
return $path;
}
/** Throw a TransportException, first sending it to any listeners */
protected function throwException(Swift_TransportException $e)
{
if ($evt = $this->eventDispatcher->createTransportExceptionEvent($this, $e)) {
$this->eventDispatcher->dispatchEvent($evt, 'exceptionThrown');
if (!$evt->bubbleCancelled()) {
throw $e;
}
} else {
throw $e;
}
}
/** Throws an Exception if a response code is incorrect */
protected function assertResponseCode($response, $wanted)
{
if (!$response) {
$this->throwException(new Swift_TransportException('Expected response code '.implode('/', $wanted).' but got an empty response'));
}
list($code) = sscanf($response, '%3d');
$valid = (empty($wanted) || \in_array($code, $wanted));
if ($evt = $this->eventDispatcher->createResponseEvent($this, $response,
$valid)) {
$this->eventDispatcher->dispatchEvent($evt, 'responseReceived');
}
if (!$valid) {
$this->throwException(new Swift_TransportException('Expected response code '.implode('/', $wanted).' but got code "'.$code.'", with message "'.$response.'"', $code));
}
}
/** Get an entire multi-line response using its sequence number */
protected function getFullResponse($seq)
{
$response = '';
try {
do {
$line = $this->buffer->readLine($seq);
$response .= $line;
} while (null !== $line && false !== $line && ' ' != $line[3]);
} catch (Swift_TransportException $e) {
$this->throwException($e);
} catch (Swift_IoException $e) {
$this->throwException(new Swift_TransportException($e->getMessage(), 0, $e));
}
return $response;
}
/** Send an email to the given recipients from the given reverse path */
private function doMailTransaction($message, $reversePath, array $recipients, array &$failedRecipients)
{
$sent = 0;
$this->doMailFromCommand($reversePath);
foreach ($recipients as $forwardPath) {
try {
$this->doRcptToCommand($forwardPath);
++$sent;
} catch (Swift_TransportException $e) {
$failedRecipients[] = $forwardPath;
} catch (Swift_AddressEncoderException $e) {
$failedRecipients[] = $forwardPath;
}
}
if (0 != $sent) {
$sent += \count($failedRecipients);
$this->doDataCommand($failedRecipients);
$sent -= \count($failedRecipients);
$this->streamMessage($message);
} else {
$this->reset();
}
return $sent;
}
/** Send a message to the given To: recipients */
private function sendTo(Swift_Mime_SimpleMessage $message, $reversePath, array $to, array &$failedRecipients)
{
if (empty($to)) {
return 0;
}
return $this->doMailTransaction($message, $reversePath, array_keys($to),
$failedRecipients);
}
/**
* Destructor.
*/
public function __destruct()
{
try {
$this->stop();
} catch (Exception $e) {
}
}
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
}

View File

@ -0,0 +1,75 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Handles CRAM-MD5 authentication.
*
* @author Chris Corbyn
*/
class Swift_Transport_Esmtp_Auth_CramMd5Authenticator implements Swift_Transport_Esmtp_Authenticator
{
/**
* Get the name of the AUTH mechanism this Authenticator handles.
*
* @return string
*/
public function getAuthKeyword()
{
return 'CRAM-MD5';
}
/**
* {@inheritdoc}
*/
public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password)
{
try {
$challenge = $agent->executeCommand("AUTH CRAM-MD5\r\n", [334]);
$challenge = base64_decode(substr($challenge, 4));
$message = base64_encode(
$username.' '.$this->getResponse($password, $challenge)
);
$agent->executeCommand(sprintf("%s\r\n", $message), [235]);
return true;
} catch (Swift_TransportException $e) {
$agent->executeCommand("RSET\r\n", [250]);
throw $e;
}
}
/**
* Generate a CRAM-MD5 response from a server challenge.
*
* @param string $secret
* @param string $challenge
*
* @return string
*/
private function getResponse($secret, $challenge)
{
if (\strlen($secret) > 64) {
$secret = pack('H32', md5($secret));
}
if (\strlen($secret) < 64) {
$secret = str_pad($secret, 64, \chr(0));
}
$k_ipad = substr($secret, 0, 64) ^ str_repeat(\chr(0x36), 64);
$k_opad = substr($secret, 0, 64) ^ str_repeat(\chr(0x5C), 64);
$inner = pack('H32', md5($k_ipad.$challenge));
$digest = md5($k_opad.$inner);
return $digest;
}
}

View File

@ -0,0 +1,45 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Handles LOGIN authentication.
*
* @author Chris Corbyn
*/
class Swift_Transport_Esmtp_Auth_LoginAuthenticator implements Swift_Transport_Esmtp_Authenticator
{
/**
* Get the name of the AUTH mechanism this Authenticator handles.
*
* @return string
*/
public function getAuthKeyword()
{
return 'LOGIN';
}
/**
* {@inheritdoc}
*/
public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password)
{
try {
$agent->executeCommand("AUTH LOGIN\r\n", [334]);
$agent->executeCommand(sprintf("%s\r\n", base64_encode($username)), [334]);
$agent->executeCommand(sprintf("%s\r\n", base64_encode($password)), [235]);
return true;
} catch (Swift_TransportException $e) {
$agent->executeCommand("RSET\r\n", [250]);
throw $e;
}
}
}

View File

@ -0,0 +1,681 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* This authentication is for Exchange servers. We support version 1 & 2.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Handles NTLM authentication.
*
* @author Ward Peeters <ward@coding-tech.com>
*/
class Swift_Transport_Esmtp_Auth_NTLMAuthenticator implements Swift_Transport_Esmtp_Authenticator
{
const NTLMSIG = "NTLMSSP\x00";
const DESCONST = 'KGS!@#$%';
/**
* Get the name of the AUTH mechanism this Authenticator handles.
*
* @return string
*/
public function getAuthKeyword()
{
return 'NTLM';
}
/**
* {@inheritdoc}
*
* @throws \LogicException
*/
public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password)
{
if (!\function_exists('openssl_encrypt')) {
throw new LogicException('The OpenSSL extension must be enabled to use the NTLM authenticator.');
}
if (!\function_exists('bcmul')) {
throw new LogicException('The BCMath functions must be enabled to use the NTLM authenticator.');
}
try {
// execute AUTH command and filter out the code at the beginning
// AUTH NTLM xxxx
$response = base64_decode(substr(trim($this->sendMessage1($agent)), 4));
// extra parameters for our unit cases
$timestamp = \func_num_args() > 3 ? func_get_arg(3) : $this->getCorrectTimestamp(bcmul(microtime(true), '1000'));
$client = \func_num_args() > 4 ? func_get_arg(4) : random_bytes(8);
// Message 3 response
$this->sendMessage3($response, $username, $password, $timestamp, $client, $agent);
return true;
} catch (Swift_TransportException $e) {
$agent->executeCommand("RSET\r\n", [250]);
throw $e;
}
}
protected function si2bin($si, $bits = 32)
{
$bin = null;
if ($si >= -2 ** ($bits - 1) && ($si <= 2 ** ($bits - 1))) {
// positive or zero
if ($si >= 0) {
$bin = base_convert($si, 10, 2);
// pad to $bits bit
$bin_length = \strlen($bin);
if ($bin_length < $bits) {
$bin = str_repeat('0', $bits - $bin_length).$bin;
}
} else {
// negative
$si = -$si - 2 ** $bits;
$bin = base_convert($si, 10, 2);
$bin_length = \strlen($bin);
if ($bin_length > $bits) {
$bin = str_repeat('1', $bits - $bin_length).$bin;
}
}
}
return $bin;
}
/**
* Send our auth message and returns the response.
*
* @return string SMTP Response
*/
protected function sendMessage1(Swift_Transport_SmtpAgent $agent)
{
$message = $this->createMessage1();
return $agent->executeCommand(sprintf("AUTH %s %s\r\n", $this->getAuthKeyword(), base64_encode($message)), [334]);
}
/**
* Fetch all details of our response (message 2).
*
* @param string $response
*
* @return array our response parsed
*/
protected function parseMessage2($response)
{
$responseHex = bin2hex($response);
$length = floor(hexdec(substr($responseHex, 28, 4)) / 256) * 2;
$offset = floor(hexdec(substr($responseHex, 32, 4)) / 256) * 2;
$challenge = hex2bin(substr($responseHex, 48, 16));
$context = hex2bin(substr($responseHex, 64, 16));
$targetInfoH = hex2bin(substr($responseHex, 80, 16));
$targetName = hex2bin(substr($responseHex, $offset, $length));
$offset = floor(hexdec(substr($responseHex, 88, 4)) / 256) * 2;
$targetInfoBlock = substr($responseHex, $offset);
list($domainName, $serverName, $DNSDomainName, $DNSServerName, $terminatorByte) = $this->readSubBlock($targetInfoBlock);
return [
$challenge,
$context,
$targetInfoH,
$targetName,
$domainName,
$serverName,
$DNSDomainName,
$DNSServerName,
hex2bin($targetInfoBlock),
$terminatorByte,
];
}
/**
* Read the blob information in from message2.
*
* @return array
*/
protected function readSubBlock($block)
{
// remove terminatorByte cause it's always the same
$block = substr($block, 0, -8);
$length = \strlen($block);
$offset = 0;
$data = [];
while ($offset < $length) {
$blockLength = hexdec(substr(substr($block, $offset, 8), -4)) / 256;
$offset += 8;
$data[] = hex2bin(substr($block, $offset, $blockLength * 2));
$offset += $blockLength * 2;
}
if (3 == \count($data)) {
$data[] = $data[2];
$data[2] = '';
}
$data[] = $this->createByte('00');
return $data;
}
/**
* Send our final message with all our data.
*
* @param string $response Message 1 response (message 2)
* @param string $username
* @param string $password
* @param string $timestamp
* @param string $client
* @param bool $v2 Use version2 of the protocol
*
* @return string
*/
protected function sendMessage3($response, $username, $password, $timestamp, $client, Swift_Transport_SmtpAgent $agent, $v2 = true)
{
list($domain, $username) = $this->getDomainAndUsername($username);
//$challenge, $context, $targetInfoH, $targetName, $domainName, $workstation, $DNSDomainName, $DNSServerName, $blob, $ter
list($challenge, , , , , $workstation, , , $blob) = $this->parseMessage2($response);
if (!$v2) {
// LMv1
$lmResponse = $this->createLMPassword($password, $challenge);
// NTLMv1
$ntlmResponse = $this->createNTLMPassword($password, $challenge);
} else {
// LMv2
$lmResponse = $this->createLMv2Password($password, $username, $domain, $challenge, $client);
// NTLMv2
$ntlmResponse = $this->createNTLMv2Hash($password, $username, $domain, $challenge, $blob, $timestamp, $client);
}
$message = $this->createMessage3($domain, $username, $workstation, $lmResponse, $ntlmResponse);
return $agent->executeCommand(sprintf("%s\r\n", base64_encode($message)), [235]);
}
/**
* Create our message 1.
*
* @return string
*/
protected function createMessage1()
{
return self::NTLMSIG
.$this->createByte('01') // Message 1
.$this->createByte('0702'); // Flags
}
/**
* Create our message 3.
*
* @param string $domain
* @param string $username
* @param string $workstation
* @param string $lmResponse
* @param string $ntlmResponse
*
* @return string
*/
protected function createMessage3($domain, $username, $workstation, $lmResponse, $ntlmResponse)
{
// Create security buffers
$domainSec = $this->createSecurityBuffer($domain, 64);
$domainInfo = $this->readSecurityBuffer(bin2hex($domainSec));
$userSec = $this->createSecurityBuffer($username, ($domainInfo[0] + $domainInfo[1]) / 2);
$userInfo = $this->readSecurityBuffer(bin2hex($userSec));
$workSec = $this->createSecurityBuffer($workstation, ($userInfo[0] + $userInfo[1]) / 2);
$workInfo = $this->readSecurityBuffer(bin2hex($workSec));
$lmSec = $this->createSecurityBuffer($lmResponse, ($workInfo[0] + $workInfo[1]) / 2, true);
$lmInfo = $this->readSecurityBuffer(bin2hex($lmSec));
$ntlmSec = $this->createSecurityBuffer($ntlmResponse, ($lmInfo[0] + $lmInfo[1]) / 2, true);
return self::NTLMSIG
.$this->createByte('03') // TYPE 3 message
.$lmSec // LM response header
.$ntlmSec // NTLM response header
.$domainSec // Domain header
.$userSec // User header
.$workSec // Workstation header
.$this->createByte('000000009a', 8) // session key header (empty)
.$this->createByte('01020000') // FLAGS
.$this->convertTo16bit($domain) // domain name
.$this->convertTo16bit($username) // username
.$this->convertTo16bit($workstation) // workstation
.$lmResponse
.$ntlmResponse;
}
/**
* @param string $timestamp Epoch timestamp in microseconds
* @param string $client Random bytes
* @param string $targetInfo
*
* @return string
*/
protected function createBlob($timestamp, $client, $targetInfo)
{
return $this->createByte('0101')
.$this->createByte('00')
.$timestamp
.$client
.$this->createByte('00')
.$targetInfo
.$this->createByte('00');
}
/**
* Get domain and username from our username.
*
* @example DOMAIN\username
*
* @param string $name
*
* @return array
*/
protected function getDomainAndUsername($name)
{
if (false !== strpos($name, '\\')) {
return explode('\\', $name);
}
if (false !== strpos($name, '@')) {
list($user, $domain) = explode('@', $name);
return [$domain, $user];
}
// no domain passed
return ['', $name];
}
/**
* Create LMv1 response.
*
* @param string $password
* @param string $challenge
*
* @return string
*/
protected function createLMPassword($password, $challenge)
{
// FIRST PART
$password = $this->createByte(strtoupper($password), 14, false);
list($key1, $key2) = str_split($password, 7);
$desKey1 = $this->createDesKey($key1);
$desKey2 = $this->createDesKey($key2);
$constantDecrypt = $this->createByte($this->desEncrypt(self::DESCONST, $desKey1).$this->desEncrypt(self::DESCONST, $desKey2), 21, false);
// SECOND PART
list($key1, $key2, $key3) = str_split($constantDecrypt, 7);
$desKey1 = $this->createDesKey($key1);
$desKey2 = $this->createDesKey($key2);
$desKey3 = $this->createDesKey($key3);
return $this->desEncrypt($challenge, $desKey1).$this->desEncrypt($challenge, $desKey2).$this->desEncrypt($challenge, $desKey3);
}
/**
* Create NTLMv1 response.
*
* @param string $password
* @param string $challenge
*
* @return string
*/
protected function createNTLMPassword($password, $challenge)
{
// FIRST PART
$ntlmHash = $this->createByte($this->md4Encrypt($password), 21, false);
list($key1, $key2, $key3) = str_split($ntlmHash, 7);
$desKey1 = $this->createDesKey($key1);
$desKey2 = $this->createDesKey($key2);
$desKey3 = $this->createDesKey($key3);
return $this->desEncrypt($challenge, $desKey1).$this->desEncrypt($challenge, $desKey2).$this->desEncrypt($challenge, $desKey3);
}
/**
* Convert a normal timestamp to a tenth of a microtime epoch time.
*
* @param string $time
*
* @return string
*/
protected function getCorrectTimestamp($time)
{
// Get our timestamp (tricky!)
$time = number_format($time, 0, '.', ''); // save microtime to string
$time = bcadd($time, '11644473600000', 0); // add epoch time
$time = bcmul($time, 10000, 0); // tenths of a microsecond.
$binary = $this->si2bin($time, 64); // create 64 bit binary string
$timestamp = '';
for ($i = 0; $i < 8; ++$i) {
$timestamp .= \chr(bindec(substr($binary, -(($i + 1) * 8), 8)));
}
return $timestamp;
}
/**
* Create LMv2 response.
*
* @param string $password
* @param string $username
* @param string $domain
* @param string $challenge NTLM Challenge
* @param string $client Random string
*
* @return string
*/
protected function createLMv2Password($password, $username, $domain, $challenge, $client)
{
$lmPass = '00'; // by default 00
// if $password > 15 than we can't use this method
if (\strlen($password) <= 15) {
$ntlmHash = $this->md4Encrypt($password);
$ntml2Hash = $this->md5Encrypt($ntlmHash, $this->convertTo16bit(strtoupper($username).$domain));
$lmPass = bin2hex($this->md5Encrypt($ntml2Hash, $challenge.$client).$client);
}
return $this->createByte($lmPass, 24);
}
/**
* Create NTLMv2 response.
*
* @param string $password
* @param string $username
* @param string $domain
* @param string $challenge Hex values
* @param string $targetInfo Hex values
* @param string $timestamp
* @param string $client Random bytes
*
* @return string
*
* @see http://davenport.sourceforge.net/ntlm.html#theNtlmResponse
*/
protected function createNTLMv2Hash($password, $username, $domain, $challenge, $targetInfo, $timestamp, $client)
{
$ntlmHash = $this->md4Encrypt($password);
$ntml2Hash = $this->md5Encrypt($ntlmHash, $this->convertTo16bit(strtoupper($username).$domain));
// create blob
$blob = $this->createBlob($timestamp, $client, $targetInfo);
$ntlmv2Response = $this->md5Encrypt($ntml2Hash, $challenge.$blob);
return $ntlmv2Response.$blob;
}
protected function createDesKey($key)
{
$material = [bin2hex($key[0])];
$len = \strlen($key);
for ($i = 1; $i < $len; ++$i) {
list($high, $low) = str_split(bin2hex($key[$i]));
$v = $this->castToByte(\ord($key[$i - 1]) << (7 + 1 - $i) | $this->uRShift(hexdec(dechex(hexdec($high) & 0xf).dechex(hexdec($low) & 0xf)), $i));
$material[] = str_pad(substr(dechex($v), -2), 2, '0', STR_PAD_LEFT); // cast to byte
}
$material[] = str_pad(substr(dechex($this->castToByte(\ord($key[6]) << 1)), -2), 2, '0');
// odd parity
foreach ($material as $k => $v) {
$b = $this->castToByte(hexdec($v));
$needsParity = 0 == (($this->uRShift($b, 7) ^ $this->uRShift($b, 6) ^ $this->uRShift($b, 5)
^ $this->uRShift($b, 4) ^ $this->uRShift($b, 3) ^ $this->uRShift($b, 2)
^ $this->uRShift($b, 1)) & 0x01);
list($high, $low) = str_split($v);
if ($needsParity) {
$material[$k] = dechex(hexdec($high) | 0x0).dechex(hexdec($low) | 0x1);
} else {
$material[$k] = dechex(hexdec($high) & 0xf).dechex(hexdec($low) & 0xe);
}
}
return hex2bin(implode('', $material));
}
/** HELPER FUNCTIONS */
/**
* Create our security buffer depending on length and offset.
*
* @param string $value Value we want to put in
* @param int $offset start of value
* @param bool $is16 Do we 16bit string or not?
*
* @return string
*/
protected function createSecurityBuffer($value, $offset, $is16 = false)
{
$length = \strlen(bin2hex($value));
$length = $is16 ? $length / 2 : $length;
$length = $this->createByte(str_pad(dechex($length), 2, '0', STR_PAD_LEFT), 2);
return $length.$length.$this->createByte(dechex($offset), 4);
}
/**
* Read our security buffer to fetch length and offset of our value.
*
* @param string $value Securitybuffer in hex
*
* @return array array with length and offset
*/
protected function readSecurityBuffer($value)
{
$length = floor(hexdec(substr($value, 0, 4)) / 256) * 2;
$offset = floor(hexdec(substr($value, 8, 4)) / 256) * 2;
return [$length, $offset];
}
/**
* Cast to byte java equivalent to (byte).
*
* @param int $v
*
* @return int
*/
protected function castToByte($v)
{
return (($v + 128) % 256) - 128;
}
/**
* Java unsigned right bitwise
* $a >>> $b.
*
* @param int $a
* @param int $b
*
* @return int
*/
protected function uRShift($a, $b)
{
if (0 == $b) {
return $a;
}
return ($a >> $b) & ~(1 << (8 * PHP_INT_SIZE - 1) >> ($b - 1));
}
/**
* Right padding with 0 to certain length.
*
* @param string $input
* @param int $bytes Length of bytes
* @param bool $isHex Did we provided hex value
*
* @return string
*/
protected function createByte($input, $bytes = 4, $isHex = true)
{
if ($isHex) {
$byte = hex2bin(str_pad($input, $bytes * 2, '00'));
} else {
$byte = str_pad($input, $bytes, "\x00");
}
return $byte;
}
/** ENCRYPTION ALGORITHMS */
/**
* DES Encryption.
*
* @param string $value An 8-byte string
* @param string $key
*
* @return string
*/
protected function desEncrypt($value, $key)
{
return substr(openssl_encrypt($value, 'DES-ECB', $key, \OPENSSL_RAW_DATA), 0, 8);
}
/**
* MD5 Encryption.
*
* @param string $key Encryption key
* @param string $msg Message to encrypt
*
* @return string
*/
protected function md5Encrypt($key, $msg)
{
$blocksize = 64;
if (\strlen($key) > $blocksize) {
$key = pack('H*', md5($key));
}
$key = str_pad($key, $blocksize, "\0");
$ipadk = $key ^ str_repeat("\x36", $blocksize);
$opadk = $key ^ str_repeat("\x5c", $blocksize);
return pack('H*', md5($opadk.pack('H*', md5($ipadk.$msg))));
}
/**
* MD4 Encryption.
*
* @param string $input
*
* @return string
*
* @see https://secure.php.net/manual/en/ref.hash.php
*/
protected function md4Encrypt($input)
{
$input = $this->convertTo16bit($input);
return \function_exists('hash') ? hex2bin(hash('md4', $input)) : mhash(MHASH_MD4, $input);
}
/**
* Convert UTF-8 to UTF-16.
*
* @param string $input
*
* @return string
*/
protected function convertTo16bit($input)
{
return iconv('UTF-8', 'UTF-16LE', $input);
}
/**
* @param string $message
*/
protected function debug($message)
{
$message = bin2hex($message);
$messageId = substr($message, 16, 8);
echo substr($message, 0, 16)." NTLMSSP Signature<br />\n";
echo $messageId." Type Indicator<br />\n";
if ('02000000' == $messageId) {
$map = [
'Challenge',
'Context',
'Target Information Security Buffer',
'Target Name Data',
'NetBIOS Domain Name',
'NetBIOS Server Name',
'DNS Domain Name',
'DNS Server Name',
'BLOB',
'Target Information Terminator',
];
$data = $this->parseMessage2(hex2bin($message));
foreach ($map as $key => $value) {
echo bin2hex($data[$key]).' - '.$data[$key].' ||| '.$value."<br />\n";
}
} elseif ('03000000' == $messageId) {
$i = 0;
$data[$i++] = substr($message, 24, 16);
list($lmLength, $lmOffset) = $this->readSecurityBuffer($data[$i - 1]);
$data[$i++] = substr($message, 40, 16);
list($ntmlLength, $ntmlOffset) = $this->readSecurityBuffer($data[$i - 1]);
$data[$i++] = substr($message, 56, 16);
list($targetLength, $targetOffset) = $this->readSecurityBuffer($data[$i - 1]);
$data[$i++] = substr($message, 72, 16);
list($userLength, $userOffset) = $this->readSecurityBuffer($data[$i - 1]);
$data[$i++] = substr($message, 88, 16);
list($workLength, $workOffset) = $this->readSecurityBuffer($data[$i - 1]);
$data[$i++] = substr($message, 104, 16);
$data[$i++] = substr($message, 120, 8);
$data[$i++] = substr($message, $targetOffset, $targetLength);
$data[$i++] = substr($message, $userOffset, $userLength);
$data[$i++] = substr($message, $workOffset, $workLength);
$data[$i++] = substr($message, $lmOffset, $lmLength);
$data[$i] = substr($message, $ntmlOffset, $ntmlLength);
$map = [
'LM Response Security Buffer',
'NTLM Response Security Buffer',
'Target Name Security Buffer',
'User Name Security Buffer',
'Workstation Name Security Buffer',
'Session Key Security Buffer',
'Flags',
'Target Name Data',
'User Name Data',
'Workstation Name Data',
'LM Response Data',
'NTLM Response Data',
];
foreach ($map as $key => $value) {
echo $data[$key].' - '.hex2bin($data[$key]).' ||| '.$value."<br />\n";
}
}
echo '<br /><br />';
}
}

View File

@ -0,0 +1,44 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Handles PLAIN authentication.
*
* @author Chris Corbyn
*/
class Swift_Transport_Esmtp_Auth_PlainAuthenticator implements Swift_Transport_Esmtp_Authenticator
{
/**
* Get the name of the AUTH mechanism this Authenticator handles.
*
* @return string
*/
public function getAuthKeyword()
{
return 'PLAIN';
}
/**
* {@inheritdoc}
*/
public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password)
{
try {
$message = base64_encode($username.\chr(0).$username.\chr(0).$password);
$agent->executeCommand(sprintf("AUTH PLAIN %s\r\n", $message), [235]);
return true;
} catch (Swift_TransportException $e) {
$agent->executeCommand("RSET\r\n", [250]);
throw $e;
}
}
}

View File

@ -0,0 +1,64 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Handles XOAUTH2 authentication.
*
* Example:
* <code>
* $transport = (new Swift_SmtpTransport('smtp.gmail.com', 587, 'tls'))
* ->setAuthMode('XOAUTH2')
* ->setUsername('YOUR_EMAIL_ADDRESS')
* ->setPassword('YOUR_ACCESS_TOKEN');
* </code>
*
* @author xu.li<AthenaLightenedMyPath@gmail.com>
*
* @see https://developers.google.com/google-apps/gmail/xoauth2_protocol
*/
class Swift_Transport_Esmtp_Auth_XOAuth2Authenticator implements Swift_Transport_Esmtp_Authenticator
{
/**
* Get the name of the AUTH mechanism this Authenticator handles.
*
* @return string
*/
public function getAuthKeyword()
{
return 'XOAUTH2';
}
/**
* {@inheritdoc}
*/
public function authenticate(Swift_Transport_SmtpAgent $agent, $email, $token)
{
try {
$param = $this->constructXOAuth2Params($email, $token);
$agent->executeCommand('AUTH XOAUTH2 '.$param."\r\n", [235]);
return true;
} catch (Swift_TransportException $e) {
$agent->executeCommand("RSET\r\n", [250]);
throw $e;
}
}
/**
* Construct the auth parameter.
*
* @see https://developers.google.com/google-apps/gmail/xoauth2_protocol#the_sasl_xoauth2_mechanism
*/
protected function constructXOAuth2Params($email, $token)
{
return base64_encode("user=$email\1auth=Bearer $token\1\1");
}
}

View File

@ -0,0 +1,268 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* An ESMTP handler for AUTH support (RFC 5248).
*
* @author Chris Corbyn
*/
class Swift_Transport_Esmtp_AuthHandler implements Swift_Transport_EsmtpHandler
{
/**
* Authenticators available to process the request.
*
* @var Swift_Transport_Esmtp_Authenticator[]
*/
private $authenticators = [];
/**
* The username for authentication.
*
* @var string
*/
private $username;
/**
* The password for authentication.
*
* @var string
*/
private $password;
/**
* The auth mode for authentication.
*
* @var string
*/
private $auth_mode;
/**
* The ESMTP AUTH parameters available.
*
* @var string[]
*/
private $esmtpParams = [];
/**
* Create a new AuthHandler with $authenticators for support.
*
* @param Swift_Transport_Esmtp_Authenticator[] $authenticators
*/
public function __construct(array $authenticators)
{
$this->setAuthenticators($authenticators);
}
/**
* Set the Authenticators which can process a login request.
*
* @param Swift_Transport_Esmtp_Authenticator[] $authenticators
*/
public function setAuthenticators(array $authenticators)
{
$this->authenticators = $authenticators;
}
/**
* Get the Authenticators which can process a login request.
*
* @return Swift_Transport_Esmtp_Authenticator[]
*/
public function getAuthenticators()
{
return $this->authenticators;
}
/**
* Set the username to authenticate with.
*
* @param string $username
*/
public function setUsername($username)
{
$this->username = $username;
}
/**
* Get the username to authenticate with.
*
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* Set the password to authenticate with.
*
* @param string $password
*/
public function setPassword($password)
{
$this->password = $password;
}
/**
* Get the password to authenticate with.
*
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Set the auth mode to use to authenticate.
*
* @param string $mode
*/
public function setAuthMode($mode)
{
$this->auth_mode = $mode;
}
/**
* Get the auth mode to use to authenticate.
*
* @return string
*/
public function getAuthMode()
{
return $this->auth_mode;
}
/**
* Get the name of the ESMTP extension this handles.
*
* @return string
*/
public function getHandledKeyword()
{
return 'AUTH';
}
/**
* Set the parameters which the EHLO greeting indicated.
*
* @param string[] $parameters
*/
public function setKeywordParams(array $parameters)
{
$this->esmtpParams = $parameters;
}
/**
* Runs immediately after a EHLO has been issued.
*
* @param Swift_Transport_SmtpAgent $agent to read/write
*/
public function afterEhlo(Swift_Transport_SmtpAgent $agent)
{
if ($this->username) {
$count = 0;
$errors = [];
foreach ($this->getAuthenticatorsForAgent() as $authenticator) {
if (\in_array(strtolower($authenticator->getAuthKeyword()), array_map('strtolower', $this->esmtpParams))) {
++$count;
try {
if ($authenticator->authenticate($agent, $this->username, $this->password)) {
return;
}
} catch (Swift_TransportException $e) {
// keep the error message, but tries the other authenticators
$errors[] = [$authenticator->getAuthKeyword(), $e->getMessage()];
}
}
}
$message = 'Failed to authenticate on SMTP server with username "'.$this->username.'" using '.$count.' possible authenticators.';
foreach ($errors as $error) {
$message .= ' Authenticator '.$error[0].' returned '.$error[1].'.';
}
throw new Swift_TransportException($message);
}
}
/**
* Not used.
*/
public function getMailParams()
{
return [];
}
/**
* Not used.
*/
public function getRcptParams()
{
return [];
}
/**
* Not used.
*/
public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = [], &$failedRecipients = null, &$stop = false)
{
}
/**
* Returns +1, -1 or 0 according to the rules for usort().
*
* This method is called to ensure extensions can be execute in an appropriate order.
*
* @param string $esmtpKeyword to compare with
*
* @return int
*/
public function getPriorityOver($esmtpKeyword)
{
return 0;
}
/**
* Returns an array of method names which are exposed to the Esmtp class.
*
* @return string[]
*/
public function exposeMixinMethods()
{
return ['setUsername', 'getUsername', 'setPassword', 'getPassword', 'setAuthMode', 'getAuthMode'];
}
/**
* Not used.
*/
public function resetState()
{
}
/**
* Returns the authenticator list for the given agent.
*
* @return array
*/
protected function getAuthenticatorsForAgent()
{
if (!$mode = strtolower($this->auth_mode)) {
return $this->authenticators;
}
foreach ($this->authenticators as $authenticator) {
if (strtolower($authenticator->getAuthKeyword()) == $mode) {
return [$authenticator];
}
}
throw new Swift_TransportException('Auth mode '.$mode.' is invalid');
}
}

View File

@ -0,0 +1,36 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* An Authentication mechanism.
*
* @author Chris Corbyn
*/
interface Swift_Transport_Esmtp_Authenticator
{
/**
* Get the name of the AUTH mechanism this Authenticator handles.
*
* @return string
*/
public function getAuthKeyword();
/**
* Try to authenticate the user with $username and $password.
*
* @param string $username
* @param string $password
*
* @return bool true if authentication worked (returning false is deprecated, throw a Swift_TransportException instead)
*
* @throws Swift_TransportException Allows the message to bubble up when authentication was not successful
*/
public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password);
}

View File

@ -0,0 +1,113 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2018 Christian Schmidt
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* An ESMTP handler for 8BITMIME support (RFC 6152).
*
* 8BITMIME is required when sending 8-bit content to over SMTP, e.g. when using
* Swift_Mime_ContentEncoder_PlainContentEncoder in "8bit" mode.
*
* 8BITMIME mode is enabled unconditionally, even when sending ASCII-only
* messages, so it should only be used with an outbound SMTP server that will
* convert the message to 7-bit MIME if the next hop does not support 8BITMIME.
*
* @author Christian Schmidt
*/
class Swift_Transport_Esmtp_EightBitMimeHandler implements Swift_Transport_EsmtpHandler
{
protected $encoding;
/**
* @param string $encoding The parameter so send with the MAIL FROM command;
* either "8BITMIME" or "7BIT"
*/
public function __construct(string $encoding = '8BITMIME')
{
$this->encoding = $encoding;
}
/**
* Get the name of the ESMTP extension this handles.
*
* @return string
*/
public function getHandledKeyword()
{
return '8BITMIME';
}
/**
* Not used.
*/
public function setKeywordParams(array $parameters)
{
}
/**
* Not used.
*/
public function afterEhlo(Swift_Transport_SmtpAgent $agent)
{
}
/**
* Get params which are appended to MAIL FROM:<>.
*
* @return string[]
*/
public function getMailParams()
{
return ['BODY='.$this->encoding];
}
/**
* Not used.
*/
public function getRcptParams()
{
return [];
}
/**
* Not used.
*/
public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = [], &$failedRecipients = null, &$stop = false)
{
}
/**
* Returns +1, -1 or 0 according to the rules for usort().
*
* This method is called to ensure extensions can be execute in an appropriate order.
*
* @param string $esmtpKeyword to compare with
*
* @return int
*/
public function getPriorityOver($esmtpKeyword)
{
return 0;
}
/**
* Not used.
*/
public function exposeMixinMethods()
{
return [];
}
/**
* Not used.
*/
public function resetState()
{
}
}

View File

@ -0,0 +1,107 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2018 Christian Schmidt
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* An ESMTP handler for SMTPUTF8 support (RFC 6531).
*
* SMTPUTF8 is required when sending to email addresses containing non-ASCII
* characters in local-part (the substring before @). This handler should be
* used together with Swift_AddressEncoder_Utf8AddressEncoder.
*
* SMTPUTF8 mode is enabled unconditionally, even when sending to ASCII-only
* addresses, so it should only be used with an outbound SMTP server that will
* deliver ASCII-only messages even if the next hop does not support SMTPUTF8.
*
* @author Christian Schmidt
*/
class Swift_Transport_Esmtp_SmtpUtf8Handler implements Swift_Transport_EsmtpHandler
{
public function __construct()
{
}
/**
* Get the name of the ESMTP extension this handles.
*
* @return string
*/
public function getHandledKeyword()
{
return 'SMTPUTF8';
}
/**
* Not used.
*/
public function setKeywordParams(array $parameters)
{
}
/**
* Not used.
*/
public function afterEhlo(Swift_Transport_SmtpAgent $agent)
{
}
/**
* Get params which are appended to MAIL FROM:<>.
*
* @return string[]
*/
public function getMailParams()
{
return ['SMTPUTF8'];
}
/**
* Not used.
*/
public function getRcptParams()
{
return [];
}
/**
* Not used.
*/
public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = [], &$failedRecipients = null, &$stop = false)
{
}
/**
* Returns +1, -1 or 0 according to the rules for usort().
*
* This method is called to ensure extensions can be execute in an appropriate order.
*
* @param string $esmtpKeyword to compare with
*
* @return int
*/
public function getPriorityOver($esmtpKeyword)
{
return 0;
}
/**
* Not used.
*/
public function exposeMixinMethods()
{
return [];
}
/**
* Not used.
*/
public function resetState()
{
}
}

View File

@ -0,0 +1,86 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* An ESMTP handler.
*
* @author Chris Corbyn
*/
interface Swift_Transport_EsmtpHandler
{
/**
* Get the name of the ESMTP extension this handles.
*
* @return string
*/
public function getHandledKeyword();
/**
* Set the parameters which the EHLO greeting indicated.
*
* @param string[] $parameters
*/
public function setKeywordParams(array $parameters);
/**
* Runs immediately after a EHLO has been issued.
*
* @param Swift_Transport_SmtpAgent $agent to read/write
*/
public function afterEhlo(Swift_Transport_SmtpAgent $agent);
/**
* Get params which are appended to MAIL FROM:<>.
*
* @return string[]
*/
public function getMailParams();
/**
* Get params which are appended to RCPT TO:<>.
*
* @return string[]
*/
public function getRcptParams();
/**
* Runs when a command is due to be sent.
*
* @param Swift_Transport_SmtpAgent $agent to read/write
* @param string $command to send
* @param int[] $codes expected in response
* @param string[] $failedRecipients to collect failures
* @param bool $stop to be set true by-reference if the command is now sent
*/
public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = [], &$failedRecipients = null, &$stop = false);
/**
* Returns +1, -1 or 0 according to the rules for usort().
*
* This method is called to ensure extensions can be execute in an appropriate order.
*
* @param string $esmtpKeyword to compare with
*
* @return int
*/
public function getPriorityOver($esmtpKeyword);
/**
* Returns an array of method names which are exposed to the Esmtp class.
*
* @return string[]
*/
public function exposeMixinMethods();
/**
* Tells this handler to clear any buffers and reset its state.
*/
public function resetState();
}

View File

@ -0,0 +1,446 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Sends Messages over SMTP with ESMTP support.
*
* @author Chris Corbyn
*/
class Swift_Transport_EsmtpTransport extends Swift_Transport_AbstractSmtpTransport implements Swift_Transport_SmtpAgent
{
/**
* ESMTP extension handlers.
*
* @var Swift_Transport_EsmtpHandler[]
*/
private $handlers = [];
/**
* ESMTP capabilities.
*
* @var string[]
*/
private $capabilities = [];
/**
* Connection buffer parameters.
*
* @var array
*/
private $params = [
'protocol' => 'tcp',
'host' => 'localhost',
'port' => 25,
'timeout' => 30,
'blocking' => 1,
'tls' => false,
'type' => Swift_Transport_IoBuffer::TYPE_SOCKET,
'stream_context_options' => [],
];
/**
* Creates a new EsmtpTransport using the given I/O buffer.
*
* @param Swift_Transport_EsmtpHandler[] $extensionHandlers
* @param string $localDomain
*/
public function __construct(Swift_Transport_IoBuffer $buf, array $extensionHandlers, Swift_Events_EventDispatcher $dispatcher, $localDomain = '127.0.0.1', Swift_AddressEncoder $addressEncoder = null)
{
parent::__construct($buf, $dispatcher, $localDomain, $addressEncoder);
$this->setExtensionHandlers($extensionHandlers);
}
/**
* Set the host to connect to.
*
* Literal IPv6 addresses should be wrapped in square brackets.
*
* @param string $host
*
* @return $this
*/
public function setHost($host)
{
$this->params['host'] = $host;
return $this;
}
/**
* Get the host to connect to.
*
* @return string
*/
public function getHost()
{
return $this->params['host'];
}
/**
* Set the port to connect to.
*
* @param int $port
*
* @return $this
*/
public function setPort($port)
{
$this->params['port'] = (int) $port;
return $this;
}
/**
* Get the port to connect to.
*
* @return int
*/
public function getPort()
{
return $this->params['port'];
}
/**
* Set the connection timeout.
*
* @param int $timeout seconds
*
* @return $this
*/
public function setTimeout($timeout)
{
$this->params['timeout'] = (int) $timeout;
$this->buffer->setParam('timeout', (int) $timeout);
return $this;
}
/**
* Get the connection timeout.
*
* @return int
*/
public function getTimeout()
{
return $this->params['timeout'];
}
/**
* Set the encryption type (tls or ssl).
*
* @param string $encryption
*
* @return $this
*/
public function setEncryption($encryption)
{
$encryption = strtolower($encryption);
if ('tls' == $encryption) {
$this->params['protocol'] = 'tcp';
$this->params['tls'] = true;
} else {
$this->params['protocol'] = $encryption;
$this->params['tls'] = false;
}
return $this;
}
/**
* Get the encryption type.
*
* @return string
*/
public function getEncryption()
{
return $this->params['tls'] ? 'tls' : $this->params['protocol'];
}
/**
* Sets the stream context options.
*
* @param array $options
*
* @return $this
*/
public function setStreamOptions($options)
{
$this->params['stream_context_options'] = $options;
return $this;
}
/**
* Returns the stream context options.
*
* @return array
*/
public function getStreamOptions()
{
return $this->params['stream_context_options'];
}
/**
* Sets the source IP.
*
* IPv6 addresses should be wrapped in square brackets.
*
* @param string $source
*
* @return $this
*/
public function setSourceIp($source)
{
$this->params['sourceIp'] = $source;
return $this;
}
/**
* Returns the IP used to connect to the destination.
*
* @return string
*/
public function getSourceIp()
{
return $this->params['sourceIp'] ?? null;
}
/**
* Sets whether SMTP pipelining is enabled.
*
* By default, support is auto-detected using the PIPELINING SMTP extension.
* Use this function to override that in the unlikely event of compatibility
* issues.
*
* @param bool $enabled
*
* @return $this
*/
public function setPipelining($enabled)
{
$this->pipelining = $enabled;
return $this;
}
/**
* Returns whether SMTP pipelining is enabled.
*
* @return bool|null a boolean if pipelining is explicitly enabled or disabled,
* or null if support is auto-detected
*/
public function getPipelining()
{
return $this->pipelining;
}
/**
* Set ESMTP extension handlers.
*
* @param Swift_Transport_EsmtpHandler[] $handlers
*
* @return $this
*/
public function setExtensionHandlers(array $handlers)
{
$assoc = [];
foreach ($handlers as $handler) {
$assoc[$handler->getHandledKeyword()] = $handler;
}
uasort($assoc, function ($a, $b) {
return $a->getPriorityOver($b->getHandledKeyword());
});
$this->handlers = $assoc;
$this->setHandlerParams();
return $this;
}
/**
* Get ESMTP extension handlers.
*
* @return Swift_Transport_EsmtpHandler[]
*/
public function getExtensionHandlers()
{
return array_values($this->handlers);
}
/**
* Run a command against the buffer, expecting the given response codes.
*
* If no response codes are given, the response will not be validated.
* If codes are given, an exception will be thrown on an invalid response.
*
* @param string $command
* @param int[] $codes
* @param string[] $failures An array of failures by-reference
* @param bool $pipeline Do not wait for response
* @param string $address the address, if command is RCPT TO
*
* @return string|null The server response, or null if pipelining is enabled
*/
public function executeCommand($command, $codes = [], &$failures = null, $pipeline = false, $address = null)
{
$failures = (array) $failures;
$stopSignal = false;
$response = null;
foreach ($this->getActiveHandlers() as $handler) {
$response = $handler->onCommand(
$this, $command, $codes, $failures, $stopSignal
);
if ($stopSignal) {
return $response;
}
}
return parent::executeCommand($command, $codes, $failures, $pipeline, $address);
}
/** Mixin handling method for ESMTP handlers */
public function __call($method, $args)
{
foreach ($this->handlers as $handler) {
if (\in_array(strtolower($method),
array_map('strtolower', (array) $handler->exposeMixinMethods())
)) {
$return = \call_user_func_array([$handler, $method], $args);
// Allow fluid method calls
if (null === $return && 'set' == substr($method, 0, 3)) {
return $this;
} else {
return $return;
}
}
}
trigger_error('Call to undefined method '.$method, E_USER_ERROR);
}
/** Get the params to initialize the buffer */
protected function getBufferParams()
{
return $this->params;
}
/** Overridden to perform EHLO instead */
protected function doHeloCommand()
{
try {
$response = $this->executeCommand(
sprintf("EHLO %s\r\n", $this->domain), [250]
);
} catch (Swift_TransportException $e) {
return parent::doHeloCommand();
}
if ($this->params['tls']) {
try {
$this->executeCommand("STARTTLS\r\n", [220]);
if (!$this->buffer->startTLS()) {
throw new Swift_TransportException('Unable to connect with TLS encryption');
}
try {
$response = $this->executeCommand(
sprintf("EHLO %s\r\n", $this->domain), [250]
);
} catch (Swift_TransportException $e) {
return parent::doHeloCommand();
}
} catch (Swift_TransportException $e) {
$this->throwException($e);
}
}
$this->capabilities = $this->getCapabilities($response);
if (!isset($this->pipelining)) {
$this->pipelining = isset($this->capabilities['PIPELINING']);
}
$this->setHandlerParams();
foreach ($this->getActiveHandlers() as $handler) {
$handler->afterEhlo($this);
}
}
/** Overridden to add Extension support */
protected function doMailFromCommand($address)
{
$address = $this->addressEncoder->encodeString($address);
$handlers = $this->getActiveHandlers();
$params = [];
foreach ($handlers as $handler) {
$params = array_merge($params, (array) $handler->getMailParams());
}
$paramStr = !empty($params) ? ' '.implode(' ', $params) : '';
$this->executeCommand(
sprintf("MAIL FROM:<%s>%s\r\n", $address, $paramStr), [250], $failures, true
);
}
/** Overridden to add Extension support */
protected function doRcptToCommand($address)
{
$address = $this->addressEncoder->encodeString($address);
$handlers = $this->getActiveHandlers();
$params = [];
foreach ($handlers as $handler) {
$params = array_merge($params, (array) $handler->getRcptParams());
}
$paramStr = !empty($params) ? ' '.implode(' ', $params) : '';
$this->executeCommand(
sprintf("RCPT TO:<%s>%s\r\n", $address, $paramStr), [250, 251, 252], $failures, true, $address
);
}
/** Determine ESMTP capabilities by function group */
private function getCapabilities($ehloResponse)
{
$capabilities = [];
$ehloResponse = trim($ehloResponse);
$lines = explode("\r\n", $ehloResponse);
array_shift($lines);
foreach ($lines as $line) {
if (preg_match('/^[0-9]{3}[ -]([A-Z0-9-]+)((?:[ =].*)?)$/Di', $line, $matches)) {
$keyword = strtoupper($matches[1]);
$paramStr = strtoupper(ltrim($matches[2], ' ='));
$params = !empty($paramStr) ? explode(' ', $paramStr) : [];
$capabilities[$keyword] = $params;
}
}
return $capabilities;
}
/** Set parameters which are used by each extension handler */
private function setHandlerParams()
{
foreach ($this->handlers as $keyword => $handler) {
if (\array_key_exists($keyword, $this->capabilities)) {
$handler->setKeywordParams($this->capabilities[$keyword]);
}
}
}
/** Get ESMTP handlers which are currently ok to use */
private function getActiveHandlers()
{
$handlers = [];
foreach ($this->handlers as $keyword => $handler) {
if (\array_key_exists($keyword, $this->capabilities)) {
$handlers[] = $handler;
}
}
return $handlers;
}
}

View File

@ -0,0 +1,103 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Contains a list of redundant Transports so when one fails, the next is used.
*
* @author Chris Corbyn
*/
class Swift_Transport_FailoverTransport extends Swift_Transport_LoadBalancedTransport
{
/**
* Registered transport currently used.
*
* @var Swift_Transport
*/
private $currentTransport;
// needed as __construct is called from elsewhere explicitly
public function __construct()
{
parent::__construct();
}
/**
* {@inheritdoc}
*/
public function ping()
{
$maxTransports = \count($this->transports);
for ($i = 0; $i < $maxTransports
&& $transport = $this->getNextTransport(); ++$i) {
if ($transport->ping()) {
return true;
} else {
$this->killCurrentTransport();
}
}
return \count($this->transports) > 0;
}
/**
* Send the given Message.
*
* Recipient/sender data will be retrieved from the Message API.
* The return value is the number of recipients who were accepted for delivery.
*
* @param string[] $failedRecipients An array of failures by-reference
*
* @return int
*/
public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
{
$maxTransports = \count($this->transports);
$sent = 0;
$this->lastUsedTransport = null;
for ($i = 0; $i < $maxTransports
&& $transport = $this->getNextTransport(); ++$i) {
try {
if (!$transport->isStarted()) {
$transport->start();
}
if ($sent = $transport->send($message, $failedRecipients)) {
$this->lastUsedTransport = $transport;
return $sent;
}
} catch (Swift_TransportException $e) {
$this->killCurrentTransport();
}
}
if (0 == \count($this->transports)) {
throw new Swift_TransportException('All Transports in FailoverTransport failed, or no Transports available');
}
return $sent;
}
protected function getNextTransport()
{
if (!isset($this->currentTransport)) {
$this->currentTransport = parent::getNextTransport();
}
return $this->currentTransport;
}
protected function killCurrentTransport()
{
$this->currentTransport = null;
parent::killCurrentTransport();
}
}

View File

@ -0,0 +1,65 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Buffers input and output to a resource.
*
* @author Chris Corbyn
*/
interface Swift_Transport_IoBuffer extends Swift_InputByteStream, Swift_OutputByteStream
{
/** A socket buffer over TCP */
const TYPE_SOCKET = 0x0001;
/** A process buffer with I/O support */
const TYPE_PROCESS = 0x0010;
/**
* Perform any initialization needed, using the given $params.
*
* Parameters will vary depending upon the type of IoBuffer used.
*/
public function initialize(array $params);
/**
* Set an individual param on the buffer (e.g. switching to SSL).
*
* @param string $param
* @param mixed $value
*/
public function setParam($param, $value);
/**
* Perform any shutdown logic needed.
*/
public function terminate();
/**
* Set an array of string replacements which should be made on data written
* to the buffer.
*
* This could replace LF with CRLF for example.
*
* @param string[] $replacements
*/
public function setWriteTranslations(array $replacements);
/**
* Get a line of output (including any CRLF).
*
* The $sequence number comes from any writes and may or may not be used
* depending upon the implementation.
*
* @param int $sequence of last write to scan from
*
* @return string
*/
public function readLine($sequence);
}

View File

@ -0,0 +1,192 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Redundantly and rotationally uses several Transports when sending.
*
* @author Chris Corbyn
*/
class Swift_Transport_LoadBalancedTransport implements Swift_Transport
{
/**
* Transports which are deemed useless.
*
* @var Swift_Transport[]
*/
private $deadTransports = [];
/**
* The Transports which are used in rotation.
*
* @var Swift_Transport[]
*/
protected $transports = [];
/**
* The Transport used in the last successful send operation.
*
* @var Swift_Transport
*/
protected $lastUsedTransport = null;
// needed as __construct is called from elsewhere explicitly
public function __construct()
{
}
/**
* Set $transports to delegate to.
*
* @param Swift_Transport[] $transports
*/
public function setTransports(array $transports)
{
$this->transports = $transports;
$this->deadTransports = [];
}
/**
* Get $transports to delegate to.
*
* @return Swift_Transport[]
*/
public function getTransports()
{
return array_merge($this->transports, $this->deadTransports);
}
/**
* Get the Transport used in the last successful send operation.
*
* @return Swift_Transport
*/
public function getLastUsedTransport()
{
return $this->lastUsedTransport;
}
/**
* Test if this Transport mechanism has started.
*
* @return bool
*/
public function isStarted()
{
return \count($this->transports) > 0;
}
/**
* Start this Transport mechanism.
*/
public function start()
{
$this->transports = array_merge($this->transports, $this->deadTransports);
}
/**
* Stop this Transport mechanism.
*/
public function stop()
{
foreach ($this->transports as $transport) {
$transport->stop();
}
}
/**
* {@inheritdoc}
*/
public function ping()
{
foreach ($this->transports as $transport) {
if (!$transport->ping()) {
$this->killCurrentTransport();
}
}
return \count($this->transports) > 0;
}
/**
* Send the given Message.
*
* Recipient/sender data will be retrieved from the Message API.
* The return value is the number of recipients who were accepted for delivery.
*
* @param string[] $failedRecipients An array of failures by-reference
*
* @return int
*/
public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
{
$maxTransports = \count($this->transports);
$sent = 0;
$this->lastUsedTransport = null;
for ($i = 0; $i < $maxTransports
&& $transport = $this->getNextTransport(); ++$i) {
try {
if (!$transport->isStarted()) {
$transport->start();
}
if ($sent = $transport->send($message, $failedRecipients)) {
$this->lastUsedTransport = $transport;
break;
}
} catch (Swift_TransportException $e) {
$this->killCurrentTransport();
}
}
if (0 == \count($this->transports)) {
throw new Swift_TransportException('All Transports in LoadBalancedTransport failed, or no Transports available');
}
return $sent;
}
/**
* Register a plugin.
*/
public function registerPlugin(Swift_Events_EventListener $plugin)
{
foreach ($this->transports as $transport) {
$transport->registerPlugin($plugin);
}
}
/**
* Rotates the transport list around and returns the first instance.
*
* @return Swift_Transport
*/
protected function getNextTransport()
{
if ($next = array_shift($this->transports)) {
$this->transports[] = $next;
}
return $next;
}
/**
* Tag the currently used (top of stack) transport as dead/useless.
*/
protected function killCurrentTransport()
{
if ($transport = array_pop($this->transports)) {
try {
$transport->stop();
} catch (Exception $e) {
}
$this->deadTransports[] = $transport;
}
}
}

View File

@ -0,0 +1,98 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Pretends messages have been sent, but just ignores them.
*
* @author Fabien Potencier
*/
class Swift_Transport_NullTransport implements Swift_Transport
{
/** The event dispatcher from the plugin API */
private $eventDispatcher;
/**
* Constructor.
*/
public function __construct(Swift_Events_EventDispatcher $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
/**
* Tests if this Transport mechanism has started.
*
* @return bool
*/
public function isStarted()
{
return true;
}
/**
* Starts this Transport mechanism.
*/
public function start()
{
}
/**
* Stops this Transport mechanism.
*/
public function stop()
{
}
/**
* {@inheritdoc}
*/
public function ping()
{
return true;
}
/**
* Sends the given message.
*
* @param string[] $failedRecipients An array of failures by-reference
*
* @return int The number of sent emails
*/
public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
{
if ($evt = $this->eventDispatcher->createSendEvent($this, $message)) {
$this->eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
if ($evt->bubbleCancelled()) {
return 0;
}
}
if ($evt) {
$evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
$this->eventDispatcher->dispatchEvent($evt, 'sendPerformed');
}
$count = (
\count((array) $message->getTo())
+ \count((array) $message->getCc())
+ \count((array) $message->getBcc())
);
return $count;
}
/**
* Register a plugin.
*/
public function registerPlugin(Swift_Events_EventListener $plugin)
{
$this->eventDispatcher->bindEventListener($plugin);
}
}

View File

@ -0,0 +1,158 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* SendmailTransport for sending mail through a Sendmail/Postfix (etc..) binary.
*
* Supported modes are -bs and -t, with any additional flags desired.
* It is advised to use -bs mode since error reporting with -t mode is not
* possible.
*
* @author Chris Corbyn
*/
class Swift_Transport_SendmailTransport extends Swift_Transport_AbstractSmtpTransport
{
/**
* Connection buffer parameters.
*
* @var array
*/
private $params = [
'timeout' => 30,
'blocking' => 1,
'command' => '/usr/sbin/sendmail -bs',
'type' => Swift_Transport_IoBuffer::TYPE_PROCESS,
];
/**
* Create a new SendmailTransport with $buf for I/O.
*
* @param string $localDomain
*/
public function __construct(Swift_Transport_IoBuffer $buf, Swift_Events_EventDispatcher $dispatcher, $localDomain = '127.0.0.1', Swift_AddressEncoder $addressEncoder = null)
{
parent::__construct($buf, $dispatcher, $localDomain, $addressEncoder);
}
/**
* Start the standalone SMTP session if running in -bs mode.
*/
public function start()
{
if (false !== strpos($this->getCommand(), ' -bs')) {
parent::start();
}
}
/**
* Set the command to invoke.
*
* If using -t mode you are strongly advised to include -oi or -i in the flags.
* For example: /usr/sbin/sendmail -oi -t
* Swift will append a -f<sender> flag if one is not present.
*
* The recommended mode is "-bs" since it is interactive and failure notifications
* are hence possible.
*
* @param string $command
*
* @return $this
*/
public function setCommand($command)
{
$this->params['command'] = $command;
return $this;
}
/**
* Get the sendmail command which will be invoked.
*
* @return string
*/
public function getCommand()
{
return $this->params['command'];
}
/**
* Send the given Message.
*
* Recipient/sender data will be retrieved from the Message API.
*
* The return value is the number of recipients who were accepted for delivery.
* NOTE: If using 'sendmail -t' you will not be aware of any failures until
* they bounce (i.e. send() will always return 100% success).
*
* @param string[] $failedRecipients An array of failures by-reference
*
* @return int
*/
public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
{
$failedRecipients = (array) $failedRecipients;
$command = $this->getCommand();
$buffer = $this->getBuffer();
$count = 0;
if (false !== strpos($command, ' -t')) {
if ($evt = $this->eventDispatcher->createSendEvent($this, $message)) {
$this->eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
if ($evt->bubbleCancelled()) {
return 0;
}
}
if (false === strpos($command, ' -f')) {
$command .= ' -f'.escapeshellarg($this->getReversePath($message));
}
$buffer->initialize(array_merge($this->params, ['command' => $command]));
if (false === strpos($command, ' -i') && false === strpos($command, ' -oi')) {
$buffer->setWriteTranslations(["\r\n" => "\n", "\n." => "\n.."]);
} else {
$buffer->setWriteTranslations(["\r\n" => "\n"]);
}
$count = \count((array) $message->getTo())
+ \count((array) $message->getCc())
+ \count((array) $message->getBcc())
;
$message->toByteStream($buffer);
$buffer->flushBuffers();
$buffer->setWriteTranslations([]);
$buffer->terminate();
if ($evt) {
$evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
$evt->setFailedRecipients($failedRecipients);
$this->eventDispatcher->dispatchEvent($evt, 'sendPerformed');
}
$message->generateId();
} elseif (false !== strpos($command, ' -bs')) {
$count = parent::send($message, $failedRecipients);
} else {
$this->throwException(new Swift_TransportException(
'Unsupported sendmail command flags ['.$command.']. '.
'Must be one of "-bs" or "-t" but can include additional flags.'
));
}
return $count;
}
/** Get the params to initialize the buffer */
protected function getBufferParams()
{
return $this->params;
}
}

View File

@ -0,0 +1,36 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Wraps an IoBuffer to send/receive SMTP commands/responses.
*
* @author Chris Corbyn
*/
interface Swift_Transport_SmtpAgent
{
/**
* Get the IoBuffer where read/writes are occurring.
*
* @return Swift_Transport_IoBuffer
*/
public function getBuffer();
/**
* Run a command against the buffer, expecting the given response codes.
*
* If no response codes are given, the response will not be validated.
* If codes are given, an exception will be thrown on an invalid response.
*
* @param string $command
* @param int[] $codes
* @param string[] $failures An array of failures by-reference
*/
public function executeCommand($command, $codes = [], &$failures = null);
}

View File

@ -0,0 +1,120 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Stores Messages in a queue.
*
* @author Fabien Potencier
*/
class Swift_Transport_SpoolTransport implements Swift_Transport
{
/** The spool instance */
private $spool;
/** The event dispatcher from the plugin API */
private $eventDispatcher;
/**
* Constructor.
*/
public function __construct(Swift_Events_EventDispatcher $eventDispatcher, Swift_Spool $spool = null)
{
$this->eventDispatcher = $eventDispatcher;
$this->spool = $spool;
}
/**
* Sets the spool object.
*
* @return $this
*/
public function setSpool(Swift_Spool $spool)
{
$this->spool = $spool;
return $this;
}
/**
* Get the spool object.
*
* @return Swift_Spool
*/
public function getSpool()
{
return $this->spool;
}
/**
* Tests if this Transport mechanism has started.
*
* @return bool
*/
public function isStarted()
{
return true;
}
/**
* Starts this Transport mechanism.
*/
public function start()
{
}
/**
* Stops this Transport mechanism.
*/
public function stop()
{
}
/**
* {@inheritdoc}
*/
public function ping()
{
return true;
}
/**
* Sends the given message.
*
* @param string[] $failedRecipients An array of failures by-reference
*
* @return int The number of sent e-mail's
*/
public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
{
if ($evt = $this->eventDispatcher->createSendEvent($this, $message)) {
$this->eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
if ($evt->bubbleCancelled()) {
return 0;
}
}
$success = $this->spool->queueMessage($message);
if ($evt) {
$evt->setResult($success ? Swift_Events_SendEvent::RESULT_SPOOLED : Swift_Events_SendEvent::RESULT_FAILED);
$this->eventDispatcher->dispatchEvent($evt, 'sendPerformed');
}
return 1;
}
/**
* Register a plugin.
*/
public function registerPlugin(Swift_Events_EventListener $plugin)
{
$this->eventDispatcher->bindEventListener($plugin);
}
}

View File

@ -0,0 +1,319 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A generic IoBuffer implementation supporting remote sockets and local processes.
*
* @author Chris Corbyn
*/
class Swift_Transport_StreamBuffer extends Swift_ByteStream_AbstractFilterableInputStream implements Swift_Transport_IoBuffer
{
/** A primary socket */
private $stream;
/** The input stream */
private $in;
/** The output stream */
private $out;
/** Buffer initialization parameters */
private $params = [];
/** The ReplacementFilterFactory */
private $replacementFactory;
/** Translations performed on data being streamed into the buffer */
private $translations = [];
/**
* Create a new StreamBuffer using $replacementFactory for transformations.
*/
public function __construct(Swift_ReplacementFilterFactory $replacementFactory)
{
$this->replacementFactory = $replacementFactory;
}
/**
* Perform any initialization needed, using the given $params.
*
* Parameters will vary depending upon the type of IoBuffer used.
*/
public function initialize(array $params)
{
$this->params = $params;
switch ($params['type']) {
case self::TYPE_PROCESS:
$this->establishProcessConnection();
break;
case self::TYPE_SOCKET:
default:
$this->establishSocketConnection();
break;
}
}
/**
* Set an individual param on the buffer (e.g. switching to SSL).
*
* @param string $param
* @param mixed $value
*/
public function setParam($param, $value)
{
if (isset($this->stream)) {
switch ($param) {
case 'timeout':
if ($this->stream) {
stream_set_timeout($this->stream, $value);
}
break;
case 'blocking':
if ($this->stream) {
stream_set_blocking($this->stream, 1);
}
}
}
$this->params[$param] = $value;
}
public function startTLS()
{
// STREAM_CRYPTO_METHOD_TLS_CLIENT only allow tls1.0 connections (some php versions)
// To support modern tls we allow explicit tls1.0, tls1.1, tls1.2
// Ssl3 and older are not allowed because they are vulnerable
// @TODO make tls arguments configurable
return stream_socket_enable_crypto($this->stream, true, STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);
}
/**
* Perform any shutdown logic needed.
*/
public function terminate()
{
if (isset($this->stream)) {
switch ($this->params['type']) {
case self::TYPE_PROCESS:
fclose($this->in);
fclose($this->out);
proc_close($this->stream);
break;
case self::TYPE_SOCKET:
default:
fclose($this->stream);
break;
}
}
$this->stream = null;
$this->out = null;
$this->in = null;
}
/**
* Set an array of string replacements which should be made on data written
* to the buffer.
*
* This could replace LF with CRLF for example.
*
* @param string[] $replacements
*/
public function setWriteTranslations(array $replacements)
{
foreach ($this->translations as $search => $replace) {
if (!isset($replacements[$search])) {
$this->removeFilter($search);
unset($this->translations[$search]);
}
}
foreach ($replacements as $search => $replace) {
if (!isset($this->translations[$search])) {
$this->addFilter(
$this->replacementFactory->createFilter($search, $replace), $search
);
$this->translations[$search] = true;
}
}
}
/**
* Get a line of output (including any CRLF).
*
* The $sequence number comes from any writes and may or may not be used
* depending upon the implementation.
*
* @param int $sequence of last write to scan from
*
* @return string
*
* @throws Swift_IoException
*/
public function readLine($sequence)
{
if (isset($this->out) && !feof($this->out)) {
$line = fgets($this->out);
if (0 == \strlen($line)) {
$metas = stream_get_meta_data($this->out);
if ($metas['timed_out']) {
throw new Swift_IoException('Connection to '.$this->getReadConnectionDescription().' Timed Out');
}
}
return $line;
}
}
/**
* Reads $length bytes from the stream into a string and moves the pointer
* through the stream by $length.
*
* If less bytes exist than are requested the remaining bytes are given instead.
* If no bytes are remaining at all, boolean false is returned.
*
* @param int $length
*
* @return string|bool
*
* @throws Swift_IoException
*/
public function read($length)
{
if (isset($this->out) && !feof($this->out)) {
$ret = fread($this->out, $length);
if (0 == \strlen($ret)) {
$metas = stream_get_meta_data($this->out);
if ($metas['timed_out']) {
throw new Swift_IoException('Connection to '.$this->getReadConnectionDescription().' Timed Out');
}
}
return $ret;
}
}
/** Not implemented */
public function setReadPointer($byteOffset)
{
}
/** Flush the stream contents */
protected function flush()
{
if (isset($this->in)) {
fflush($this->in);
}
}
/** Write this bytes to the stream */
protected function doCommit($bytes)
{
if (isset($this->in)) {
$bytesToWrite = \strlen($bytes);
$totalBytesWritten = 0;
while ($totalBytesWritten < $bytesToWrite) {
$bytesWritten = fwrite($this->in, substr($bytes, $totalBytesWritten));
if (false === $bytesWritten || 0 === $bytesWritten) {
break;
}
$totalBytesWritten += $bytesWritten;
}
if ($totalBytesWritten > 0) {
return ++$this->sequence;
}
}
}
/**
* Establishes a connection to a remote server.
*/
private function establishSocketConnection()
{
$host = $this->params['host'];
if (!empty($this->params['protocol'])) {
$host = $this->params['protocol'].'://'.$host;
}
$timeout = 15;
if (!empty($this->params['timeout'])) {
$timeout = $this->params['timeout'];
}
$options = [];
if (!empty($this->params['sourceIp'])) {
$options['socket']['bindto'] = $this->params['sourceIp'].':0';
}
if (isset($this->params['stream_context_options'])) {
$options = array_merge($options, $this->params['stream_context_options']);
}
$streamContext = stream_context_create($options);
set_error_handler(function ($type, $msg) {
throw new Swift_TransportException('Connection could not be established with host '.$this->params['host'].' :'.$msg);
});
try {
$this->stream = stream_socket_client($host.':'.$this->params['port'], $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $streamContext);
} finally {
restore_error_handler();
}
if (!empty($this->params['blocking'])) {
stream_set_blocking($this->stream, 1);
} else {
stream_set_blocking($this->stream, 0);
}
stream_set_timeout($this->stream, $timeout);
$this->in = &$this->stream;
$this->out = &$this->stream;
}
/**
* Opens a process for input/output.
*/
private function establishProcessConnection()
{
$command = $this->params['command'];
$descriptorSpec = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$pipes = [];
$this->stream = proc_open($command, $descriptorSpec, $pipes);
stream_set_blocking($pipes[2], 0);
if ($err = stream_get_contents($pipes[2])) {
throw new Swift_TransportException('Process could not be started ['.$err.']');
}
$this->in = &$pipes[0];
$this->out = &$pipes[1];
}
private function getReadConnectionDescription()
{
switch ($this->params['type']) {
case self::TYPE_PROCESS:
return 'Process '.$this->params['command'];
break;
case self::TYPE_SOCKET:
default:
$host = $this->params['host'];
if (!empty($this->params['protocol'])) {
$host = $this->params['protocol'].'://'.$host;
}
$host .= ':'.$this->params['port'];
return $host;
break;
}
}
}