Jump to content
HeartWare

IMAP using TIdIMAP4

Recommended Posts

I currently have the following code:

Quote

 

procedure TForm141.ConnectBtnClick(Sender: TObject);
  VAR
    IMAP        : TIdIMAP4;
    SSL         : TIdSSLIOHandlerSocketOpenSSL;

  BEGIN
    IMAP:=TIdIMAP4.Create(NIL);
    TRY
      SSL:=TIdSSLIOHandlerSocketOpenSSL.Create(IMAP);
      IMAP.Host:=Host.Text;
      IMAP.Username:=UserName.Text;
      IMAP.Password:=Password.Text;
      IMAP.IOHandler:=SSL;
      IMAP.AuthType:=TIdIMAP4AuthenticationType.iatUserPass;
      IMAP.UseTLS:=TIdUseTLS.utUseImplicitTLS;
      SSL.SSLOptions.SSLVersions:=[TIdSSLVersion.sslvTLSv1_2];
      IMAP.Port:=993;
      IMAP.Connect;
      TRY
        Information('Messages: '+IntToStr(IMAP.MailBox.TotalMsgs))
      FINALLY
        IMAP.Disconnect
      END
    FINALLY
      FreeAndNIL(IMAP)
    END
  END;

 

 

but when I try to connect to Microsoft Office365 (at outlook.office365.com) I can't get it to work (get "LOGIN failed" exception). Is there some other property of the TIdIMAP that I need to set? And what exactly should I put into the UserName/Password fields?

 

Anyone have some working code that connects to outlook.office365.com using TIdIMAP4 ?

 

Keld R. Hansen

Share this post


Link to post

Outlook365 requires OAuth authentication:

 

https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth

 

At this time, Indy does not have any components for retrieving an access token from an OAuth provider, so you will have to handle that yourself manually per the above documentation, such as with TIdHTTP.  However, there is currently a sasl-oauth branch in Indy's GitHub repo (it hasn't been merged into the main code yet) which adds a few new TIdSASL components for sending OAuth access tokens for authentication in POP3, SMTP, IMAP, and DICT protocols.  So, in this case, you could add the TIdSASLXOAuth2 component to the TIdIMAP4.SASLMechanisms property, and then use the OAuth access token as the password.

  • Like 1

Share this post


Link to post

Thank you, Remy.

 

I have downloaded the files, explicitly added the necessary .PAS files to my project (to force the use of these instead of the Indy10 in my RAD Studio installation), and added the following lines:

Quote

      SASL:=TIdSASLXOAuth2.Create(IMAP);
      // What do I need to initialize in SASL ?
      IMAP.SASLMechanisms.Add.SASL:=SASL;

after the "UseTLS" line.

 

I assume I need to assign something to SASL.UserPassProvider and/or call some of the Autheticate functions. But what should I put into the parameters of the Authenticate calls? Host/Protocol I assume will be outlook.office365.com and 993 (?) but what about "ProtocolName" and what do I send/receive in VirtualResponse? Where do I put the base64 encoded string with user and bearer token? In OnGetAccessToken, I assume?

 

What final value from SASL should I assign to IMAP.Password after authentication? Or does that happen internally, due to my adding SASL to the IMAP's SASLMechanisms?

 

Can you help me out, please? 🙂

Edited by HeartWare

Share this post


Link to post
9 hours ago, HeartWare said:

I assume I need to assign something to SASL.UserPassProvider and/or call some of the Autheticate functions.

No, you do not call the Authenticate methods directly.

 

Yes, you need to assign a TIdUserPassProvider object to TIdSASLXOAuth2.UserProvider, and then assign the IMAP username to TIdUserPassProvider.Username, and either 1) assign the OAuth access token to TIdUserPassProvider.Password, or 2) set the TIdUserPassProvider.Password to a blank string and instead assign a handler to the TIdSASLXOAuth2.OnGetAccessToken event to return the access token.

 

Then, simply set TIdIMAP4.AuthType to iatSASL, and call TIdIMAP4.Connect() with AAutoLogin=True (which is the default behavior), or call Connect() with AAutoLogin=False and then call TIdIMAP4.Login() when needed.

Quote

But what should I put into the parameters of the Authenticate calls?

You don't call them yourself at all.

Quote

Host/Protocol I assume will be outlook.office365.com and 993 (?) but what about "ProtocolName" and what do I send/receive in VirtualResponse? Where do I put the base64 encoded string with user and bearer token? In OnGetAccessToken, I assume?

No to all of that.  TIdIMAP4 and TIdSASLXOAuth2 will handle all of that detail internally for you.  All you have to provide is the access token, as given to you by the OAuth provider.

Quote

What final value from SASL should I assign to IMAP.Password after authentication?

Just the OAuth access token.  But it needs to be provided by TIdUserPassProvider.Password or TIdSASLXOAuth2.OnGetAccessToken, not by TIdIMAP4.Password.  The TIdIMAP4.Username and TIdIMAP4.Password properties are used only when TIdIMAP4.AuthType=iatUserPass.

Quote

Or does that happen internally, due to my adding SASL to the IMAP's SASLMechanisms?

Yes.

Edited by Remy Lebeau

Share this post


Link to post

