I want to make SMTP TLS "implicit/explicit" configuration transparent, and to do this I thought of using event triggers.
My idea is just to capture if the TLS negotiation succeeds or fails and then try again with the other option.
I saw some events on TIdSMTP
and TIdSSLIOHandlerSocketOpenSSL
, but none of them seems to trigger correctly.
Documentation for Indy also can't be downloaded here (DNS broken error).
I used the following events with a "Sleep(1);
" just to put a breakpoint in them.
TIdSMTP
:
OnTLSNotAvailable
OnTLSHandShakeFailed
OnTLSNegCmdFailed
OnFailedEHLO
TIdSSLIOHandlerSocketOpenSSL
:
OnStatus
OnStatusInfo
OnStatusInfoEx
What I've tested:
I used for example Outlook smtp.office365.com with port 587 (which is STARTTLS
- utUseExplicitTLS
in Indy), but I intentionally changed the TIdSMTP.UseTLS
to utUseImlicitTLS
to simulate a TLS negotiation failure.
In the first TIdSMTP.Connect()
test, I got an EIdSocketError
exception ('Socket Error # 10060 Connection timed out') instead of a TLS negotiation failed message/exception. In this test, the only triggered events were TIdSSLIOHandlerSocketOpenSSL.OnStatus
and TIdSMTP.OnStatus
.
In the second TIdSMTP.Connect()
test, I don't receive the EIdSocketError
with timeout message, and aside from the events on the first try, it also triggered the TIdSSLIOHandlerSocketOpenSSL.OnStatusInfo
and TIdSSLIOHandlerSocketOpenSSL.OnStatusInfoEx
events, handling TLS connection error.
Some code snippet:
procedure Connect;
var
LSMTP: TIdSMTP;
LSSL: TIdSSLIOHandlerSocketOpenSSL;
begin
LSMTP:= TIdSMTP.Create(nil);
LSSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
{functions to be declared}
LSMTP.OnTLSNotAvailable := MyTLSNotAvailable;
LSMTP.OnTLSHandShakeFailed:= MyTLSHandShakeFailed;
LSMTP.OnTLSNegCmdFailed := MyTLSNegCmdFailed;
LSMTP.OnFailedEHLO := MyFailedEHLO;
LSSL.OnStatus := MyOnStatus;
LSSL.OnStatusInfo := MySSLInfo;
LSSL.OnStatusInfoEx:= MySSlInfoEx;
LSSL.ConnectTimeout:= 1000000;
LSSL.ReadTimeout := 1000000;
LSMTP.IOHandler := FSSL;
LSMTP.AuthType := satDefault;
LSMTP.UseTLS := utUseExplicitTLS;
LSSL.SSLOptions.Method:= sslvSSLv23;
LSSL.SSLOptions.Mode := sslmBoth;
LSMTP.Host:= 'smtp.office365.com';
LSMTP.Port:= '587';
LSMTP.Username:= mail@example.com;
LSMTP.Password:= password;
LSMTP.From:= LSMTP.Username;
try
LSMTP.Connect;
except
raise Exception.Create(Format('Error during connect attempt: %s',[Exception(ExceptObject).Message]));
end;
end;
So, I have some questions:
Why don't the TIdSMTP.OnTLSNotAvailable
, TIdSMTP.OnTLSHandShakeFailed
, and TIdSMTP.OnTLSNegCmdFailed
events trigger in those cases?
Why do the TIdSSLIOHandlerSocketOpenSSL.OnStatusInfo
and TIdSSLIOHandlerSocketOpenSSL.OnStatusInfoEx
events trigger only in the second connection try?
Am I missing something?
You should not use events to accomplish what you are attempting, as Indy is not event-driven. Events are for informational purposes only, not for flow control. If the TLS handshake fails, an exception will be raised accordingly. Catch the exception, close the TCP connection, and retry as needed.
As for your attempt to simulate a TLS failure:
If you use utUseImplicitTLS
on a non-tls or explicit-tls SMTP port, the client will send a TLS handshake immediately upon establishing the TCP connection, and then fail the TLS handshake when it misinterprets the server's unencrypted SMTP greeting as-if it were a TLS handshake response.
If you use utUseExplicitTLS
on an implicit-tls port, the client will timeout (or deadlock if you don't use a timeout) after establishing the TCP connection, because it will be waiting for an unencrypted SMTP greeting that the server never sends, because the server is waiting for a TLS handshake that the client never sends.
Regarding the various events that are available:
OnTLSNotAvailable
is fired when:
UseTLS
is utUseRequireTLS
1, and
UseEhlo
is True
, and
the SMTP server does not advertise support for STARTTLS
when the client sends an EHLO
command to learn the server's capabilities.
If the event handler sets the VContinue
parameter to False
then an EIdTLSClientTLSNotAvailable
exception is raised, otherwise the SMTP session continues unencrypted. The default is True
.
1: On the client side, utUseRequireTLS
is similar to utUseExplicitTLS
, but a server's refusal to use TLS is not acceptable. Whereas TLS is optional with utUseExplicitTLS
.
OnTLSHandShakeFailed
is fired when:
UseTLS
is utUseImplicitTLS
or utUseExplicitTLS
(not utUseRequireTLS
), and
the SSLIOHandler
raises an exception during the TLS handshake.
If the event handler sets the VContinue
parameter to False
then an EIdTLSClientTLSHandShakeFailed
exception is raised, otherwise the SMTP session continues unencrypted. The default is False
.
NOTE: in this case, if the exception is bypassed then the SMTP session will technically be in undefined behavior territory, as the state of the socket's data will be unknown (was the exception raised in the middle of a packet? In between packets? Are more packets still in-flight? Etc). Unlike with the other events where the socket data is left in a well-defined state.
OnTLSNegCmdFailed
is fired when:
UseTLS
is utUseExplicitTLS
(not utUseRequireTLS
), and
the server sends a failure response to the STARTTLS
command before a TLS handshake begins.
If the event handler sets the VContinue
parameter to False
then an EIdTLSClientTLSNegCmdFailed
exception is raised, otherwise the SMTP session continues unencrypted. The default is False
.
OnFailedEHLO
is fired when:
UseEhlo
is True
, and
the server sends a failure response to the EHLO
command.
If the event handler sets the VContinue
parameter to False
then the TCP connection is closed and an EIdSMTPReplyError
exception is raised, otherwise the SMTP session continues without knowing what the server's capabilities are. The default is True
.
OnStatus
is fired for some general-purpose component-level actions, like resolving the IP address of a hostname, connecting to a TCP port, encoding email, etc.
OnStatusInfo/Ex
are fired by the SSLIOHandler
to provide status updates from the OpenSSL library during a TLS session. Refer to OpenSSL's documentation for information about what kind of status values are provided:
https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set_info_callback.html