opensslsmtpgmailc++builderindy10

We are having a Gmail login issue with Indy TIdSMTP


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.


Solution

  • 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();
    }