Thank you very much, Remy. I have now come a bit longer, but it still fails (maybe I'm feeding it the wrong "secret key"). I have this code now:

Quote

 

PROCEDURE TForm141.ConnectBtnClick(Sender: TObject);
  VAR
    IMAP        : TIdIMAP4;
    SSL         : TIdSSLIOHandlerSocketOpenSSL;
    PASS        : TIdUserPassProvider;
    SASL        : TIdSASLXOAuth2;

  BEGIN
    IMAP:=TIdIMAP4.Create(NIL);
    TRY
      IMAP.Host:=Host.Text;
      IMAP.AuthType:=TIdIMAP4AuthenticationType.iatSASL;
      SSL:=TIdSSLIOHandlerSocketOpenSSL.Create(IMAP);
      IMAP.IOHandler:=SSL;
      IMAP.UseTLS:=TIdUseTLS.utUseImplicitTLS;
      PASS:=TIdUserPassProvider.Create(IMAP);
      PASS.Username:=UserName.Text;
      PASS.Password:=SecretKey;
      SASL:=TIdSASLXOAuth2.Create(IMAP);
      SASL.UserPassProvider:=PASS;
      IMAP.SASLMechanisms.Add.SASL:=SASL;
      SSL.SSLOptions.SSLVersions:=[TIdSSLVersion.sslvTLSv1_2];
      IMAP.Port:=993;
      IMAP.Connect;
      TRY
        Information('Messages: '+IntToStr(IMAP.MailBox.TotalMsgs))
      FINALLY
        IMAP.Disconnect
      END
    FINALLY
      FreeAndNIL(IMAP)
    END
  END;

 

 

but it fails in IdIMAP4.PAS at this line:

Quote

 

function PerformSASLLogin_IMAP(ASASL: TIdSASL; AEncoder: TIdEncoder;
  ADecoder: TIdDecoder; AClient : TIdIMAP4): Boolean;
const
  AOkReplies: array[0..0] of string = (IMAP_OK);
  AContinueReplies: array[0..0] of string = (IMAP_CONT);
var
  S: String;
  AuthStarted: Boolean;
begin
  Result := False;
  AuthStarted := False;

  // TODO: use UTF-8 when base64-encoding strings...

  if AClient.IsCapabilityListed('SASL-IR') then begin {Do not localize}
    if ASASL.TryStartAuthenticate(AClient.Host, AClient.Port, IdGSKSSN_imap, S) then begin
      AClient.SendCmd(AClient.NewCmdCounter, 'AUTHENTICATE ' + String(ASASL.ServiceName) + ' ' + AEncoder.Encode(S), [], True); {Do not Localize}
      if CheckStrFail(AClient.LastCmdResult.Code, AOkReplies, AContinueReplies) then begin
        ASASL.FinishAuthenticate;
        Exit; // this mechanism is not supported // <--- Exits here
      end;
      AuthStarted := True;
    end;
  end;

 

 

And ASASL.Service = 'XOAUTH2' and S = 'user=<MailBox@domain>'#1'auth=Bearer <Secret Key>'#1#1 and AClient.LastCmdResult.Code = 'NO'

 

When I generated my tokens/keys according to Outlook documentation, I got two values. I have tried with both of them, and they both fail at this spot.

 

Is there a way where I can generate the token needed in code somehow, or do I need to use the value I was given via the online web system?

 

Edited by HeartWare

Share this post


Link to post

This is the communication log (>> is me sending, << is receiveing)

Quote

 


<< * OK The Microsoft Exchange IMAP4 service is ready. [TQBNADAAUAAyADgAMABDAEEAMAAwADgAMAAuAFMAVwBFAFAAMgA4ADAALgBQAFIATwBEAC4ATwBVAFQATABPAE8ASwAuAEMATwBNAA==]
>> C1 CAPABILITY
<< * CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN AUTH=XOAUTH2 SASL-IR UIDPLUS ID UNSELECT CHILDREN IDLE NAMESPACE LITERAL+
<< C1 OK CAPABILITY completed.
>> C2 AUTHENTICATE XOAUTH2 dXNlcj1ib3VuY2VAYWRtaW5kLmRrAWF1d******DQyLWM4YzEtNGJhYy1iN2Y1LTQxN2Y4ZmZlOTc0ZAEB
<< C2 NO AUTHENTICATE failed.
>> C3 LOGOUT
<< * BYE Microsoft Exchange Server IMAP4 server signing off.
<< C3 OK LOGOUT completed.

 

 

Share this post


Link to post
10 hours ago, HeartWare said:

When I generated my tokens/keys according to Outlook documentation, I got two values. I have tried with both of them, and they both fail at this spot.

You are not supposed to use your secret keys as-is for the login token.  You are supposed to use the keys to request an access token dynamically, and then use that token instead for login.  NEVER give out your secret keys!

Quote

Is there a way where I can generate the token needed in code somehow, or do I need to use the value I was given via the online web system?

Did you read the documentation I linked to earlier? Authenticate an IMAP, POP or SMTP connection using OAuth

Quote

You can use the OAuth authentication service provided by Azure Active Directory (Azure AD) to enable your application to connect with IMAP, POP or SMTP protocols to access Exchange Online in Office 365. To use OAuth with your application, you need to:

  1. Register your application with Azure AD.
  2. Get an access token from a token server.
  3. Authenticate connection requests with an access token.

In particular, make sure you read the section titled "Get an access token".  You are responsible for handling Steps 1 (which presumably you have already done) and Step 2 (the piece that is currently missing), and TIdIMAP4/TIdSASLXOuth2 will handle step 3 for you.

Edited by Remy Lebeau

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×