Files
cacti/lib/CactiMailerTransport.php
c741f71c5c refactor(mail): replace PHPMailer with Symfony Mailer (#7190)
* refactor(mail): replace phpmailer with symfony mailer

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* style(codex): fix coding standards in Symfony Mailer migration

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(codex): fix TLS downgrade bug and From address overwrite in Symfony Mailer

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(codex): guard SMTP stream setTimeout calls behind SocketStream check

setTimeout() exists on SocketStream only; getStream() is typed AbstractStream, which fails PHPStan level 6.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(codex): correct inline graph CID, mandatory STARTTLS, and address validation in Symfony Mailer

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(codex): test inline CID, STARTTLS enforcement, and address-error handling

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(mail): qualify bare local addresses so sendmail recipients still work

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(mail): prune the stale PHPMailer vendor tree on upgrade

Replacing PHPMailer with Symfony Mailer dropped phpmailer from composer.json,
but the change also removed the prune_deprecated_files entries that cleaned up
the vendored tree, so an upgraded install would keep stale PHPMailer code on
disk. Add include/vendor/phpmailer (whole directory) to the prune list.

Co-Authored-By: Oz <oz-agent@warp.dev>
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(mail): address transport review feedback

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(mail): drop setAutoTls() call missing from symfony/mailer 6.4

EsmtpTransport::setAutoTls() only exists in symfony/mailer >= 7.1, and the
6.4 series (needed for PHP 8.1) has no switch to disable opportunistic
STARTTLS, so hide the advertised capability in a small subclass instead.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(mail): vendor the psr/log 1.1.4 sources

include/vendor/psr/log was tracked with only its package metadata, so
composer treats it as installed and never extracts the classes. That was
harmless while nothing loaded them, but Symfony Mailer's AbstractTransport
instantiates Psr\Log\NullLogger on every transport, which now fatals at
runtime and in the test job.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(mail): match PHPMailer SMTP and attachment semantics

Empty secure modes use no-STARTTLS transport; inline disposition
is honoured; unreadable attachments fail closed.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: Thomas Vincent <thomas@vincent.dev>
Co-authored-by: Oz <oz-agent@warp.dev>
2026-07-15 08:30:24 -04:00

70 lines
2.7 KiB
PHP

<?php
declare(strict_types = 1);
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
| |
| This program is free software; you can redistribute it and/or |
| modify it under the terms of the GNU General Public License |
| as published by the Free Software Foundation; either version 2 |
| of the License, or (at your option) any later version. |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
*/
use Symfony\Component\Mailer\Exception\TransportException;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream;
/**
* EsmtpTransport that requires STARTTLS.
*
* The base EsmtpTransport only upgrades to STARTTLS when the server advertises
* it, so a server that omits the capability is talked to in plaintext. This
* matches PHPMailer's mandatory-STARTTLS behaviour: after the connection is
* established the stream must be encrypted, otherwise the send fails closed.
* This Symfony release has no setRequireTls(), so the check is done here.
*/
class CactiRequireTlsEsmtpTransport extends EsmtpTransport {
public function start(): void {
parent::start();
$stream = $this->getStream();
// After EHLO, an advertised STARTTLS leaves the stream encrypted. A
// plaintext stream here means the server never offered STARTTLS.
if ($stream instanceof SocketStream && !$stream->isTLS()) {
$this->stop();
throw new TransportException('STARTTLS is required but the SMTP server did not offer it.');
}
}
}
/**
* EsmtpTransport that never upgrades to STARTTLS.
*
* The base EsmtpTransport upgrades to STARTTLS whenever the server advertises
* it, even when no encryption was requested. This matches PHPMailer's
* SMTPAutoTLS = false behaviour for the 'none' security mode: the STARTTLS
* capability is stripped from the EHLO response before the base class can act
* on it. This Symfony release has no setAutoTls(), so the filtering is done
* here.
*/
class CactiNoTlsEsmtpTransport extends EsmtpTransport {
/**
* @param int[] $codes
*/
public function executeCommand(string $command, array $codes): string {
$response = parent::executeCommand($command, $codes);
// Hide the STARTTLS capability so the base class never upgrades.
if (str_starts_with($command, 'EHLO ')) {
$response = preg_replace('/^\d{3}[ -]STARTTLS\r\n/mi', '', $response) ?? $response;
}
return $response;
}
}