vendor/symfony/mailer/Transport/Smtp/EsmtpTransport.php line 113

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Mailer\Transport\Smtp;
  11. use Psr\EventDispatcher\EventDispatcherInterface;
  12. use Psr\Log\LoggerInterface;
  13. use Symfony\Component\Mailer\Exception\TransportException;
  14. use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
  15. use Symfony\Component\Mailer\Transport\Smtp\Auth\AuthenticatorInterface;
  16. use Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream;
  17. use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream;
  18. /**
  19.  * Sends Emails over SMTP with ESMTP support.
  20.  *
  21.  * @author Fabien Potencier <fabien@symfony.com>
  22.  * @author Chris Corbyn
  23.  */
  24. class EsmtpTransport extends SmtpTransport
  25. {
  26.     private array $authenticators = [];
  27.     private string $username '';
  28.     private string $password '';
  29.     private array $capabilities;
  30.     public function __construct(string $host 'localhost'int $port 0bool $tls nullEventDispatcherInterface $dispatcher nullLoggerInterface $logger nullAbstractStream $stream null)
  31.     {
  32.         parent::__construct($stream$dispatcher$logger);
  33.         // order is important here (roughly most secure and popular first)
  34.         $this->authenticators = [
  35.             new Auth\CramMd5Authenticator(),
  36.             new Auth\LoginAuthenticator(),
  37.             new Auth\PlainAuthenticator(),
  38.             new Auth\XOAuth2Authenticator(),
  39.         ];
  40.         /** @var SocketStream $stream */
  41.         $stream $this->getStream();
  42.         if (null === $tls) {
  43.             if (465 === $port) {
  44.                 $tls true;
  45.             } else {
  46.                 $tls \defined('OPENSSL_VERSION_NUMBER') && === $port && 'localhost' !== $host;
  47.             }
  48.         }
  49.         if (!$tls) {
  50.             $stream->disableTls();
  51.         }
  52.         if (=== $port) {
  53.             $port $tls 465 25;
  54.         }
  55.         $stream->setHost($host);
  56.         $stream->setPort($port);
  57.     }
  58.     /**
  59.      * @return $this
  60.      */
  61.     public function setUsername(string $username): static
  62.     {
  63.         $this->username $username;
  64.         return $this;
  65.     }
  66.     public function getUsername(): string
  67.     {
  68.         return $this->username;
  69.     }
  70.     /**
  71.      * @return $this
  72.      */
  73.     public function setPassword(string $password): static
  74.     {
  75.         $this->password $password;
  76.         return $this;
  77.     }
  78.     public function getPassword(): string
  79.     {
  80.         return $this->password;
  81.     }
  82.     public function addAuthenticator(AuthenticatorInterface $authenticator): void
  83.     {
  84.         $this->authenticators[] = $authenticator;
  85.     }
  86.     public function executeCommand(string $command, array $codes): string
  87.     {
  88.         return [250] === $codes && str_starts_with($command'HELO ') ? $this->doEhloCommand() : parent::executeCommand($command$codes);
  89.     }
  90.     final protected function getCapabilities(): array
  91.     {
  92.         return $this->capabilities;
  93.     }
  94.     private function doEhloCommand(): string
  95.     {
  96.         try {
  97.             $response $this->executeCommand(sprintf("EHLO %s\r\n"$this->getLocalDomain()), [250]);
  98.         } catch (TransportExceptionInterface $e) {
  99.             try {
  100.                 return parent::executeCommand(sprintf("HELO %s\r\n"$this->getLocalDomain()), [250]);
  101.             } catch (TransportExceptionInterface $ex) {
  102.                 if (!$ex->getCode()) {
  103.                     throw $e;
  104.                 }
  105.                 throw $ex;
  106.             }
  107.         }
  108.         $this->capabilities $this->parseCapabilities($response);
  109.         /** @var SocketStream $stream */
  110.         $stream $this->getStream();
  111.         // WARNING: !$stream->isTLS() is right, 100% sure :)
  112.         // if you think that the ! should be removed, read the code again
  113.         // if doing so "fixes" your issue then it probably means your SMTP server behaves incorrectly or is wrongly configured
  114.         if (!$stream->isTLS() && \defined('OPENSSL_VERSION_NUMBER') && \array_key_exists('STARTTLS'$this->capabilities)) {
  115.             $this->executeCommand("STARTTLS\r\n", [220]);
  116.             if (!$stream->startTLS()) {
  117.                 throw new TransportException('Unable to connect with STARTTLS.');
  118.             }
  119.             $response $this->executeCommand(sprintf("EHLO %s\r\n"$this->getLocalDomain()), [250]);
  120.             $this->capabilities $this->parseCapabilities($response);
  121.         }
  122.         if (\array_key_exists('AUTH'$this->capabilities)) {
  123.             $this->handleAuth($this->capabilities['AUTH']);
  124.         }
  125.         return $response;
  126.     }
  127.     private function parseCapabilities(string $ehloResponse): array
  128.     {
  129.         $capabilities = [];
  130.         $lines explode("\r\n"trim($ehloResponse));
  131.         array_shift($lines);
  132.         foreach ($lines as $line) {
  133.             if (preg_match('/^[0-9]{3}[ -]([A-Z0-9-]+)((?:[ =].*)?)$/Di'$line$matches)) {
  134.                 $value strtoupper(ltrim($matches[2], ' ='));
  135.                 $capabilities[strtoupper($matches[1])] = $value explode(' '$value) : [];
  136.             }
  137.         }
  138.         return $capabilities;
  139.     }
  140.     private function handleAuth(array $modes): void
  141.     {
  142.         if (!$this->username) {
  143.             return;
  144.         }
  145.         $authNames = [];
  146.         $errors = [];
  147.         $modes array_map('strtolower'$modes);
  148.         foreach ($this->authenticators as $authenticator) {
  149.             if (!\in_array(strtolower($authenticator->getAuthKeyword()), $modestrue)) {
  150.                 continue;
  151.             }
  152.             $authNames[] = $authenticator->getAuthKeyword();
  153.             try {
  154.                 $authenticator->authenticate($this);
  155.                 return;
  156.             } catch (TransportExceptionInterface $e) {
  157.                 try {
  158.                     $this->executeCommand("RSET\r\n", [250]);
  159.                 } catch (TransportExceptionInterface) {
  160.                     // ignore this exception as it probably means that the server error was final
  161.                 }
  162.                 // keep the error message, but tries the other authenticators
  163.                 $errors[$authenticator->getAuthKeyword()] = $e->getMessage();
  164.             }
  165.         }
  166.         if (!$authNames) {
  167.             throw new TransportException(sprintf('Failed to find an authenticator supported by the SMTP server, which currently supports: "%s".'implode('", "'$modes)));
  168.         }
  169.         $message sprintf('Failed to authenticate on SMTP server with username "%s" using the following authenticators: "%s".'$this->usernameimplode('", "'$authNames));
  170.         foreach ($errors as $name => $error) {
  171.             $message .= sprintf(' Authenticator "%s" returned "%s".'$name$error);
  172.         }
  173.         throw new TransportException($message);
  174.     }
  175. }