Indy SMTP 和 Exchange 服务器

Indy SMTP and Exchange Server

我在通过程序中的两种不同机制通过 Indy 发送电子邮件时遇到了一个非常奇怪的问题。这个问题和[这个][1]类似,但不完全一样。我将 Indy 10.5 与最新的 OpenSSL 库和 Delphi XE3 一起使用。

第一个有效的代码片段是我编写的一个简单的 SMTP 客户端。这是我如何设置它的示例。这不是确切的代码,但它应该给你一个想法。

  FIndySMTP.Intercept := FIndyLogFile;
  FIndySMTP.IOHandler := FIndySSLHandler;

  FIndyMessage.From.Address := FEmailAddress;

  FIndySSLHandler.Destination := FSMTPAddress + ':' + IntToStr(FSMTPPort);
  FIndySSLHandler.Host := FSMTPAddress;
  FIndySSLHandler.Port := FSMTPPort;
  FIndySMTP.Host := FSMTPAddress;
  FIndySMTP.Port := FSMTPPort;
  FIndySMTP.Username := FAccountName;
  FIndySMTP.Password := FAccountPass;
  FIndySMTP.AuthType := satDefault;
  FIndySMTP.UseEhlo := True;
  FIndySMTP.UseTLS := utUseExplicitTLS

  FIndySMTP.Connect;
  try
    if FIndySMTP.Connected = True then
      FIndySMTP.Send(FIndyMessage);
  finally
    FIndySMTP.Disconnect;
  end;

这会生成一封包含日志的成功电子邮件:

Recv 10/9/2015 10:41:02 AM: 220 server.server1.local Microsoft ESMTP MAIL Service ready at Fri, 9 Oct 2015 13:41:01 -0400<EOL>
Sent 10/9/2015 10:41:02 AM: EHLO DEIMOS<EOL>
Recv 10/9/2015 10:41:02 AM: 250-server.server1.local Hello [68.14.239.173]<EOL>250-SIZE 36700160<EOL>250-PIPELINING<EOL>250-DSN<EOL>250-ENHANCEDSTATUSCODES<EOL>250-AUTH<EOL>250-8BITMIME<EOL>250-BINARYMIME<EOL>250 CHUNKING<EOL>
Sent 10/9/2015 10:41:02 AM: RSET<EOL>
Recv 10/9/2015 10:41:02 AM: 250 2.0.0 Resetting<EOL>
....(The rest snipped)

现在是第二种方法,它使用内置于电子邮件组件中的 Report Builder 通过电子邮件发送报告(同样,为简洁起见,一些代码被剪掉了,真正的线索在日志中):

    lIOHandler.Destination := Host + ':' + IntToStr(Port);
    lIOHandler.Host := Host;
    lIOHandler.Port := Port;

    TheReport.EmailSettings.HostAddress := Host;
    TheReport.EmailSettings.UserName := UserName;
    TheReport.EmailSettings.Password := Password;
    lEmail.SMTP.OnEmailError := FMain.EmailErrorEvent;
    TppSMTPIndy(lEmail.SMTP).IndySMTP.OnFailedRecipient := FMain.idSMTPFailedRecipient;
    TppSMTPIndy(lEmail.SMTP).IndySMTP.Intercept := FMain.IdLogFile1;
    TppSMTPIndy(lEmail.SMTP).IndySMTP.Port := Port;
    TppSMTPIndy(lEmail.SMTP).IndySMTP.IOHandler := lIOHandler;
    TppSMTPIndy(lEmail.SMTP).IndySMTP.UseTLS := utUseExplicitTLS; 
    TppSMTPIndy(lEmail.SMTP).IndySMTP.AuthType := satDefault;
    TppSMTPIndy(lEmail.SMTP).IndySMTP.UseEhlo := True;

    TheReport.SendMail;

可以看到除了使用Report Builder TppSMTPIndy外,其他设置都是一样的。但是电子邮件没有发送,日志如下所示:

