Skip to content

[Mailer] [Smtp] Add DSN param to enforce TLS/STARTTLS #59479

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Symfony/Component/Mailer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CHANGELOG
* Add DSN param `retry_period` to override default email transport retry period
* Add `Dsn::getBooleanOption()`
* Add DSN param `source_ip` to allow binding to a (specific) IPv4 or IPv6 address.
* Add DSN param `require_tls` to enforce use of TLS/STARTTLS
* Add `DkimSignedMessageListener`, `SmimeEncryptedMessageListener`, and `SmimeSignedMessageListener`

7.2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,18 @@ public static function createProvider(): iterable
Dsn::fromString('smtps://:@example.com:465?source_ip=[2606:4700:20::681a:5fb]'),
$transport,
];

$transport = new EsmtpTransport('example.com', 465, true, null, $logger);
$transport->setRequireTls(true);

yield [
new Dsn('smtps', 'example.com', '', '', 465, ['require_tls' => true]),
$transport,
];
yield [
Dsn::fromString('smtps://:@example.com?require_tls=true'),
$transport,
];
}

public static function unsupportedSchemeProvider(): iterable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,23 @@ public function testSocketTimeout()
$stream->getCommands()
);
}

public function testRequireTls()
{
$stream = new DummyStream();
$transport = new EsmtpTransport(stream: $stream);
$transport->setRequireTls(true);

$message = new Email();
$message->from('sender@example.org');
$message->addTo('recipient@example.org');
$message->text('.');

$this->expectException(TransportException::class);
$this->expectExceptionMessage('TLS required but neither TLS or STARTTLS are in use.');

$transport->send($message);
}
}

class CustomEsmtpTransport extends EsmtpTransport
Expand Down
22 changes: 22 additions & 0 deletions src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class EsmtpTransport extends SmtpTransport
private string $password = '';
private array $capabilities;
private bool $autoTls = true;
private bool $requireTls = false;

public function __construct(string $host = 'localhost', int $port = 0, ?bool $tls = null, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null, ?AbstractStream $stream = null, ?array $authenticators = null)
{
Expand Down Expand Up @@ -116,6 +117,21 @@ public function isAutoTls(): bool
return $this->autoTls;
}

/**
* @return $this
*/
public function setRequireTls(bool $requireTls): static
{
$this->requireTls = $requireTls;

return $this;
}

public function isTlsRequired(): bool
{
return $this->requireTls;
}

public function setAuthenticators(array $authenticators): void
{
$this->authenticators = [];
Expand Down Expand Up @@ -159,6 +175,7 @@ private function doEhloCommand(): string

/** @var SocketStream $stream */
$stream = $this->getStream();
$tlsStarted = $stream->isTls();
// WARNING: !$stream->isTLS() is right, 100% sure :)
// if you think that the ! should be removed, read the code again
// if doing so "fixes" your issue then it probably means your SMTP server behaves incorrectly or is wrongly configured
Expand All @@ -169,10 +186,15 @@ private function doEhloCommand(): string
throw new TransportException('Unable to connect with STARTTLS.');
}

$tlsStarted = true;
$response = $this->executeCommand(\sprintf("EHLO %s\r\n", $this->getLocalDomain()), [250]);
$this->capabilities = $this->parseCapabilities($response);
}

if (!$tlsStarted && $this->isTlsRequired()) {
throw new TransportException('TLS required but neither TLS or STARTTLS are in use.');
}

if (\array_key_exists('AUTH', $this->capabilities)) {
$this->handleAuth($this->capabilities['AUTH']);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public function create(Dsn $dsn): TransportInterface

$transport = new EsmtpTransport($host, $port, $tls, $this->dispatcher, $this->logger);
$transport->setAutoTls($autoTls);
$transport->setRequireTls($dsn->getBooleanOption('require_tls'));

/** @var SocketStream $stream */
$stream = $transport->getStream();
Expand Down
Loading