Bug 849540 - Log in to Gmail (IMAP/SMTP) using OAuth, backend part, r=rkent, a=rkent
authorJoshua Cranmer <Pidgeot18@gmail.com>
Fri, 10 Apr 2015 23:19:00 -0700
changeset 22002 80ec65353d8f
parent 22001 de98ea1bf621
child 22003 ff42e50d846c
child 22005 4231e422562a
push id1338
push userkent@caspia.com
push dateFri, 17 Apr 2015 04:13:17 +0000
treeherdercomm-beta@80ec65353d8f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrkent, rkent
bugs849540
Bug 849540 - Log in to Gmail (IMAP/SMTP) using OAuth, backend part, r=rkent, a=rkent
mailnews/base/src/msgOAuth2Module.js
mailnews/base/util/OAuth2.jsm
mailnews/compose/src/nsSmtpProtocol.cpp
mailnews/compose/src/nsSmtpProtocol.h
mailnews/imap/src/nsImapCore.h
mailnews/imap/src/nsImapProtocol.cpp
mailnews/imap/src/nsImapProtocol.h
mailnews/imap/src/nsImapServerResponseParser.cpp
mailnews/imap/src/nsSyncRunnableHelpers.cpp
mailnews/imap/src/nsSyncRunnableHelpers.h
--- a/mailnews/base/src/msgOAuth2Module.js
+++ b/mailnews/base/src/msgOAuth2Module.js
@@ -23,18 +23,18 @@ OAuth2Module.prototype = {
 
   _loadOAuthClientDetails(aIssuer) {
     if (aIssuer == "accounts.google.com") {
       // For the moment, these details are hard-coded, since Google does not
       // provide dynamic client registration. Don't copy these values for your
       // own application--register it yourself. This code (and possibly even the
       // registration itself) will disappear when this is switched to dynamic
       // client registration.
-      this._appKey = '572172754692-vfo2oqvu2oju9be729s915glghp1vpfj.apps.googleusercontent.com';
-      this._appSecret = 'YpeuM0eYPQe_r98HZ7p16zUm';
+      this._appKey = '406964657835-aq8lmia8j95dhl1a2bvharmfk3t1hgqj.apps.googleusercontent.com';
+      this._appSecret = 'kSmqreRr0qwBWJgbf5Y-PjSU';
       this._authURI = "https://accounts.google.com/o/oauth2/auth";
       this._tokenURI = "https://www.googleapis.com/oauth2/v3/token";
     } else {
       throw Cr.NS_ERROR_INVALID_ARGUMENT;
     }
   },
   initFromSmtp(aServer) {
     return this._initPrefs("mail.smtpserver." + aServer.key + ".",
@@ -46,18 +46,28 @@ OAuth2Module.prototype = {
   },
   _initPrefs(root, aUsername, aHostname) {
     // Load all of the parameters from preferences.
     let issuer = Preferences.get(root + "oauth2.issuer", "");
     let scope = Preferences.get(root + "oauth2.scope", "");
 
     // These properties are absolutely essential to OAuth2 support. If we don't
     // have them, we don't support OAuth2.
-    if (!issuer || !scope)
-      return false;
+    if (!issuer || !scope) {
+      // Since we currently only support gmail, init values if server matches.
+      if (aHostname == "imap.googlemail.com" || aHostname == "smtp.googlemail.com")
+      {
+        issuer = "accounts.google.com";
+        scope = "http://mail.google.com/";
+        Preferences.set(root + "oauth2.issuer", issuer);
+        Preferences.set(root + "oauth2.scope", scope);
+      }
+      else
+        return false;
+    }
 
     // Find the app key we need for the OAuth2 string. Eventually, this should
     // be using dynamic client registration, but there are no current
     // implementations that we can test this with.
     this._loadOAuthClientDetails(issuer);
 
     // Username is needed to generate the XOAUTH2 string.
     this._username = aUsername;
@@ -105,19 +115,27 @@ OAuth2Module.prototype = {
   set refreshToken(token) {
     let loginMgr = Cc["@mozilla.org/login-manager;1"]
                      .getService(Ci.nsILoginManager);
 
     // Check if we already have a login with this username, and modify the
     // password on that, if we do.
     let logins = loginMgr.findLogins({}, this._loginUrl, null, this._scope);
     for (let login of logins) {
-      if (login.username == this._username)
-        loginMgr.modifyLogin(login, {password: token});
-      return token;
+      if (login.username == this._username) {
+        if (token) {
+          let propBag = Cc["@mozilla.org/hash-property-bag;1"].
+                        createInstance(Ci.nsIWritablePropertyBag);
+          propBag.setProperty("password", token);
+          loginMgr.modifyLogin(login, propBag);
+        }
+        else
+          loginMgr.removeLogin(login);
+        return token;
+      }
     }
 
     // Otherwise, we need a new login, so create one and fill it in.
     let login = Cc["@mozilla.org/login-manager/loginInfo;1"]
                   .createInstance(Ci.nsILoginInfo);
     login.init(this._loginUrl, null, this._scope, this._username, token,
       '', '');
     loginMgr.addLogin(login);
--- a/mailnews/base/util/OAuth2.jsm
+++ b/mailnews/base/util/OAuth2.jsm
@@ -18,16 +18,19 @@ function parseURLData(aData) {
   let result = {};
   aData.split(/[?#]/, 2)[1].split("&").forEach(function (aParam) {
     let [key, value] = aParam.split("=");
     result[key] = value;
   });
   return result;
 }
 
+// Only allow one connecting window per endpoint.
+var gConnecting = {};
+
 function OAuth2(aBaseURI, aScope, aAppKey, aAppSecret) {
     this.authURI = aBaseURI + "oauth2/auth";
     this.tokenURI = aBaseURI + "oauth2/token";
     this.consumerKey = aAppKey;
     this.consumerSecret = aAppSecret;
     this.scope = aScope;
     this.extraAuthParams = [];
 
@@ -46,37 +49,37 @@ OAuth2.prototype = {
     requestWindowURI: "chrome://messenger/content/browserRequest.xul",
     requestWindowFeatures: "chrome,private,centerscreen,width=980,height=600",
     requestWindowTitle: "",
     scope: null,
 
     accessToken: null,
     refreshToken: null,
     tokenExpires: 0,
-    connecting: false,
 
     connect: function connect(aSuccess, aFailure, aWithUI, aRefresh) {
-        if (this.connecting) {
+        if (gConnecting[this.authURI]) {
+            aFailure("Window already open");
             return;
         }
 
         this.connectSuccessCallback = aSuccess;
         this.connectFailureCallback = aFailure;
 
         if (!aRefresh && this.accessToken) {
             aSuccess();
         } else if (this.refreshToken) {
-            this.connecting = true;
+            gConnecting[this.authURI] = true;
             this.requestAccessToken(this.refreshToken, OAuth2.CODE_REFRESH);
         } else {
             if (!aWithUI) {
                 aFailure('{ "error": "auth_noui" }');
                 return;
             }
-            this.connecting = true;
+            gConnecting[this.authURI] = true;
             this.requestAuthorization();
         }
     },
 
     requestAuthorization: function requestAuthorization() {
         let params = [
             ["response_type", this.responseType],
             ["client_id", this.consumerKey],
@@ -165,25 +168,27 @@ OAuth2.prototype = {
             this._browserRequest._listener._cleanUp();
         }
         delete this._browserRequest;
     },
 
     onAuthorizationReceived: function(aData) {
         this.log.info("authorization received" + aData);
         let results = parseURLData(aData);
-        if (this.responseType == "code") {
+        if (this.responseType == "code" && results.code) {
             this.requestAccessToken(results.code, OAuth2.CODE_AUTHORIZATION);
         } else if (this.responseType == "token") {
             this.onAccessTokenReceived(JSON.stringify(results));
         }
+        else
+          this.onAuthorizationFailed(null, aData);
     },
 
     onAuthorizationFailed: function(aError, aData) {
-        this.connecting = false;
+        gConnecting[this.authURI] = false;
         this.connectFailureCallback(aData);
     },
 
     requestAccessToken: function requestAccessToken(aCode, aType) {
         let params = [
             ["client_id", this.consumerKey],
             ["client_secret", this.consumerSecret],
             ["grant_type", aType],
@@ -200,33 +205,33 @@ OAuth2.prototype = {
           postData: params,
           onLoad: this.onAccessTokenReceived.bind(this),
           onError: this.onAccessTokenFailed.bind(this)
         }
         httpRequest(this.tokenURI, options);
     },
 
     onAccessTokenFailed: function onAccessTokenFailed(aError, aData) {
+        gConnecting[this.authURI] = false;
         if (aError != "offline") {
             this.refreshToken = null;
         }
-        this.connecting = false;
         this.connectFailureCallback(aData);
     },
 
     onAccessTokenReceived: function onRequestTokenReceived(aData) {
+        gConnecting[this.authURI] = false;
         let result = JSON.parse(aData);
 
         this.accessToken = result.access_token;
         if ("refresh_token" in result) {
             this.refreshToken = result.refresh_token;
         }
         if ("expires_in" in result) {
             this.tokenExpires = (new Date()).getTime() + (result.expires_in * 1000);
         } else {
             this.tokenExpires = Number.MAX_VALUE;
         }
         this.tokenType = result.token_type;
 
-        this.connecting = false;
         this.connectSuccessCallback();
     }
 };
--- a/mailnews/compose/src/nsSmtpProtocol.cpp
+++ b/mailnews/compose/src/nsSmtpProtocol.cpp
@@ -201,21 +201,18 @@ esmtp_value_encode(const char *addr)
   *bp=0;
   return buffer;
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////
 // END OF TEMPORARY HARD CODED FUNCTIONS
 ///////////////////////////////////////////////////////////////////////////////////////////
 
-NS_IMPL_ADDREF_INHERITED(nsSmtpProtocol, nsMsgAsyncWriteProtocol)
-NS_IMPL_RELEASE_INHERITED(nsSmtpProtocol, nsMsgAsyncWriteProtocol)
-
-NS_INTERFACE_MAP_BEGIN(nsSmtpProtocol)
-NS_INTERFACE_MAP_END_INHERITING(nsMsgAsyncWriteProtocol)
+NS_IMPL_ISUPPORTS_INHERITED(nsSmtpProtocol, nsMsgAsyncWriteProtocol,
+                            msgIOAuth2ModuleListener)
 
 nsSmtpProtocol::nsSmtpProtocol(nsIURI * aURL)
     : nsMsgAsyncWriteProtocol(aURL)
 {
 }
 
 nsSmtpProtocol::~nsSmtpProtocol()
 {
@@ -278,16 +275,28 @@ void nsSmtpProtocol::Initialize(nsIURI *
     // round trip communication between the client and server
     int32_t authMethod = 0;
     nsCOMPtr<nsISmtpServer> smtpServer;
     m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer));
     if (smtpServer) {
         smtpServer->GetAuthMethod(&authMethod);
         smtpServer->GetSocketType(&m_prefSocketType);
         smtpServer->GetHelloArgument(getter_Copies(m_helloArgument));
+
+        // Query for OAuth2 support. If the SMTP server preferences don't allow
+        // for OAuth2, then don't carry around the OAuth2 module any longer
+        // since we won't need it.
+        mOAuth2Support = do_CreateInstance(MSGIOAUTH2MODULE_CONTRACTID);
+        if (mOAuth2Support)
+        {
+          bool supportsOAuth = false;
+          mOAuth2Support->InitFromSmtp(smtpServer, &supportsOAuth);
+          if (!supportsOAuth)
+            mOAuth2Support = nullptr;
+        }
     }
     InitPrefAuthMethods(authMethod);
 
     nsAutoCString hostName;
     int32_t port = 0;
 
     aURL->GetPort(&port);
     aURL->GetAsciiHost(hostName);
@@ -760,16 +769,20 @@ nsresult nsSmtpProtocol::SendEhloRespons
 
           if (responseLine.Find(NS_LITERAL_CSTRING("LOGIN"),
                                 CaseInsensitiveCompare) >= 0)
             SetFlag(SMTP_AUTH_LOGIN_ENABLED);
 
           if (responseLine.Find(NS_LITERAL_CSTRING("EXTERNAL"),
                                 CaseInsensitiveCompare) >= 0)
             SetFlag(SMTP_AUTH_EXTERNAL_ENABLED);
+
+          if (responseLine.Find(NS_LITERAL_CSTRING("XOAUTH2"),
+                                CaseInsensitiveCompare) >= 0)
+            SetFlag(SMTP_AUTH_OAUTH2_ENABLED);
         }
         else if (StringBeginsWith(responseLine, NS_LITERAL_CSTRING("SIZE"), nsCaseInsensitiveCStringComparator()))
         {
             SetFlag(SMTP_EHLO_SIZE_ENABLED);
 
             m_sizelimit = atol((responseLine.get()) + 4);
         }
 
@@ -850,16 +863,19 @@ void nsSmtpProtocol::InitPrefAuthMethods
       break;
     case nsMsgAuthMethod::NTLM:
       m_prefAuthMethods = SMTP_AUTH_NTLM_ENABLED |
           SMTP_AUTH_MSN_ENABLED;
       break;
     case nsMsgAuthMethod::GSSAPI:
       m_prefAuthMethods = SMTP_AUTH_GSSAPI_ENABLED;
       break;
+    case nsMsgAuthMethod::OAuth2:
+      m_prefAuthMethods = SMTP_AUTH_OAUTH2_ENABLED;
+      break;
     case nsMsgAuthMethod::secure:
       m_prefAuthMethods = SMTP_AUTH_CRAM_MD5_ENABLED |
           SMTP_AUTH_GSSAPI_ENABLED |
           SMTP_AUTH_NTLM_ENABLED | SMTP_AUTH_MSN_ENABLED |
           SMTP_AUTH_EXTERNAL_ENABLED; // TODO: Expose EXTERNAL? How?
       break;
     default:
       NS_ASSERTION(false, "SMTP: authMethod pref invalid");
@@ -867,19 +883,25 @@ void nsSmtpProtocol::InitPrefAuthMethods
       PR_LOG(SMTPLogModule, PR_LOG_ERROR,
           ("SMTP: bad pref authMethod = %d\n", authMethodPrefValue));
       // fall to any
     case nsMsgAuthMethod::anything:
       m_prefAuthMethods =
           SMTP_AUTH_LOGIN_ENABLED | SMTP_AUTH_PLAIN_ENABLED |
           SMTP_AUTH_CRAM_MD5_ENABLED | SMTP_AUTH_GSSAPI_ENABLED |
           SMTP_AUTH_NTLM_ENABLED | SMTP_AUTH_MSN_ENABLED |
+          SMTP_AUTH_OAUTH2_ENABLED |
           SMTP_AUTH_EXTERNAL_ENABLED;
       break;
   }
+
+  // Only enable OAuth2 support if we can do the lookup.
+  if ((m_prefAuthMethods & SMTP_AUTH_OAUTH2_ENABLED) && !mOAuth2Support)
+    m_prefAuthMethods &= ~SMTP_AUTH_OAUTH2_ENABLED;
+
   NS_ASSERTION(m_prefAuthMethods != 0, "SMTP:InitPrefAuthMethods() failed");
 }
 
 /**
  * Changes m_currentAuthMethod to pick the next-best one
  * which is allowed by server and prefs and not marked failed.
  * The order of preference and trying of auth methods is encoded here.
  */
@@ -901,16 +923,18 @@ nsresult nsSmtpProtocol::ChooseAuthMetho
   if (SMTP_AUTH_GSSAPI_ENABLED & availCaps)
     m_currentAuthMethod = SMTP_AUTH_GSSAPI_ENABLED;
   else if (SMTP_AUTH_CRAM_MD5_ENABLED & availCaps)
     m_currentAuthMethod = SMTP_AUTH_CRAM_MD5_ENABLED;
   else if (SMTP_AUTH_NTLM_ENABLED & availCaps)
     m_currentAuthMethod = SMTP_AUTH_NTLM_ENABLED;
   else if (SMTP_AUTH_MSN_ENABLED & availCaps)
     m_currentAuthMethod = SMTP_AUTH_MSN_ENABLED;
+  else if (SMTP_AUTH_OAUTH2_ENABLED & availCaps)
+    m_currentAuthMethod = SMTP_AUTH_OAUTH2_ENABLED;
   else if (SMTP_AUTH_PLAIN_ENABLED & availCaps)
     m_currentAuthMethod = SMTP_AUTH_PLAIN_ENABLED;
   else if (SMTP_AUTH_LOGIN_ENABLED & availCaps)
     m_currentAuthMethod = SMTP_AUTH_LOGIN_ENABLED;
   else if (SMTP_AUTH_EXTERNAL_ENABLED & availCaps)
     m_currentAuthMethod = SMTP_AUTH_EXTERNAL_ENABLED;
   else
   {
@@ -1007,16 +1031,20 @@ nsresult nsSmtpProtocol::ProcessAuth()
   {
     m_nextState = SMTP_SEND_AUTH_LOGIN_STEP1;
   }
   else if (m_currentAuthMethod == SMTP_AUTH_LOGIN_ENABLED ||
            m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED)
   {
     m_nextState = SMTP_SEND_AUTH_LOGIN_STEP0;
   }
+  else if (m_currentAuthMethod == SMTP_AUTH_OAUTH2_ENABLED)
+  {
+    m_nextState = SMTP_AUTH_OAUTH2_STEP;
+  }
   else // All auth methods failed
   {
     // show an appropriate error msg
     if (m_failedAuthMethods == 0)
     {
       // we didn't even try anything, so we had a non-working config:
       // pref doesn't match server
       PR_LOG(SMTPLogModule, PR_LOG_ERROR,
@@ -1426,16 +1454,66 @@ nsresult nsSmtpProtocol::AuthLoginStep2(
     SetFlag(SMTP_PAUSE_FOR_READ);
     return (status);
   }
 
   // XXX -1 is not a valid nsresult
   return static_cast<nsresult>(-1);
 }
 
+nsresult nsSmtpProtocol::AuthOAuth2Step1()
+{
+  MOZ_ASSERT(mOAuth2Support, "Can't do anything without OAuth2 support");
+
+  nsresult rv = mOAuth2Support->Connect(true, this);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  m_nextState = SMTP_SUSPENDED;
+  return NS_OK;
+}
+
+nsresult nsSmtpProtocol::OnSuccess(const nsACString &aAccessToken)
+{
+  MOZ_ASSERT(mOAuth2Support, "Can't do anything without OAuth2 support");
+
+  nsCString base64Str;
+  mOAuth2Support->BuildXOAuth2String(base64Str);
+
+  // Send the AUTH XOAUTH2 command, and then siphon us back to the regular
+  // authentication login stream.
+  nsAutoCString buffer;
+  buffer.AppendLiteral("AUTH XOAUTH2 ");
+  buffer += base64Str;
+  buffer += CRLF;
+  nsresult rv = SendData(buffer.get(), true);
+  if (NS_FAILED(rv))
+  {
+    m_nextState = SMTP_ERROR_DONE;
+  }
+  else
+  {
+    m_nextState = SMTP_RESPONSE;
+    m_nextStateAfterResponse = SMTP_AUTH_LOGIN_RESPONSE;
+  }
+
+  SetFlag(SMTP_PAUSE_FOR_READ);
+
+  ProcessProtocolState(nullptr, nullptr, 0, 0);
+  return NS_OK;
+}
+
+nsresult nsSmtpProtocol::OnFailure(nsresult aError)
+{
+  PR_LOG(SMTPLogModule, PR_LOG_DEBUG, ("OAuth2 login error %08x",
+    (uint32_t)aError));
+  m_urlErrorState = aError;
+  m_nextState = SMTP_ERROR_DONE;
+  return ProcessProtocolState(nullptr, nullptr, 0, 0);
+}
+
 
 nsresult nsSmtpProtocol::SendMailResponse()
 {
   nsresult status = NS_OK;
   nsAutoCString buffer;
   nsresult rv;
 
   if (m_responseCode/10 != 25)
@@ -1916,16 +1994,20 @@ nsresult nsSmtpProtocol::ProcessProtocol
       case SMTP_SEND_AUTH_LOGIN_STEP1:
         status = AuthLoginStep1();
         break;
 
       case SMTP_SEND_AUTH_LOGIN_STEP2:
         status = AuthLoginStep2();
         break;
 
+      case SMTP_AUTH_OAUTH2_STEP:
+        status = AuthOAuth2Step1();
+        break;
+
 
       case SMTP_SEND_MAIL_RESPONSE:
         if (inputStream == nullptr)
           SetFlag(SMTP_PAUSE_FOR_READ);
         else
           status = SendMailResponse();
         break;
 
@@ -1973,16 +2055,22 @@ nsresult nsSmtpProtocol::ProcessProtocol
         m_nextState = SMTP_FREE;
         break;
 
       case SMTP_FREE:
         // smtp is a one time use connection so kill it if we get here...
         nsMsgAsyncWriteProtocol::CloseSocket();
         return NS_OK; /* final end */
 
+      // This state means we're going into an async loop and waiting for
+      // something (say auth) to happen. ProcessProtocolState will be
+      // retriggered when necessary.
+      case SMTP_SUSPENDED:
+        return NS_OK;
+
       default: /* should never happen !!! */
         m_nextState = SMTP_ERROR_DONE;
         break;
     }
 
     /* check for errors during load and call error
     * state if found
     */
--- a/mailnews/compose/src/nsSmtpProtocol.h
+++ b/mailnews/compose/src/nsSmtpProtocol.h
@@ -2,27 +2,31 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsSmtpProtocol_h___
 #define nsSmtpProtocol_h___
 
 #include "mozilla/Attributes.h"
+#include "msgIOAuth2Module.h"
 #include "nsMsgProtocol.h"
 #include "nsIStreamListener.h"
 #include "nsISmtpUrl.h"
 #include "nsIMsgStatusFeedback.h"
 #include "nsMsgLineBuffer.h"
 #include "nsIAuthModule.h"
 #include "MailNewsTypes2.h" // for nsMsgSocketType
 
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 
+class nsIVariant;
+class nsIWritableVariant;
+
  /* states of the machine
  */
 typedef enum _SmtpState {
 SMTP_RESPONSE = 0,                                  // 0
 SMTP_START_CONNECT,                                 // 1
 SMTP_FINISH_CONNECT,                                // 2
 SMTP_SEND_HELO_RESPONSE,                            // 3
 SMTP_SEND_EHLO_RESPONSE,                            // 4
@@ -40,17 +44,20 @@ SMTP_SEND_AUTH_LOGIN_STEP0,             
 SMTP_SEND_AUTH_LOGIN_STEP1,                         // 16
 SMTP_SEND_AUTH_LOGIN_STEP2,                         // 17
 SMTP_AUTH_LOGIN_RESPONSE,                           // 18
 SMTP_TLS_RESPONSE,                                  // 19
 SMTP_AUTH_EXTERNAL_RESPONSE,                        // 20
 SMTP_AUTH_PROCESS_STATE,                            // 21
 SMTP_AUTH_CRAM_MD5_CHALLENGE_RESPONSE,              // 22
 SMTP_SEND_AUTH_GSSAPI_FIRST,                        // 23
-SMTP_SEND_AUTH_GSSAPI_STEP                          // 24
+SMTP_SEND_AUTH_GSSAPI_STEP,                         // 24
+SMTP_SUSPENDED,                                     // 25
+SMTP_AUTH_OAUTH2_STEP,                              // 26
+SMTP_AUTH_OAUTH2_RESPONSE,                          // 27
 } SmtpState;
 
 // State Flags (Note, I use the word state in terms of storing 
 // state information about the connection (authentication, have we sent
 // commands, etc. I do not intend it to refer to protocol state)
 #define SMTP_PAUSE_FOR_READ             0x00000001  /* should we pause for the next read */
 #define SMTP_ESMTP_SERVER               0x00000002
 #define SMTP_EHLO_DSN_ENABLED           0x00000004
@@ -62,27 +69,30 @@ SMTP_SEND_AUTH_GSSAPI_STEP              
 #define SMTP_AUTH_PLAIN_ENABLED         0x00000200
 #define SMTP_AUTH_EXTERNAL_ENABLED      0x00000400
 // secure mechanisms follow
 #define SMTP_AUTH_GSSAPI_ENABLED        0x00000800
 #define SMTP_AUTH_DIGEST_MD5_ENABLED    0x00001000
 #define SMTP_AUTH_CRAM_MD5_ENABLED      0x00002000
 #define SMTP_AUTH_NTLM_ENABLED          0x00004000
 #define SMTP_AUTH_MSN_ENABLED           0x00008000
+#define SMTP_AUTH_OAUTH2_ENABLED        0x00010000
 // sum of all above auth mechanisms
-#define SMTP_AUTH_ANY                   0x0000FF00
+#define SMTP_AUTH_ANY                   0x0001FF00
 // indicates that AUTH has been advertised
-#define SMTP_AUTH                       0x00010000
+#define SMTP_AUTH                       0x00020000
 // No login necessary (pref)
-#define SMTP_AUTH_NONE_ENABLED          0x00020000
+#define SMTP_AUTH_NONE_ENABLED          0x00040000
 
-class nsSmtpProtocol : public nsMsgAsyncWriteProtocol
+class nsSmtpProtocol : public nsMsgAsyncWriteProtocol,
+                       public msgIOAuth2ModuleListener
 {
 public:
     NS_DECL_ISUPPORTS_INHERITED
+    NS_DECL_MSGIOAUTH2MODULELISTENER
 
     // Creating a protocol instance requires the URL which needs to be run.
     nsSmtpProtocol(nsIURI * aURL);
 
     virtual nsresult LoadUrl(nsIURI * aURL, nsISupports * aConsumer = nullptr) override;
     virtual nsresult SendData(const char * dataBuffer, bool aSuppressLogging = false) override;
 
     ////////////////////////////////////////////////////////////////////////////////////////
@@ -166,16 +176,17 @@ private:
 
     nsresult AuthGSSAPIFirst();
     nsresult AuthGSSAPIStep();
     nsresult AuthLoginStep0();
     void     AuthLoginStep0Response();
     nsresult AuthLoginStep1();
     nsresult AuthLoginStep2();
     nsresult AuthLoginResponse(nsIInputStream * stream, uint32_t length);
+    nsresult AuthOAuth2Step1();
 
     nsresult SendTLSResponse();
     nsresult SendMailResponse();
     nsresult SendRecipientResponse();
     nsresult SendDataResponse();
     void     SendPostData();
     nsresult SendMessageResponse();
     nsresult ProcessAuth();
@@ -199,11 +210,15 @@ private:
     void    MarkAuthMethodAsFailed(int32_t failedAuthMethod);
     void    ResetAuthMethods();
 
     virtual const char* GetType() override {return "smtp";}
 
     int32_t m_prefAuthMethods; // set of capability flags for auth methods
     int32_t m_failedAuthMethods; // ditto
     int32_t m_currentAuthMethod; // exactly one capability flag, or 0
+
+    // The support module for OAuth2 logon, only present if OAuth2 is enabled
+    // and working.
+    nsCOMPtr<msgIOAuth2Module> mOAuth2Support;
 };
 
 #endif  // nsSmtpProtocol_h___
--- a/mailnews/imap/src/nsImapCore.h
+++ b/mailnews/imap/src/nsImapCore.h
@@ -142,16 +142,17 @@ const eIMAPCapabilityFlag kHasXListCapab
 const eIMAPCapabilityFlag kHasCompressDeflateCapability = 0x10000000;  /* RFC 4978 COMPRESS extension */
 const eIMAPCapabilityFlag kHasAuthExternalCapability = 0x20000000;  /* RFC 2222 SASL AUTH EXTERNAL */
 const eIMAPCapabilityFlag kHasMoveCapability = 0x40000000;  /* Proposed MOVE RFC */
 const eIMAPCapabilityFlag kHasHighestModSeqCapability = 0x80000000;  /* Subset of RFC 3551 */
 // above are 32bit; below start the uint64_t bits 33-64
 const eIMAPCapabilityFlag kHasListExtendedCapability = 0x100000000LL;  /* RFC 5258 */
 const eIMAPCapabilityFlag kHasSpecialUseCapability = 0x200000000LL;  /* RFC 6154: Sent, Draft etc. folders */
 const eIMAPCapabilityFlag kGmailImapCapability = 0x400000000LL;  /* X-GM-EXT-1 capability extension for gmail */
+const eIMAPCapabilityFlag kHasXOAuth2Capability = 0x800000000LL;  /* AUTH XOAUTH2 extension */
 
 
 // this used to be part of the connection object class - maybe we should move it into 
 // something similar
 typedef enum {
     kEveryThingRFC822,
     kEveryThingRFC822Peek,
     kHeadersRFC822andUid,
--- a/mailnews/imap/src/nsImapProtocol.cpp
+++ b/mailnews/imap/src/nsImapProtocol.cpp
@@ -767,17 +767,17 @@ nsresult nsImapProtocol::SetupWithUrl(ns
     {
       NS_ASSERTION(!m_channelListener, "shouldn't already have a channel listener");
       m_channelListener = new StreamListenerProxy(aRealStreamListener);
     }
 
     server->GetRealHostName(m_realHostName);
     int32_t authMethod;
     (void) server->GetAuthMethod(&authMethod);
-    InitPrefAuthMethods(authMethod);
+    InitPrefAuthMethods(authMethod, server);
     (void) server->GetSocketType(&m_socketType);
     bool shuttingDown;
     (void) imapServer->GetShuttingDown(&shuttingDown);
     if (!shuttingDown)
       (void) imapServer->GetUseIdle(&m_useIdle);
     else
       m_useIdle = false;
     imapServer->GetFetchByChunks(&m_fetchByChunks);
@@ -5496,17 +5496,18 @@ void nsImapProtocol::EscapeUserNamePassw
         {
             resultStr->Append('\\');
         }
         resultStr->Append(strToEscape[i]);
     }
   }
 }
 
-void nsImapProtocol::InitPrefAuthMethods(int32_t authMethodPrefValue)
+void nsImapProtocol::InitPrefAuthMethods(int32_t authMethodPrefValue,
+                                         nsIMsgIncomingServer *aServer)
 {
     // for m_prefAuthMethods, using the same flags as server capablities.
     switch (authMethodPrefValue)
     {
       case nsMsgAuthMethod::none:
         m_prefAuthMethods = kHasAuthNoneCapability;
         break;
       case nsMsgAuthMethod::old:
@@ -5539,71 +5540,85 @@ void nsImapProtocol::InitPrefAuthMethods
         PR_LOG(IMAP, PR_LOG_ERROR,
             ("IMAP: bad pref authMethod = %d\n", authMethodPrefValue));
         // fall to any
       case nsMsgAuthMethod::anything:
         m_prefAuthMethods = kHasAuthOldLoginCapability |
             kHasAuthLoginCapability | kHasAuthPlainCapability |
             kHasCRAMCapability | kHasAuthGssApiCapability |
             kHasAuthNTLMCapability | kHasAuthMSNCapability |
-            kHasAuthExternalCapability;
+            kHasAuthExternalCapability | kHasXOAuth2Capability;
+        break;
+      case nsMsgAuthMethod::OAuth2:
+        m_prefAuthMethods = kHasXOAuth2Capability;
         break;
     }
+
+    if (m_prefAuthMethods & kHasXOAuth2Capability)
+      mOAuth2Support = new mozilla::mailnews::OAuth2ThreadHelper(aServer);
+
+    // Disable OAuth2 support if we don't have the prefs installed.
+    if (m_prefAuthMethods & kHasXOAuth2Capability &&
+        (!mOAuth2Support || !mOAuth2Support->SupportsOAuth2()))
+      m_prefAuthMethods &= ~kHasXOAuth2Capability;
+
     NS_ASSERTION(m_prefAuthMethods != kCapabilityUndefined,
          "IMAP: InitPrefAuthMethods() didn't work");
 }
 
 /**
  * Changes m_currentAuthMethod to pick the best remaining one
  * which is allowed by server and prefs and not marked failed.
  * The order of preference and trying of auth methods is encoded here.
  */
 nsresult nsImapProtocol::ChooseAuthMethod()
 {
   eIMAPCapabilityFlags serverCaps = GetServerStateParser().GetCapabilityFlag();
   eIMAPCapabilityFlags availCaps = serverCaps & m_prefAuthMethods & ~m_failedAuthMethods;
 
-  PR_LOG(IMAP, PR_LOG_DEBUG, ("IMAP auth: server caps 0x%X, pref 0x%X, failed 0x%X, avail caps 0x%X",
+  PR_LOG(IMAP, PR_LOG_DEBUG, ("IMAP auth: server caps 0x%llx, pref 0x%llx, failed 0x%llx, avail caps 0x%llx",
         serverCaps, m_prefAuthMethods, m_failedAuthMethods, availCaps));
-  PR_LOG(IMAP, PR_LOG_DEBUG, ("(GSSAPI = 0x%X, CRAM = 0x%X, NTLM = 0x%X, "
-        "MSN =  0x%X, PLAIN = 0x%X, LOGIN = 0x%X, old-style IMAP login = 0x%X)"
-        "auth external IMAP login = 0x%X",
+  PR_LOG(IMAP, PR_LOG_DEBUG, ("(GSSAPI = 0x%llx, CRAM = 0x%llx, NTLM = 0x%llx, "
+        "MSN = 0x%llx, PLAIN = 0x%llx,\n  LOGIN = 0x%llx, old-style IMAP login = 0x%llx"
+        ", auth external IMAP login = 0x%llx, OAUTH2 = 0x%llx)",
         kHasAuthGssApiCapability, kHasCRAMCapability, kHasAuthNTLMCapability,
         kHasAuthMSNCapability, kHasAuthPlainCapability, kHasAuthLoginCapability,
-        kHasAuthOldLoginCapability, kHasAuthExternalCapability));
+        kHasAuthOldLoginCapability, kHasAuthExternalCapability, kHasXOAuth2Capability));
 
   if (kHasAuthExternalCapability & availCaps)
     m_currentAuthMethod = kHasAuthExternalCapability;
   else if (kHasAuthGssApiCapability & availCaps)
     m_currentAuthMethod = kHasAuthGssApiCapability;
   else if (kHasCRAMCapability & availCaps)
     m_currentAuthMethod = kHasCRAMCapability;
   else if (kHasAuthNTLMCapability & availCaps)
     m_currentAuthMethod = kHasAuthNTLMCapability;
   else if (kHasAuthMSNCapability & availCaps)
     m_currentAuthMethod = kHasAuthMSNCapability;
+  else if (kHasXOAuth2Capability & availCaps)
+    m_currentAuthMethod = kHasXOAuth2Capability;
   else if (kHasAuthPlainCapability & availCaps)
     m_currentAuthMethod = kHasAuthPlainCapability;
   else if (kHasAuthLoginCapability & availCaps)
     m_currentAuthMethod = kHasAuthLoginCapability;
   else if (kHasAuthOldLoginCapability & availCaps)
     m_currentAuthMethod = kHasAuthOldLoginCapability;
   else
   {
     PR_LOG(IMAP, PR_LOG_DEBUG, ("no remaining auth method"));
     m_currentAuthMethod = kCapabilityUndefined;
     return NS_ERROR_FAILURE;
   }
-  PR_LOG(IMAP, PR_LOG_DEBUG, ("trying auth method 0x%X", m_currentAuthMethod));
+  PR_LOG(IMAP, PR_LOG_DEBUG, ("trying auth method 0x%llx", m_currentAuthMethod));
   return NS_OK;
 }
 
 void nsImapProtocol::MarkAuthMethodAsFailed(eIMAPCapabilityFlags failedAuthMethod)
 {
-  PR_LOG(IMAP, PR_LOG_DEBUG, ("marking auth method 0x%X failed", failedAuthMethod));
+  PR_LOG(IMAP, PR_LOG_DEBUG, ("marking auth method 0x%llx failed", failedAuthMethod));
   m_failedAuthMethods |= failedAuthMethod;
 }
 
 /**
  * Start over, trying all auth methods again
  */
 void nsImapProtocol::ResetAuthMethods()
 {
@@ -5615,17 +5630,17 @@ void nsImapProtocol::ResetAuthMethods()
 nsresult nsImapProtocol::AuthLogin(const char *userName, const nsCString &password, eIMAPCapabilityFlag flag)
 {
   ProgressEventFunctionUsingName("imapStatusSendingAuthLogin");
   IncrementCommandTagNumber();
 
   char * currentCommand=nullptr;
   nsresult rv;
 
-  PR_LOG(IMAP, PR_LOG_DEBUG, ("IMAP: trying auth method 0x%X", m_currentAuthMethod));
+  PR_LOG(IMAP, PR_LOG_DEBUG, ("IMAP: trying auth method 0x%llx", m_currentAuthMethod));
 
   if (flag & kHasAuthExternalCapability)
   {
       char *base64UserName = PL_Base64Encode(userName, strlen(userName), nullptr);
       nsAutoCString command (GetServerCommandTag());
       command.Append(" authenticate EXTERNAL " );
       command.Append(base64UserName);
       command.Append(CRLF);
@@ -5829,16 +5844,43 @@ nsresult nsImapProtocol::AuthLogin(const
     nsAutoCString correctedPassword;
     EscapeUserNamePasswordString(password.get(), &correctedPassword);
     command.Append(correctedPassword);
     command.Append("\"" CRLF);
     rv = SendData(command.get(), true /* suppress logging */);
     NS_ENSURE_SUCCESS(rv, rv);
     ParseIMAPandCheckForNewMail();
   }
+  else if (flag & kHasXOAuth2Capability)
+  {
+    PR_LOG(IMAP, PR_LOG_DEBUG, ("XOAUTH2 auth"));
+
+    // Get the XOAuth2 base64 string.
+    NS_ASSERTION(mOAuth2Support,
+      "What are we doing here without OAuth2 helper?");
+    if (!mOAuth2Support)
+      return NS_ERROR_UNEXPECTED;
+    nsAutoCString base64Str;
+    mOAuth2Support->GetXOAuth2String(base64Str);
+    mOAuth2Support = nullptr; // Its purpose has been served.
+    if (base64Str.IsEmpty())
+    {
+      PR_LOG(IMAP, PR_LOG_DEBUG, ("OAuth2 failed"));
+      return NS_ERROR_FAILURE;
+    }
+
+    // Send the data on the network.
+    nsAutoCString command (GetServerCommandTag());
+    command += " AUTHENTICATE XOAUTH2 ";
+    command += base64Str;
+    command += CRLF;
+    rv = SendData(command.get(), true /* suppress logging */);
+    NS_ENSURE_SUCCESS(rv, rv);
+    ParseIMAPandCheckForNewMail();
+  }
   else if (flag & kHasAuthNoneCapability)
   {
     // TODO What to do? "login <username>" like POP?
     return NS_ERROR_NOT_IMPLEMENTED;
   }
   else
   {
     PR_LOG(IMAP, PR_LOG_ERROR, ("flags param has no auth scheme selected"));
@@ -8376,16 +8418,17 @@ bool nsImapProtocol::TryToLogon()
   GetMsgWindow(getter_AddRefs(msgWindow));
 
   // This loops over 1) auth methods (only one per loop) and 2) password tries (with UI)
   while (!loginSucceeded && !skipLoop && !DeathSignalReceived())
   {
       // Get password
       if (m_currentAuthMethod != kHasAuthGssApiCapability && // GSSAPI uses no pw in apps
           m_currentAuthMethod != kHasAuthExternalCapability &&
+          m_currentAuthMethod != kHasXOAuth2Capability &&
           m_currentAuthMethod != kHasAuthNoneCapability)
       {
           rv = GetPassword(password, newPasswordRequested);
           newPasswordRequested = false;
           if (rv == NS_MSG_PASSWORD_PROMPT_CANCELLED || NS_FAILED(rv))
           {
             PR_LOG(IMAP, PR_LOG_ERROR, ("IMAP: password prompt failed or user canceled it"));
             break;
@@ -8412,16 +8455,25 @@ bool nsImapProtocol::TryToLogon()
           if (m_prefAuthMethods == kHasAuthGssApiCapability)
           {
             // GSSAPI failed, and it's the only available method,
             // and it's password-less, so nothing left to do.
             AlertUserEventUsingName("imapAuthGssapiFailed");
             break;
           }
 
+          if (m_prefAuthMethods & kHasXOAuth2Capability)
+          {
+            // OAuth2 failed. We don't have an error message for this, and we
+            // in a string freeze, so use a generic error message. Entering
+            // a password does not help.
+            AlertUserEventUsingName("imapUnknownHostError");
+            break;
+          }
+
           // The reason that we failed might be a wrong password, so
           // ask user what to do
           PR_LOG(IMAP, PR_LOG_WARN, ("IMAP: ask user what to do (after login failed): new passwort, retry, cancel"));
           if (!m_imapServerSink)
             break;
           // if there's no msg window, don't forget the password
           if (!msgWindow)
             break;
--- a/mailnews/imap/src/nsImapProtocol.h
+++ b/mailnews/imap/src/nsImapProtocol.h
@@ -458,17 +458,18 @@ private:
   uint32_t m_currentServerCommandTagNumber;
   void IncrementCommandTagNumber();
   const char *GetServerCommandTag();
 
   void StartTLS();
 
   // login related methods.
   nsresult GetPassword(nsCString &password, bool aNewPasswordRequested);
-  void InitPrefAuthMethods(int32_t authMethodPrefValue);
+  void InitPrefAuthMethods(int32_t authMethodPrefValue,
+                           nsIMsgIncomingServer *aServer);
   nsresult ChooseAuthMethod();
   void MarkAuthMethodAsFailed(eIMAPCapabilityFlags failedAuthMethod);
   void ResetAuthMethods();
 
   // All of these methods actually issue protocol
   void Capability(); // query host for capabilities.
   void ID(); // send RFC 2971 app info to server
   void EnableCondStore(); 
@@ -665,16 +666,18 @@ private:
   nsVoidArray                 m_listedMailboxList;
   nsVoidArray*                m_deletableChildren;
   uint32_t                    m_flagChangeCount;
   PRTime                      m_lastCheckTime;
 
   bool CheckNeeded();
 
   nsString m_emptyMimePartString;
+
+  nsRefPtr<mozilla::mailnews::OAuth2ThreadHelper> mOAuth2Support;
 };
 
 // This small class is a "mock" channel because it is a mockery of the imap channel's implementation...
 // it's a light weight channel that we can return to necko when they ask for a channel on a url before
 // we actually have an imap protocol instance around which can run the url. Please see my comments in
 // nsIImapMockChannel.idl for more details..
 //
 // Threading concern: This class lives entirely in the UI thread.
--- a/mailnews/imap/src/nsImapServerResponseParser.cpp
+++ b/mailnews/imap/src/nsImapServerResponseParser.cpp
@@ -2244,16 +2244,18 @@ void nsImapServerResponseParser::capabil
       else if (token.Equals("AUTH=NTLM", nsCaseInsensitiveCStringComparator()))
         fCapabilityFlag |= kHasAuthNTLMCapability;
       else if (token.Equals("AUTH=GSSAPI", nsCaseInsensitiveCStringComparator()))
         fCapabilityFlag |= kHasAuthGssApiCapability;
       else if (token.Equals("AUTH=MSN", nsCaseInsensitiveCStringComparator()))
         fCapabilityFlag |= kHasAuthMSNCapability;
       else if (token.Equals("AUTH=EXTERNAL", nsCaseInsensitiveCStringComparator()))
         fCapabilityFlag |= kHasAuthExternalCapability;
+      else if (token.Equals("AUTH=XOAUTH2", nsCaseInsensitiveCStringComparator()))
+        fCapabilityFlag |= kHasXOAuth2Capability;
       else if (token.Equals("STARTTLS", nsCaseInsensitiveCStringComparator()))
         fCapabilityFlag |= kHasStartTLSCapability;
       else if (token.Equals("LOGINDISABLED", nsCaseInsensitiveCStringComparator()))
         fCapabilityFlag &= ~kHasAuthOldLoginCapability; // remove flag
       else if (token.Equals("XSENDER", nsCaseInsensitiveCStringComparator()))
         fCapabilityFlag |= kHasXSenderCapability;
       else if (token.Equals("IMAP4", nsCaseInsensitiveCStringComparator()))
         fCapabilityFlag |= kIMAP4Capability;
--- a/mailnews/imap/src/nsSyncRunnableHelpers.cpp
+++ b/mailnews/imap/src/nsSyncRunnableHelpers.cpp
@@ -443,8 +443,151 @@ NS_SYNCRUNNABLEMETHOD3(ImapServerSink, A
 NS_SYNCRUNNABLEATTRIBUTE(ImapServerSink, UserAuthenticated, bool)
 NS_SYNCRUNNABLEMETHOD3(ImapServerSink, SetMailServerUrls, const nsACString &, const nsACString &, const nsACString &)
 NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetArbitraryHeaders, nsACString &)
 NS_SYNCRUNNABLEMETHOD0(ImapServerSink, ForgetPassword)
 NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetShowAttachmentsInline, bool *)
 NS_SYNCRUNNABLEMETHOD3(ImapServerSink, CramMD5Hash, const char *, const char *, char **)
 NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetLoginUsername, nsACString &)
 NS_SYNCRUNNABLEMETHOD1(ImapServerSink, UpdateTrySTARTTLSPref, bool)
+
+namespace mozilla {
+namespace mailnews {
+
+NS_IMPL_ISUPPORTS(OAuth2ThreadHelper, msgIOAuth2ModuleListener)
+
+OAuth2ThreadHelper::OAuth2ThreadHelper(nsIMsgIncomingServer *aServer)
+: mMonitor("OAuth thread lock"),
+  mServer(aServer)
+{
+}
+
+OAuth2ThreadHelper::~OAuth2ThreadHelper()
+{
+  if (mOAuth2Support)
+  {
+    nsCOMPtr<nsIThread> mainThread;
+    NS_GetMainThread(getter_AddRefs(mainThread));
+    NS_ProxyRelease(mainThread, mOAuth2Support);
+  }
+}
+
+bool OAuth2ThreadHelper::SupportsOAuth2()
+{
+  // Acquire a lock early, before reading anything. Guarantees memory visibility
+  // issues.
+  MonitorAutoLock lockGuard(mMonitor);
+
+  // If we don't have a server, we can't init, and therefore, we don't support
+  // OAuth2.
+  if (!mServer)
+    return false;
+
+  // If we have this, then we support OAuth2.
+  if (mOAuth2Support)
+    return true;
+
+  // Initialize. This needs to be done on-main-thread: if we're off that thread,
+  // synchronously dispatch to the main thread.
+  if (NS_IsMainThread())
+  {
+    MonitorAutoUnlock lockGuard(mMonitor);
+    Init();
+  }
+  else
+  {
+    nsCOMPtr<nsIRunnable> runInit =
+      NS_NewRunnableMethod(this, &OAuth2ThreadHelper::Init);
+    NS_DispatchToMainThread(runInit);
+    mMonitor.Wait();
+  }
+
+  // After synchronously initializing, if we didn't get an object, then we don't
+  // support XOAuth2.
+  return mOAuth2Support != nullptr;
+}
+
+void OAuth2ThreadHelper::GetXOAuth2String(nsACString &base64Str)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "This method cannot run on the main thread");
+
+  // Acquire a lock early, before reading anything. Guarantees memory visibility
+  // issues.
+  MonitorAutoLock lockGuard(mMonitor);
+
+  // Umm... what are you trying to do?
+  if (!mOAuth2Support)
+    return;
+
+  nsCOMPtr<nsIRunnable> runInit =
+    NS_NewRunnableMethod(this, &OAuth2ThreadHelper::Connect);
+  NS_DispatchToMainThread(runInit);
+  mMonitor.Wait();
+
+  // Now we either have the string, or we failed (in which case the string is
+  // empty).
+  base64Str = mOAuth2String;
+}
+
+void OAuth2ThreadHelper::Init()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Can't touch JS off-main-thread");
+  MonitorAutoLock lockGuard(mMonitor);
+
+  // Create the OAuth2 helper module and initialize it. If the preferences are
+  // not set up on this server, we don't support OAuth2, and we nullify our
+  // members to indicate this.
+  mOAuth2Support = do_CreateInstance(MSGIOAUTH2MODULE_CONTRACTID);
+  if (mOAuth2Support)
+  {
+    bool supportsOAuth = false;
+    mOAuth2Support->InitFromMail(mServer, &supportsOAuth);
+    if (!supportsOAuth)
+      mOAuth2Support = nullptr;
+  }
+
+  // There is now no longer any need for the server. Kill it now--this helps
+  // prevent us from maintaining a refcount cycle.
+  mServer = nullptr;
+
+  // Notify anyone waiting that we're done.
+  mMonitor.Notify();
+}
+
+void OAuth2ThreadHelper::Connect()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Can't touch JS off-main-thread");
+  MOZ_ASSERT(mOAuth2Support, "Should not be here if no OAuth2 support");
+
+  // OK to delay lock since mOAuth2Support is only written on main thread.
+  nsresult rv = mOAuth2Support->Connect(true, this);
+  // If the method failed, we'll never get a callback, so notify the monitor
+  // immediately so that IMAP can react.
+  if (NS_FAILED(rv))
+  {
+    MonitorAutoLock lockGuard(mMonitor);
+    mMonitor.Notify();
+  }
+}
+
+nsresult OAuth2ThreadHelper::OnSuccess(const nsACString &aAccessToken)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Can't touch JS off-main-thread");
+  MonitorAutoLock lockGuard(mMonitor);
+
+  MOZ_ASSERT(mOAuth2Support, "Should not be here if no OAuth2 support");
+  mOAuth2Support->BuildXOAuth2String(mOAuth2String);
+  mMonitor.Notify();
+  return NS_OK;
+}
+
+nsresult OAuth2ThreadHelper::OnFailure(nsresult aError)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Can't touch JS off-main-thread");
+  MonitorAutoLock lockGuard(mMonitor);
+
+  mOAuth2String.Truncate();
+  mMonitor.Notify();
+  return NS_OK;
+}
+
+} // namespace mailnews
+} // namespace mozilla
--- a/mailnews/imap/src/nsSyncRunnableHelpers.h
+++ b/mailnews/imap/src/nsSyncRunnableHelpers.h
@@ -3,16 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsSyncRunnableHelpers_h
 #define nsSyncRunnableHelpers_h
 
 #include "nsThreadUtils.h"
 #include "nsProxyRelease.h"
 
+#include "mozilla/Monitor.h"
+#include "msgIOAuth2Module.h"
 #include "nsIStreamListener.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIImapMailFolderSink.h"
 #include "nsIImapServerSink.h"
 #include "nsIImapProtocolSink.h"
 #include "nsIImapMessageSink.h"
 
 // The classes in this file proxy method calls to the main thread
@@ -107,9 +109,43 @@ public:
 
 private:
   ~ImapProtocolSinkProxy() {
     nsCOMPtr<nsIThread> thread = do_GetMainThread();
     NS_ProxyRelease(thread, mReceiver);
   }
   nsCOMPtr<nsIImapProtocolSink> mReceiver;
 };
+
+class msgIOAuth2Module;
+class nsIMsgIncomingServer;
+class nsIVariant;
+class nsIWritableVariant;
+
+namespace mozilla {
+namespace mailnews {
+
+class OAuth2ThreadHelper final : public msgIOAuth2ModuleListener
+{
+public:
+  OAuth2ThreadHelper(nsIMsgIncomingServer *aServer);
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_MSGIOAUTH2MODULELISTENER
+
+  bool SupportsOAuth2();
+  void GetXOAuth2String(nsACString &base64Str);
+
+private:
+  ~OAuth2ThreadHelper();
+  void Init();
+  void Connect();
+
+  Monitor mMonitor;
+  nsCOMPtr<msgIOAuth2Module> mOAuth2Support;
+  nsCOMPtr<nsIMsgIncomingServer> mServer;
+  nsCString mOAuth2String;
+};
+
+} // namespace mailnews
+} // namespace mozilla
+
 #endif // nsSyncRunnableHelpers_h