N°9584 - Fix "Unable to connect with STARTTLS" error when sending emails#901
Conversation
|
@odain-cbd do you have any idea how I could make a unit test for that? |
|
| Filename | Overview |
|---|---|
| sources/Core/Email/EmailSymfony.php | Extracts SMTP transport creation into CreateSmtpTransport() and removes the array_key_exists('ssl', $aOptions) guard so SSL options are written unconditionally when verify_peer=false, fixing the STARTTLS regression. |
| tests/php-unit-tests/unitary-tests/sources/core/Email/EmailSymfonyTest.php | Adds six data-provider cases covering STARTTLS, implicit TLS, and plain SMTP with both verify_peer=true and verify_peer=false, directly exercising the regression scenario. |
Sequence Diagram
sequenceDiagram
participant S as SendSynchronous
participant C as CreateSmtpTransport
participant T as EsmtpTransport
participant St as SocketStream
participant PHP as PHP TLS layer
S->>C: CreateSmtpTransport(dsn, bVerifyPeer)
C->>T: Transport::fromDsn(dsn)
T-->>C: EsmtpTransport
C->>St: getStream().getStreamOptions()
St-->>C: aOptions (no 'ssl' key for STARTTLS)
alt bVerifyPeer = false
C->>C: aOptions['ssl']['verify_peer'] = false
C->>C: aOptions['ssl']['verify_peer_name'] = false
C->>C: aOptions['ssl']['allow_self_signed'] = true
end
C->>St: setStreamOptions(aOptions)
C-->>S: EsmtpTransport (ssl options pre-populated)
S->>T: send(message) → SMTP handshake
T->>PHP: stream_socket_enable_crypto()
Note over PHP: Reads ssl options from stream context (now correctly set before this call)
Reviews (2): Last reviewed commit: "N°9584 - Refactor EMailSymfony transport..." | Re-trigger Greptile
There was a problem hiding this comment.
Pull request overview
Fixes SMTP STARTTLS connection failures after the Laminas → Symfony Mailer migration by ensuring SSL stream context options are applied even when the connection starts in cleartext (STARTTLS upgrade path).
Changes:
- Removes the
array_key_exists('ssl', $aOptions)guard so SSL verification-disabling options are written even when thesslcontext doesn’t exist yet (STARTTLS). - Adds inline documentation explaining why SSL options must be set before
stream_socket_enable_crypto()is invoked.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * N°9584 Connection with the SMTP server always starts unencrypted, so there won't be any `ssl` option at first. | ||
| * But we have to force them if certificate should not be verified so they can be used when the connection is "upgraded" to TLS via STARTTLS |
| $aOptions['ssl']['verify_peer'] = false; | ||
| $aOptions['ssl']['verify_peer_name'] = false; | ||
| $aOptions['ssl']['allow_self_signed'] = true; |
There was a problem hiding this comment.
I think this is irrelevant as it's an ssl option, whereas what your point is an inexisting iTop config parameter
| if (!$bVerifyPeer) { | ||
| // Disable verification - must set even when 'ssl' key is absent (e.g. STARTTLS starts plain and upgrades later) | ||
| $aOptions['ssl']['verify_peer'] = false; | ||
| $aOptions['ssl']['verify_peer_name'] = false; | ||
| $aOptions['ssl']['allow_self_signed'] = true; |
|
@greptileai review again |
@odain-cbd I made a unit test but feel free to comment / advise on how we could do it. |
3063a79 to
b8149b6
Compare
Base information
Symptom (bug)
When sending an email from iTop with SMTP transport, the following error occurs if the SMTP server presents a certificate whose CN does not match the configured host:
Reproduction procedure (bug)
tls, andverify_peer = falseCause (bug)
This issue appeared after the migration from Laminas to Symfony Mailer. The
email_transport_smtp.verify_peerconfiguration parameter (defaulttrue) is supposed to allow disabling certificate verification, but has no longer any effect.The
array_key_exists('ssl', $aOptions)condition is the root cause. When using STARTTLS (encryption=tls), the connection starts unencrypted on a plain socket and only upgrades to TLS later (via theSTARTTLSSMTP command, see RFC 3207 section 4). At the timegetStreamOptions()is called, the'ssl'key does not yet exist in the stream context, so the condition silently fails and the options are never written.When PHP later calls
stream_socket_enable_crypto()to upgrade the connection, it uses the SSL context options as-is — with peer verification still enabled by default — causing the CN mismatch error.This worked with Laminas because that library configured SSL options through a dedicated configuration object, independently of whether the connection had already established an SSL context.
Proposed solution (bug and enhancement)
Remove the
array_key_exists('ssl', $aOptions)guard so that SSL options are unconditionally written whenverify_peerisfalse, regardless of whether the'ssl'key is pre-populated which does no harm is the connection is not encrypted:This ensures the options are present in the stream context before
stream_socket_enable_crypto()is called, both for STARTTLS and for implicit TLS (smtps).Checklist before requesting a review
Checklist before requesting a review