Stat Connected.
Recv 10/9/2015 10:44:31 AM: 220 server.server1.local Microsoft ESMTP MAIL Service ready at Fri, 9 Oct 2015 13:44:28 -0400<EOL>
Sent 10/9/2015 10:44:31 AM: EHLO DEIMOS<EOL>
Recv 10/9/2015 10:44:31 AM: 250-server.server1.local Hello [68.14.239.173]<EOL>250-SIZE 36700160<EOL>250-PIPELINING<EOL>250-DSN<EOL>250-ENHANCEDSTATUSCODES<EOL>250-AUTH<EOL>250-8BITMIME<EOL>250-BINARYMIME<EOL>250 CHUNKING<EOL>
Sent 10/9/2015 10:44:31 AM: QUIT<EOL>
Recv 10/9/2015 10:44:31 AM: 221 2.0.0 Service closing transmission channel<EOL>
Stat Disconnected.

可以看到收到HELLO后立即发送QUIT。这就是为什么我的问题与上面的 link 不同。那个人至少收到了 STARTTLS 请求。

是什么导致 Indy 在收到 HELLO 后立即发送 QUIT?我没有收到任何错误。它只是默默地失败了,程序继续进行。

现在有一个更重要的提示。如果我在 Report Builder 示例中将 AuthType 设置为 satNone,它就可以工作。在我的第一个示例中,我可以将 AuthType 设置为 satNone 和 satDefault 并且两者都有效。

有什么想法吗?

非常感谢您的宝贵时间。

What could be causing Indy to send a QUIT immediately after receiving a HELLO?

唯一一次发送 QUIT 是在调用 TIdSMTP.Disconnect() 时。

TIdSMTP 本身调用 Disconnect() 的唯一时间是:

  1. TIdSMTP.Connect() 内部引发异常,例如服务器的问候语有错误代码,或者在解析服务器的问候语或 EHLO 响应时出现意外问题.

  2. TIdSMTP.StartTLS() 内部引发异常,它由 TIdSMTP.Authenticate() 调用(如果事先未调用,则由 TIdSMTP.Send() 调用)。但是,由于您设置了 UseTLS=utUseExplicitTLS 并且服务器的 EHLO 响应不通告对 STARTTLS 的支持,因此 TIdSMTP.StartTLS() 在该服务器上实际上是一个空操作。

I am not getting any errors. It just silently fails and the program moves on.

除非 Report Builder 在内部捕获异常并且没有将它们传递到您的代码中,否则最可能的情况是 Report Builder 本身正在调用 TIdSMTP.Disconnect() 而没有先调用 TIdSMTP.Send()。日志中显示的 RSET 命令由电子邮件开头的 TIdSMTP.Send() 发送(顺便说一句,Indy 的最新版本不再发送 RSET 除非电子邮件失败并显示 SMTP 错误代码). Report Builder 可能只是跳过 Send(),我能想到它可能会这样做的一个可能原因。

AuthType=satDefault 使用 AUTH LOGIN 命令(这不是安全命令),但您的服务器的 EHLO 响应不是广告支持 LOGIN 身份验证(实际上,它根本不是对 any 身份验证的广告支持)。因此,TIdSMTP.Authenticate() 将默认跳过此服务器上的身份验证和 return False,并将 TIdSMTP.DidAuthenticate 属性 设置为 False。也许 Report Builder 正在直接调用 TIdSMTP.Authenticate() 并在调用 TIdSMTP.Send() 之前检查结果。您的非 ReportBuilder 示例不进行该验证。设置 AuthType=satNone 将导致 TIdSMTP.Authenticate() 为 return True,并将 TIdSMTP.DidAuthenticate 属性 设置为 True。

如果服务器刚好支持LOGIN认证(有些服务器不做广告就支持),你可以设置TIdSMTP.ValidateAuthLoginCapability属性 设置为 False(默认情况下为 True)以使 satDefault 尝试身份验证,只要 TIdSMTP.Username 属性 已分配了非空字符串。