delphisslsmtpindy

Which TIdSMTP/SSLIOHandler event should trigger when TLS negotiation fails?


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:

Am I missing something?


Solution

  • 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:

    Regarding the various events that are available: