我正在使用 C++Builder 11.3 中的 Indy 组件尝试从我们的 RAD 服务器项目中发送电子邮件。 出于测试目的,我编写了下面的 64 位代码,并且收到了一个错误,该错误已由 Remy 在 Delphi 11 中对 “SSL 协商失败”的回答中解决。
我完全从 Remy 的帖子中移植了代码,除了 C++ 警告之外,我收到以下错误:
用户名和密码不被接受以及指向“support.google.com.../mail/?p=Bad Credentials”的 URL。
我想知道该问题是否与 SSL/TLS 版本有关?
如果有人发现代码有任何问题,请告诉我。如果有人认为这是 SSL/TLS 问题,并且知道在哪里可以找到 64 位 Windows/Windows Server 2019 的正确版本,我将非常感谢为我指明正确的方向。
void __fastcall TMainForm::SendBtnClick(TObject *Sender)
{
try
{
// Point to SSL
IdOpenSSLSetLibPath(L"C:\\openssl");
// Set up SMTP
TIdSMTP *IdSMTP = new TIdSMTP(NULL);
IdSMTP->Host = "smtp.gmail.com";
IdSMTP->Port = 587;
IdSMTP->Username = "[email protected]";
IdSMTP->Password = "my_password_phrase";
IdSMTP->AuthType = satDefault;
// Set up SSL/TLS
TIdSSLIOHandlerSocketOpenSSL *IdSSL = new TIdSSLIOHandlerSocketOpenSSL(IdSMTP);
IdSSL->SSLOptions->SSLVersions << sslvTLSv1 << sslvTLSv1_1 << sslvTLSv1_2;
IdSSL->SSLOptions->Mode = sslmUnassigned;
IdSSL->SSLOptions->VerifyMode = TIdSSLVerifyModeSet();
IdSSL->SSLOptions->VerifyDepth = 0;
IdSMTP->IOHandler = IdSSL;
// Had to move the IdSMTP TLS property below the IOHandler as
// it appears that in C++ you should have your IOHandler set
// up before setting the IdSMPT property, or you will get,
// "SSL IOHandler is required for this setting" exception.
IdSMTP->UseTLS = utUseExplicitTLS;
// Set up Message
TIdMessage *IdMessage = new TIdMessage(NULL);
IdMessage->From->Address = "[email protected]";
IdMessage->Recipients->Add()->Address = "[email protected]";
IdMessage->Subject = "Test Email";
IdMessage->Body->Text = "Super Cool Test...";
try {
IdSMTP->Connect();
IdSMTP->Send(IdMessage);
IdSMTP->Disconnect();
} catch (const Exception &e) {
ErrorMemo->Lines->Add("[Connect Exception]: " + e.Message);
}
// Tidy up
delete IdSMTP;
delete IdSSL;
delete IdMessage;
ErrorMemo->Lines->Add("No leaks");
}
catch (const Exception &exception)
{
ErrorMemo->Lines->Add("[Method Exception]: " + exception.Message);
}
}
我尝试使用设计时组件属性和很少的代码来实现您在上面看到的内容。 另外,我通过搜索安装了几个不同的二进制版本的 SSL 软件包,但似乎无法使其工作。
由于您尝试使用用户名+密码,请确保您的 Gmail 帐户配置为允许这样做。 如果您启用了双因素验证,那么您需要创建一个
应用程序密码,而不是使用真实密码。 否则,您将必须启用“不太安全的应用程序”才能使用您的真实密码。
替代方案是使用 OAuth 身份验证,这是 Gmail 目前更喜欢的方法。目前,Indy 不
官方支持 OAuth,但是 Indy 的 GitHub 存储库中有一个
sasl-oauth
分支可用(尚未合并到主代码中),它添加了新的 TIdSASL...
组件对于 OAuth。例如,
TIdSASLXOAuth2
可以与 Gmail 一起使用。您可以将
TIdSMTP::AuthType
属性设置为
satSASL
,然后在
TIdSASLXOAuth2
集合中包含
TIdSMTP::SASLMechanism
对象。 例如:
__fastcall TMainForm::TMainForm(TComponent *Owner)
: TForm(Owner)
{
// Point to SSL
IdOpenSSLSetLibPath(_D("C:\\openssl"));
}
void __fastcall TMainForm::SendBtnClick(TObject *Sender)
{
try
{
String OAuthToken = ...; // see comment below...
// Set up SMTP
TIdSMTP *IdSMTP = new TIdSMTP(NULL);
IdSMTP->Host = _D("smtp.gmail.com");
IdSMTP->Port = 587;
IdSMTP->AuthType = satSASL;
// Set up SSL/TLS
TIdSSLIOHandlerSocketOpenSSL *IdSSL = new TIdSSLIOHandlerSocketOpenSSL(IdSMTP);
IdSSL->SSLOptions->SSLVersions = TIdSSLVersions() << sslvTLSv1 << sslvTLSv1_1 << sslvTLSv1_2;
IdSSL->SSLOptions->Mode = sslmUnassigned;
IdSSL->SSLOptions->VerifyMode = TIdSSLVerifyModeSet();
IdSSL->SSLOptions->VerifyDepth = 0;
IdSMTP->IOHandler = IdSSL;
// Had to move the IdSMTP TLS property below the IOHandler as
// it appears that you should have your IOHandler set up
// before setting the UseTLS property, or you will get a
// "SSL IOHandler is required for this setting" exception.
IdSMTP->UseTLS = utUseExplicitTLS;
// Set up SASL
TIdSASLXOAuth2 *IdOAuth = new TIdSASLXOAuth2(IdSMTP);
IdOAuth->Username = _D("[email protected]");
IdOAuth->Password = OAuthToken;
IdSMTP->SASLMechanisms->Add()->SASL = IdOAuth;
// Set up Message
TIdMessage *IdMessage = new TIdMessage(IdSMTP);
IdMessage->From->Address = _D("[email protected]");
IdMessage->Recipients->Add()->Address = _D("[email protected]");
IdMessage->Subject = _D("Test Email");
IdMessage->Body->Text = _D("Super Cool Test...");
try {
IdSMTP->Connect();
try {
IdSMTP->Send(IdMessage);
} __finally {
IdSMTP->Disconnect();
}
} catch (const Exception &e) {
ErrorMemo->Lines->Add("[SMTP Exception]: " + e.Message);
}
// Tidy up
delete IdSMTP;
ErrorMemo->Lines->Add("No leaks");
}
catch (const Exception &exception)
{
ErrorMemo->Lines->Add("[Method Exception]: " + exception.Message);
}
}
您只需负责从 Gmail 手动获取 OAuth 访问令牌,例如通过 HTTP(有关详细信息,请参阅使用 OAuth 2.0 访问 Google API),然后您可以将该令牌分配给 TIdSASLXOAuth2::Password
属性,如图所示多于。或者,您可以从
TIdSASLXOAuth2::OnGetAccessToken
事件返回它,例如:
void __fastcall TMainForm::GetAccessToken(TObject* Sender, String &AccessToken)
{
AccessToken = ...;
}
...
// Set up SASL
TIdSASLXOAuth2 *IdOAuth = new TIdSASLXOAuth2(IdSMTP);
IdOAuth->Username = _D("[email protected]");
IdOAuth->OnGetAccessToken = &GetAccessToken;
话虽如此,如果您不想安装分支代码,您可以简单地使用 TIdSMTP::SendCmd()
方法在调用
XOUATH2
之前手动发送必要的 TIdSMTP::Send()
SMTP 命令(详细信息请参阅OAuth 2.0 机制) (请务必事先将
TIdSMTP::AuthType
设置为
satNone
)。
String user = _D("[email protected]");
IdSMTP->SendCmd(_D("user=") + user + _D("\x01auth=Bearer ") + OAuthToken + _D("\x01\x01");