I'm using the Indy components in C++Builder 11.3 to attempt to send an email from within our RAD Server project. For testing purposes, I wrote the 64-bit code below, and I'm getting an error that has been addressed before by Remy in his answer to "SSL negotiation failed" in Delphi 11.
I ported the code exactly from Remy's post, except for the C++ caveats, and I'm getting the following error:
Username and Password not accepted
and a URL pointing to "support.google.com.../mail/?p=Bad Credentials".
I'm wondering if the issue is related to SSL/TLS versions?
If anyone sees anything wrong with the code, please let me know. If anyone believes it's an SSL/TLS issue, and knows where I can find the correct versions for 64-bit Windows/Windows Server 2019, I would very much appreciate pointing me in the right direction.
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 = "myEmailAddress@our-domain.com";
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 = "myEmailAddress@our-domain.com";
IdMessage->Recipients->Add()->Address = "myOtherEmail@gmail.com";
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);
}
}
I tried using the design-time component properties and very little code to what you see above. Also, I've installed several different binary versions of the SSL packages from searches, but can't seem to get it working.
SSL/TLS and Authentication are two separate things. The error in question has nothing to do with SSL/TLS.
Since you are trying to use a username+password, make sure your Gmail account is configured to allow that. If you have 2-Factor Verification enabled, then you need to create an App Password instead of using your real password. Otherwise, you will have to enable "less secure apps" to use your real password.
The alternative is to use OAuth authentication instead, which is what Gmail prefers nowadays.
At this time, Indy does not officially support OAuth, however there is a sasl-oauth
branch available in Indy's GitHub repo (it has not been merged into the main code yet) which adds new TIdSASL...
components for OAuth. For instance, TIdSASLXOAuth2
can be used with Gmail. You can set the TIdSMTP::AuthType
property to satSASL
and then include a TIdSASLXOAuth2
object in the TIdSMTP::SASLMechanisms
collection, eg:
__fastcall TMainForm::TMainForm(TComponent *Owner)
: TForm(Owner)
{
// Point to SSL
IdOpenSSLSetLibPath(_D("C:\\openssl"));
}
void __fastcall TMainForm::SendBtnClick(TObject *Sender)
{
try
{
String OAuthToken = ...; // see further 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("myEmailAddress@our-domain.com");
IdOAuth->Password = OAuthToken;
IdSMTP->SASLMechanisms->Add()->SASL = IdOAuth;
// Set up Message
TIdMessage *IdMessage = new TIdMessage(IdSMTP);
IdMessage->From->Address = _D("myEmailAddress@our-domain.com");
IdMessage->Recipients->Add()->Address = _D("myOtherEmail@gmail.com");
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);
}
}
You would just be responsible for manually obtaining an OAuth access token from Gmail, such as via HTTP (see Using OAuth 2.0 to Access Google APIs for details), and then you can assign that token to the TIdSASLXOAuth2::Password
property as shown above. Or, you can instead return it from the TIdSASLXOAuth2::OnGetAccessToken
event, eg:
void __fastcall TMainForm::GetAccessToken(TObject* Sender, String &AccessToken)
{
AccessToken = ...;
}
...
// Set up SASL
TIdSASLXOAuth2 *IdOAuth = new TIdSASLXOAuth2(IdSMTP);
IdOAuth->Username = _D("myEmailAddress@our-domain.com");
IdOAuth->OnGetAccessToken = &GetAccessToken;
That being said, if you don't want to install the branch code, you can simply use the TIdSMTP::SendCmd()
method to manually send the necessary AUTH XOAUTH2
SMTP command (see OAuth 2.0 Mechanism for details) before calling TIdSMTP::Send()
(just be sure to set TIdSMTP::AuthType
to satNone
beforehand), eg:
IdSMTP->Connect();
try {
String user = _D("myEmailAddress@our-domain.com");
IdSMTP->SendCmd(_D("AUTH XOAUTH2 ") + TIdEncoderMIME::EncodeString(_D("user=") + user + _D("\x01auth=Bearer ") + OAuthToken + _D("\x01\x01")), 235);
IdSMTP->Send(IdMessage);
} __finally {
IdSMTP->Disconnect();
}