Bug 1538409 - implement oAuth authentication for POP accounts r=benc a=mkmelin
authorMagnus Melin <mkmelin+mozilla@iki.fi>
Wed, 29 Jan 2020 16:25:17 +0200
changeset 37901 f6219b04be6d83cd8bc0bf968bbf619708b6a2e2
parent 37900 943d4e0ca6b6de06aea8a82ed3de457b6b427ce0
child 37902 aec74518a5251400527bb1f4f5dadc30de4ed4b8
push id397
push userclokep@gmail.com
push dateMon, 10 Feb 2020 21:16:13 +0000
reviewersbenc, mkmelin
bugs1538409
Bug 1538409 - implement oAuth authentication for POP accounts r=benc a=mkmelin
mail/components/accountcreation/content/emailWizard.js
mailnews/base/prefs/content/am-server.js
mailnews/base/prefs/content/am-smtp.js
mailnews/base/util/OAuth2Providers.jsm
mailnews/imap/src/nsImapProtocol.cpp
mailnews/local/src/nsPop3Protocol.cpp
mailnews/local/src/nsPop3Protocol.h
--- a/mail/components/accountcreation/content/emailWizard.js
+++ b/mail/components/accountcreation/content/emailWizard.js
@@ -1463,35 +1463,33 @@ EmailConfigWizard.prototype = {
     e("incoming_username").value = config.incoming.username;
     if (config.incoming.port) {
       e("incoming_port").value = config.incoming.port;
     } else {
       this.adjustIncomingPortToSSLAndProtocol(config);
     }
     this.fillPortDropdown(config.incoming.type);
 
-    // If the hostname supports OAuth2 and imap is enabled, enable OAuth2.
+    // If the incoming server hostname supports OAuth2, enable OAuth2 for it.
     let iDetails = OAuth2Providers.getHostnameDetails(config.incoming.hostname);
+    e("in-authMethod-oauth2").hidden = !iDetails;
     if (iDetails) {
       gEmailWizardLogger.info(
         "OAuth2 details for incoming server " +
           config.incoming.hostname +
           " is " +
           iDetails
       );
-    }
-    e("in-authMethod-oauth2").hidden = !(
-      iDetails && e("incoming_protocol").value == 1
-    );
-    if (!e("in-authMethod-oauth2").hidden) {
       config.oauthSettings = {};
       [config.oauthSettings.issuer, config.oauthSettings.scope] = iDetails;
       // oauthsettings are not stored nor changeable in the user interface, so just
       // store them in the base configuration.
       this._currentConfig.oauthSettings = config.oauthSettings;
+    } else {
+      e("incoming_authMethod").value = 0; // Set to autodetect.
     }
 
     // outgoing server
     e("outgoing_hostname").value = config.outgoing.hostname;
     e("outgoing_username").value = config.outgoing.username;
     // While sameInOutUsernames is true we synchronize values of incoming
     // and outgoing username.
     this.sameInOutUsernames = true;
@@ -1506,33 +1504,33 @@ EmailConfigWizard.prototype = {
       0
     );
     if (config.outgoing.port) {
       e("outgoing_port").value = config.outgoing.port;
     } else {
       this.adjustOutgoingPortToSSLAndProtocol(config);
     }
 
-    // If the hostname supports OAuth2 and imap is enabled, enable OAuth2.
+    // If the smtp hostname supports OAuth2, enable OAuth2 for it.
     let oDetails = OAuth2Providers.getHostnameDetails(config.outgoing.hostname);
+    e("out-authMethod-oauth2").hidden = !oDetails;
     if (oDetails) {
       gEmailWizardLogger.info(
         "OAuth2 details for outgoing server " +
           config.outgoing.hostname +
           " is " +
           oDetails
       );
-    }
-    e("out-authMethod-oauth2").hidden = !oDetails;
-    if (!e("out-authMethod-oauth2").hidden) {
       config.oauthSettings = {};
       [config.oauthSettings.issuer, config.oauthSettings.scope] = oDetails;
       // oauthsettings are not stored nor changeable in the user interface, so just
       // store them in the base configuration.
       this._currentConfig.oauthSettings = config.oauthSettings;
+    } else {
+      e("outgoing_authMethod").value = 0; // Set to autodetect.
     }
 
     // populate fields even if existingServerKey, in case user changes back
     if (config.outgoing.existingServerKey) {
       let menulist = e("outgoing_hostname");
       // We can't use menulist.value = config.outgoing.existingServerKey
       // because would overwrite the text field, so have to do it manually:
       let menuitems = menulist.menupopup.children;
--- a/mailnews/base/prefs/content/am-server.js
+++ b/mailnews/base/prefs/content/am-server.js
@@ -92,18 +92,20 @@ function onInit(aPageId, aServerId) {
   onCheckItem("nntp.maxArticles", ["nntp.notifyOn"]);
   setupMailOnServerUI();
   setupFixedUI();
   let serverType = document.getElementById("server.type").getAttribute("value");
   if (serverType == "imap") {
     setupImapDeleteUI(aServerId);
   }
 
-  // TLS Cert (External) and OAuth2 are only supported on IMAP.
-  document.getElementById("authMethod-oauth2").hidden = serverType != "imap";
+  // OAuth2 are only supported on IMAP and POP.
+  document.getElementById("authMethod-oauth2").hidden =
+    serverType != "imap" && serverType != "pop3";
+  // TLS Cert (External) and OAuth2 only supported on IMAP.
   document.getElementById("authMethod-external").hidden = serverType != "imap";
 
   // "STARTTLS, if available" is vulnerable to MITM attacks so we shouldn't
   // allow users to choose it anymore. Hide the option unless the user already
   // has it set.
   hideUnlessSelected(document.getElementById("connectionSecurityType-1"));
 
   // UI for account store type.
--- a/mailnews/base/prefs/content/am-smtp.js
+++ b/mailnews/base/prefs/content/am-smtp.js
@@ -5,16 +5,20 @@
 
 /* import-globals-from amUtils.js */
 
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 var { MailServices } = ChromeUtils.import(
   "resource:///modules/MailServices.jsm"
 );
 
+var { OAuth2Providers } = ChromeUtils.import(
+  "resource:///modules/OAuth2Providers.jsm"
+);
+
 var gSmtpServerListWindow = {
   mBundle: null,
   mServerList: null,
   mAddButton: null,
   mEditButton: null,
   mDeleteButton: null,
   mSetDefaultServerButton: null,
 
@@ -169,16 +173,23 @@ var gSmtpServerListWindow = {
       default:
         // leave empty
         Cu.reportError(
           "Warning: unknown value for smtpserver... authMethod: " +
             aServer.authMethod
         );
     }
     if (authStr) {
+      let details = OAuth2Providers.getHostnameDetails(aServer.hostname);
+      if (!details) {
+        document
+          .getElementById("authMethod-oauth2")
+          .toggleAttribute("disabled", true);
+      }
+
       document.getElementById("authMethodValue").value = this.mBundle.getString(
         authStr
       );
     }
   },
 
   refreshServerList(aServerKeyToSelect, aFocusList) {
     // remove all children
--- a/mailnews/base/util/OAuth2Providers.jsm
+++ b/mailnews/base/util/OAuth2Providers.jsm
@@ -6,29 +6,33 @@
  * Details of supported OAuth2 Providers.
  */
 var EXPORTED_SYMBOLS = ["OAuth2Providers"];
 
 // map of hostnames to [issuer, scope]
 var kHostnames = new Map([
   ["imap.googlemail.com", ["accounts.google.com", "https://mail.google.com/"]],
   ["smtp.googlemail.com", ["accounts.google.com", "https://mail.google.com/"]],
+  ["pop.googlemail.com", ["accounts.google.com", "https://mail.google.com/"]],
   ["imap.gmail.com", ["accounts.google.com", "https://mail.google.com/"]],
   ["smtp.gmail.com", ["accounts.google.com", "https://mail.google.com/"]],
+  ["pop.gmail.com", ["accounts.google.com", "https://mail.google.com/"]],
 
   ["imap.mail.ru", ["o2.mail.ru", "mail.imap"]],
   ["smtp.mail.ru", ["o2.mail.ru", "mail.imap"]],
 
   ["imap.yandex.com", ["oauth.yandex.com", "mail:imap_full"]],
   ["smtp.yandex.com", ["oauth.yandex.com", "mail:smtp"]],
 
   ["imap.mail.yahoo.com", ["login.yahoo.com", "mail-w"]],
+  ["pop.mail.yahoo.com", ["login.yahoo.com", "mail-w"]],
   ["smtp.mail.yahoo.com", ["login.yahoo.com", "mail-w"]],
 
   ["imap.aol.com", ["login.aol.com", "mail-w"]],
+  ["pop.aol.com", ["login.aol.com", "mail-w"]],
   ["smtp.aol.com", ["login.aol.com", "mail-w"]],
 ]);
 
 // map of issuers to appKey, appSecret, authURI, tokenURI
 
 // 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
--- a/mailnews/imap/src/nsImapProtocol.cpp
+++ b/mailnews/imap/src/nsImapProtocol.cpp
@@ -5434,45 +5434,44 @@ void nsImapProtocol::InitPrefAuthMethods
       break;
     case nsMsgAuthMethod::External:
       m_prefAuthMethods = kHasAuthExternalCapability;
       break;
     case nsMsgAuthMethod::secure:
       m_prefAuthMethods = kHasCRAMCapability | kHasAuthGssApiCapability |
                           kHasAuthNTLMCapability | kHasAuthMSNCapability;
       break;
+    case nsMsgAuthMethod::OAuth2:
+      m_prefAuthMethods = kHasXOAuth2Capability;
+      break;
     default:
       NS_ASSERTION(false, "IMAP: authMethod pref invalid");
-      // TODO log to error console
       MOZ_LOG(IMAP, LogLevel::Error,
               ("IMAP: bad pref authMethod = %d", authMethodPrefValue));
       // fall to any
       [[fallthrough]];
     case nsMsgAuthMethod::anything:
       m_prefAuthMethods = kHasAuthOldLoginCapability | kHasAuthLoginCapability |
                           kHasAuthPlainCapability | kHasCRAMCapability |
                           kHasAuthGssApiCapability | kHasAuthNTLMCapability |
                           kHasAuthMSNCapability | kHasAuthExternalCapability |
                           kHasXOAuth2Capability;
       break;
-    case nsMsgAuthMethod::OAuth2:
-      m_prefAuthMethods = kHasXOAuth2Capability;
-      break;
-  }
-
-  if (m_prefAuthMethods & kHasXOAuth2Capability)
+  }
+
+  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");
+    if (!mOAuth2Support || !mOAuth2Support->SupportsOAuth2()) {
+      // Disable OAuth2 support if we don't have the prefs installed.
+      m_prefAuthMethods &= ~kHasXOAuth2Capability;
+      mOAuth2Support = nullptr;
+      MOZ_LOG(IMAP, LogLevel::Warning,
+              ("IMAP: no OAuth2 support for this server."));
+    }
+  }
 }
 
 /**
  * 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() {
@@ -5510,29 +5509,29 @@ nsresult nsImapProtocol::ChooseAuthMetho
     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 {
-    MOZ_LOG(IMAP, LogLevel::Debug, ("no remaining auth method"));
+    MOZ_LOG(IMAP, LogLevel::Debug, ("No remaining auth method"));
     m_currentAuthMethod = kCapabilityUndefined;
     return NS_ERROR_FAILURE;
   }
   MOZ_LOG(IMAP, LogLevel::Debug,
-          ("trying auth method 0x%" PRIx64, m_currentAuthMethod));
+          ("Trying auth method 0x%" PRIx64, m_currentAuthMethod));
   return NS_OK;
 }
 
 void nsImapProtocol::MarkAuthMethodAsFailed(
     eIMAPCapabilityFlags failedAuthMethod) {
   MOZ_LOG(IMAP, LogLevel::Debug,
-          ("marking auth method 0x%" PRIx64 " failed", failedAuthMethod));
+          ("Marking auth method 0x%" PRIx64 " failed", failedAuthMethod));
   m_failedAuthMethods |= failedAuthMethod;
 }
 
 /**
  * Start over, trying all auth methods again
  */
 void nsImapProtocol::ResetAuthMethods() {
   MOZ_LOG(IMAP, LogLevel::Debug, ("resetting (failed) auth methods"));
@@ -8084,17 +8083,17 @@ nsImapProtocol::OnPromptCanceled() {
   // A prompt was cancelled, so notify the imap thread.
   m_passwordStatus = NS_MSG_PASSWORD_PROMPT_CANCELLED;
   ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);
   passwordMon.Notify();
   return NS_OK;
 }
 
 bool nsImapProtocol::TryToLogon() {
-  MOZ_LOG(IMAP, LogLevel::Debug, ("try to log in"));
+  MOZ_LOG(IMAP, LogLevel::Debug, ("Try to log in"));
   NS_ENSURE_TRUE(m_imapServerSink, false);
   bool loginSucceeded = false;
   bool skipLoop = false;
   nsAutoString password;
   nsAutoCString userName;
 
   nsresult rv = ChooseAuthMethod();
   if (NS_FAILED(rv))  // all methods failed
--- a/mailnews/local/src/nsPop3Protocol.cpp
+++ b/mailnews/local/src/nsPop3Protocol.cpp
@@ -371,27 +371,21 @@ nsresult nsPop3Protocol::MarkMsgForHost(
     MarkMsgInHashTable(uidlHost->hash, UIDLArray[i], &changed);
   }
 
   if (changed) net_pop3_write_state(uidlHost, mailDirectory);
   net_pop3_free_state(uidlHost);
   return NS_OK;
 }
 
-NS_IMPL_ADDREF_INHERITED(nsPop3Protocol, nsMsgProtocol)
-NS_IMPL_RELEASE_INHERITED(nsPop3Protocol, nsMsgProtocol)
-
-NS_INTERFACE_MAP_BEGIN(nsPop3Protocol)
-  NS_INTERFACE_MAP_ENTRY(nsIPop3Protocol)
-  NS_INTERFACE_MAP_ENTRY(nsIMsgAsyncPromptListener)
-  NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
-NS_INTERFACE_MAP_END_INHERITING(nsMsgProtocol)
-
 // nsPop3Protocol class implementation
 
+NS_IMPL_ISUPPORTS_INHERITED(nsPop3Protocol, nsMsgProtocol,
+                            msgIOAuth2ModuleListener, nsIProtocolProxyCallback)
+
 nsPop3Protocol::nsPop3Protocol(nsIURI *aURL)
     : nsMsgProtocol(aURL),
       m_bytesInMsgReceived(0),
       m_totalFolderSize(0),
       m_totalDownloadSize(0),
       m_totalBytesReceived(0),
       m_pop3ConData(nullptr) {}
 
@@ -446,28 +440,37 @@ nsPop3Protocol::OnProxyAvailable(nsICanc
     Cancel(rv);
   }
 
   return rv;
 }
 
 nsresult nsPop3Protocol::InitializeInternal(nsIProxyInfo *aProxyInfo) {
   nsresult rv;
-
   m_proxyRequest = nullptr;
 
   NS_ENSURE_TRUE(m_url, NS_ERROR_NOT_INITIALIZED);
 
   // extract out message feedback if there is any.
   nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
   if (mailnewsUrl) {
     nsCOMPtr<nsIMsgIncomingServer> server;
     mailnewsUrl->GetServer(getter_AddRefs(server));
     NS_ENSURE_TRUE(server, NS_MSG_INVALID_OR_MISSING_SERVER);
 
+    // Query for OAuth2 support. If the POP 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->InitFromMail(server, &supportsOAuth);
+      if (!supportsOAuth) mOAuth2Support = nullptr;
+    }
+
     rv = server->GetSocketType(&m_socketType);
     NS_ENSURE_SUCCESS(rv, rv);
 
     int32_t authMethod = 0;
     rv = server->GetAuthMethod(&authMethod);
     NS_ENSURE_SUCCESS(rv, rv);
     InitPrefAuthMethods(authMethod);
 
@@ -1265,20 +1268,19 @@ int32_t nsPop3Protocol::Pop3SendData(con
 
   m_pop3ConData->next_state = POP3_ERROR_DONE;
   MOZ_LOG(POP3LOGMODULE, LogLevel::Info,
           (POP3LOG("Pop3SendData failed: %" PRIx32),
            static_cast<uint32_t>(result)));
   return -1;
 }
 
-/*
+/**
  * POP3 AUTH extension
  */
-
 int32_t nsPop3Protocol::SendAuth() {
   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("SendAuth()")));
 
   if (!m_pop3ConData->command_succeeded) return Error("pop3ServerError");
 
   nsAutoCString command("AUTH" CRLF);
 
   m_pop3ConData->next_state_after_response = POP3_AUTH_RESPONSE;
@@ -1332,16 +1334,18 @@ int32_t nsPop3Protocol::AuthResponse(nsI
   else if (!PL_strcasecmp(line, "MSN"))
     SetCapFlag(POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN);
   else if (!PL_strcasecmp(line, "GSSAPI"))
     SetCapFlag(POP3_HAS_AUTH_GSSAPI);
   else if (!PL_strcasecmp(line, "PLAIN"))
     SetCapFlag(POP3_HAS_AUTH_PLAIN);
   else if (!PL_strcasecmp(line, "LOGIN"))
     SetCapFlag(POP3_HAS_AUTH_LOGIN);
+  else if (!PL_strcasecmp(line, "XOAUTH2"))
+    SetCapFlag(POP3_HAS_AUTH_XOAUTH2);
 
   PR_Free(line);
   return 0;
 }
 
 /*
  * POP3 CAPA extension, see RFC 2449, chapter 5
  */
@@ -1385,34 +1389,30 @@ int32_t nsPop3Protocol::CapaResponse(nsI
 
   if (!PL_strcmp(line, ".")) {
     // now that we've read all the CAPA responses, go for it
     m_pop3ConData->next_state = POP3_PROCESS_AUTH;
     m_pop3ConData->pause_for_read = false; /* don't pause */
   } else if (!PL_strcasecmp(line, "XSENDER")) {
     SetCapFlag(POP3_HAS_XSENDER);
     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
-  } else
-      // see RFC 2449, chapter 6.4
-      if (!PL_strcasecmp(line, "RESP-CODES")) {
+  } else if (!PL_strcasecmp(line, "RESP-CODES")) {
+    // see RFC 2449, chapter 6.4
     SetCapFlag(POP3_HAS_RESP_CODES);
     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
-  } else
-      // see RFC 3206, chapter 6
-      if (!PL_strcasecmp(line, "AUTH-RESP-CODE")) {
+  } else if (!PL_strcasecmp(line, "AUTH-RESP-CODE")) {
+    // see RFC 3206, chapter 6
     SetCapFlag(POP3_HAS_AUTH_RESP_CODE);
     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
-  } else
-      // see RFC 2595, chapter 4
-      if (!PL_strcasecmp(line, "STLS")) {
+  } else if (!PL_strcasecmp(line, "STLS")) {
+    // see RFC 2595, chapter 4
     SetCapFlag(POP3_HAS_STLS);
     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
-  } else
-      // see RFC 2449, chapter 6.3
-      if (!PL_strncasecmp(line, "SASL", 4) && strlen(line) > 6) {
+  } else if (!PL_strncasecmp(line, "SASL", 4) && strlen(line) > 6) {
+    // see RFC 2449, chapter 6.3
     nsAutoCString responseLine;
     responseLine.Assign(line + 5);
 
     if (responseLine.Find("PLAIN", /* ignoreCase = */ true) >= 0)
       SetCapFlag(POP3_HAS_AUTH_PLAIN);
 
     if (responseLine.Find("LOGIN", /* ignoreCase = */ true) >= 0)
       SetCapFlag(POP3_HAS_AUTH_LOGIN);
@@ -1424,16 +1424,19 @@ int32_t nsPop3Protocol::CapaResponse(nsI
       SetCapFlag(POP3_HAS_AUTH_CRAM_MD5);
 
     if (responseLine.Find("NTLM", /* ignoreCase = */ true) >= 0)
       SetCapFlag(POP3_HAS_AUTH_NTLM);
 
     if (responseLine.Find("MSN", /* ignoreCase = */ true) >= 0)
       SetCapFlag(POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN);
 
+    if (responseLine.Find("XOAUTH2", /* ignoreCase = */ true) >= 0)
+      SetCapFlag(POP3_HAS_AUTH_XOAUTH2);
+
     m_pop3Server->SetPop3CapabilityFlags(m_pop3ConData->capability_flags);
   }
 
   PR_Free(line);
   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
           (POP3LOG("Capability entry processed")));
   return 0;
 }
@@ -1497,60 +1500,81 @@ void nsPop3Protocol::InitPrefAuthMethods
       m_prefAuthMethods = POP3_HAS_AUTH_CRAM_MD5 | POP3_HAS_AUTH_APOP;
       break;
     case nsMsgAuthMethod::NTLM:
       m_prefAuthMethods = POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN;
       break;
     case nsMsgAuthMethod::GSSAPI:
       m_prefAuthMethods = POP3_HAS_AUTH_GSSAPI;
       break;
+    case nsMsgAuthMethod::OAuth2:
+      m_prefAuthMethods = POP3_HAS_AUTH_XOAUTH2;
+      break;
     case nsMsgAuthMethod::secure:
       m_prefAuthMethods = POP3_HAS_AUTH_APOP | POP3_HAS_AUTH_CRAM_MD5 |
                           POP3_HAS_AUTH_GSSAPI | POP3_HAS_AUTH_NTLM |
                           POP3_HAS_AUTH_MSN;
       break;
     default:
       NS_ASSERTION(false, "POP: authMethod pref invalid");
       // TODO log to error console
       MOZ_LOG(
           POP3LOGMODULE, LogLevel::Error,
           (POP3LOG("POP: bad pref authMethod = %d\n"), authMethodPrefValue));
       // fall to any
       [[fallthrough]];
     case nsMsgAuthMethod::anything:
-      m_prefAuthMethods = POP3_HAS_AUTH_USER | POP3_HAS_AUTH_LOGIN |
-                          POP3_HAS_AUTH_PLAIN | POP3_HAS_AUTH_CRAM_MD5 |
-                          POP3_HAS_AUTH_APOP | POP3_HAS_AUTH_GSSAPI |
-                          POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN;
+      m_prefAuthMethods =
+          POP3_HAS_AUTH_USER | POP3_HAS_AUTH_LOGIN | POP3_HAS_AUTH_PLAIN |
+          POP3_HAS_AUTH_CRAM_MD5 | POP3_HAS_AUTH_APOP | POP3_HAS_AUTH_GSSAPI |
+          POP3_HAS_AUTH_NTLM | POP3_HAS_AUTH_MSN | POP3_HAS_AUTH_XOAUTH2;
       // TODO needed?
       break;
   }
+
+  // Only enable OAuth2 support if we can do the lookup.
+  if ((m_prefAuthMethods & POP3_HAS_AUTH_XOAUTH2) && !mOAuth2Support) {
+    m_prefAuthMethods &= ~POP3_HAS_AUTH_XOAUTH2;  // disable it
+  }
+
   NS_ASSERTION(m_prefAuthMethods != POP3_AUTH_MECH_UNDEFINED,
                "POP: InitPrefAuthMethods() didn't work");
 }
 
 /**
  * Changes m_currentAuthMethod to pick the 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.
  */
 nsresult nsPop3Protocol::ChooseAuthMethod() {
   int32_t availCaps = GetCapFlags() & m_prefAuthMethods & ~m_failedAuthMethods;
 
   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
           (POP3LOG("POP auth: server caps 0x%X, pref 0x%X, failed 0x%X, avail "
                    "caps 0x%X"),
            GetCapFlags(), m_prefAuthMethods, m_failedAuthMethods, availCaps));
+  MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
+          (POP3LOG("(GSSAPI = 0x%X, CRAM = 0x%X, APOP = 0x%X, NTLM = 0x%X, "
+                   "MSN = 0x%X, PLAIN = 0x%X, LOGIN = 0x%X, USER/PASS = 0x%X, "
+                   "XOAUTH2 = 0x%X)"),
+           POP3_HAS_AUTH_GSSAPI, POP3_HAS_AUTH_CRAM_MD5, POP3_HAS_AUTH_APOP,
+           POP3_HAS_AUTH_NTLM, POP3_HAS_AUTH_MSN, POP3_HAS_AUTH_PLAIN,
+           POP3_HAS_AUTH_LOGIN, POP3_HAS_AUTH_USER, POP3_HAS_AUTH_XOAUTH2));
+
   MOZ_LOG(
       POP3LOGMODULE, LogLevel::Debug,
-      (POP3LOG("(GSSAPI = 0x%X, CRAM = 0x%X, APOP = 0x%X, NTLM = 0x%X, "
-               "MSN =  0x%X, PLAIN = 0x%X, LOGIN = 0x%X, USER/PASS = 0x%X)"),
-       POP3_HAS_AUTH_GSSAPI, POP3_HAS_AUTH_CRAM_MD5, POP3_HAS_AUTH_APOP,
-       POP3_HAS_AUTH_NTLM, POP3_HAS_AUTH_MSN, POP3_HAS_AUTH_PLAIN,
-       POP3_HAS_AUTH_LOGIN, POP3_HAS_AUTH_USER));
+      (POP3LOG("(Ehabled - GSSAPI=%d, CRAM=%d, APOP=%d, NTLM=%d, "
+               "MSN=%d, PLAIN=%d, LOGIN=%d, USER/PASS=%d, "
+               "XOAUTH2=%d)"),
+       !!(POP3_HAS_AUTH_GSSAPI & availCaps),
+       !!(POP3_HAS_AUTH_CRAM_MD5 & availCaps),
+       !!(POP3_HAS_AUTH_APOP & availCaps), !!(POP3_HAS_AUTH_NTLM & availCaps),
+       !!(POP3_HAS_AUTH_MSN & availCaps), !!(POP3_HAS_AUTH_PLAIN & availCaps),
+       !!(POP3_HAS_AUTH_LOGIN & availCaps), !!(POP3_HAS_AUTH_USER & availCaps),
+       !!(POP3_HAS_AUTH_XOAUTH2 & availCaps)));
 
   if (POP3_HAS_AUTH_GSSAPI & availCaps)
     m_currentAuthMethod = POP3_HAS_AUTH_GSSAPI;
   else if (POP3_HAS_AUTH_CRAM_MD5 & availCaps)
     m_currentAuthMethod = POP3_HAS_AUTH_CRAM_MD5;
   else if (POP3_HAS_AUTH_APOP & availCaps)
     m_currentAuthMethod = POP3_HAS_AUTH_APOP;
   else if (POP3_HAS_AUTH_NTLM & availCaps)
@@ -1558,40 +1582,43 @@ nsresult nsPop3Protocol::ChooseAuthMetho
   else if (POP3_HAS_AUTH_MSN & availCaps)
     m_currentAuthMethod = POP3_HAS_AUTH_MSN;
   else if (POP3_HAS_AUTH_PLAIN & availCaps)
     m_currentAuthMethod = POP3_HAS_AUTH_PLAIN;
   else if (POP3_HAS_AUTH_LOGIN & availCaps)
     m_currentAuthMethod = POP3_HAS_AUTH_LOGIN;
   else if (POP3_HAS_AUTH_USER & availCaps)
     m_currentAuthMethod = POP3_HAS_AUTH_USER;
+  else if (POP3_HAS_AUTH_XOAUTH2 & availCaps)
+    m_currentAuthMethod = POP3_HAS_AUTH_XOAUTH2;
+
   else {
     // there are no matching login schemes at all, per server and prefs
     m_currentAuthMethod = POP3_AUTH_MECH_UNDEFINED;
     MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
-            (POP3LOG("no auth method remaining")));
+            (POP3LOG("No auth method remaining")));
     return NS_ERROR_FAILURE;
   }
   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
-          (POP3LOG("trying auth method 0x%X"), m_currentAuthMethod));
+          (POP3LOG("Rrying auth method 0x%X"), m_currentAuthMethod));
   return NS_OK;
 }
 
 void nsPop3Protocol::MarkAuthMethodAsFailed(int32_t failedAuthMethod) {
   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
-          (POP3LOG("marking auth method 0x%X failed"), failedAuthMethod));
+          (POP3LOG("Marking auth method 0x%X failed"), failedAuthMethod));
   m_failedAuthMethods |= failedAuthMethod;
 }
 
 /**
  * Start over, trying all auth methods again
  */
 void nsPop3Protocol::ResetAuthMethods() {
   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug,
-          (POP3LOG("resetting (failed) auth methods")));
+          (POP3LOG("Resetting (failed) auth methods")));
   m_currentAuthMethod = POP3_AUTH_MECH_UNDEFINED;
   m_failedAuthMethods = 0;
 }
 
 /**
  * state POP3_PROCESS_AUTH
  * Called when we should try to authenticate to the server.
  * Also called when one auth method fails and we want to try and start
@@ -1680,39 +1707,87 @@ int32_t nsPop3Protocol::ProcessAuth() {
       MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP NTLM")));
       m_pop3ConData->next_state = POP3_AUTH_NTLM;
       break;
     case POP3_HAS_AUTH_NONE:
       MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP no auth")));
       m_pop3ConData->command_succeeded = true;
       m_pop3ConData->next_state = POP3_NEXT_AUTH_STEP;
       break;
+    case POP3_HAS_AUTH_XOAUTH2:
+      MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("POP XOAUTH2")));
+      m_pop3ConData->command_succeeded = true;
+      m_pop3ConData->next_state = POP3_AUTH_OAUTH2_RESPONSE;
+      break;
     default:
       MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
               (POP3LOG("POP: m_currentAuthMethod has unknown value")));
       return Error("pop3AuthMechNotSupported");
   }
 
   m_pop3ConData->pause_for_read = false;
-
+  return 0;
+}
+
+int32_t nsPop3Protocol::AuthOAuth2Response() {
+  if (!mOAuth2Support) {
+    return Error("pop3AuthMechNotSupported");
+  }
+  nsresult rv = mOAuth2Support->Connect(true, this);
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
+            (POP3LOG("OAuth2 authorizattion failed")));
+    return -1;
+  }
+  m_pop3ConData->pause_for_read = true;
   return 0;
 }
 
+/** msgIOAuth2ModuleListener implementation */
+nsresult nsPop3Protocol::OnSuccess(const nsACString &aOAuth2String) {
+  MOZ_ASSERT(mOAuth2Support, "Can't do anything without OAuth2 support");
+  // Send the AUTH XOAUTH2 command, and then siphon us back to the regular
+  // authentication login stream.
+  nsAutoCString cmd;
+  cmd.AppendLiteral("AUTH XOAUTH2 ");
+  cmd += aOAuth2String;
+  cmd += CRLF;
+  m_pop3ConData->next_state = POP3_WAIT_FOR_RESPONSE;
+  m_pop3ConData->next_state_after_response = POP3_NEXT_AUTH_STEP;
+  m_pop3ConData->pause_for_read = true;
+  m_password_already_sent = true;
+  if (Pop3SendData(cmd.get(), true)) {
+    MOZ_LOG(POP3LOGMODULE, LogLevel::Error,
+            (POP3LOG("POP: XOAUTH2 authentication failed")));
+    m_pop3ConData->next_state = POP3_ERROR_DONE;
+  }
+  ProcessProtocolState(nullptr, nullptr, 0, 0);
+  return NS_OK;
+}
+
+/** msgIOAuth2ModuleListener implementation */
+nsresult nsPop3Protocol::OnFailure(nsresult aError) {
+  MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
+          ("OAuth2 login error %08x", (uint32_t)aError));
+  m_pop3ConData->next_state = POP3_ERROR_DONE;
+  return ProcessProtocolState(nullptr, nullptr, 0, 0);
+}
+
 /**
  * state POP3_NEXT_AUTH_STEP
  * This is called when we finished one auth step (e.g. sending username
  * or password are separate steps, similarly for AUTH LOGIN, NTLM etc.)
  * and want to proceed to the next one.
  */
 int32_t nsPop3Protocol::NextAuthStep() {
   MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("NextAuthStep()")));
   if (m_pop3ConData->command_succeeded) {
     if (m_password_already_sent ||  // (also true for GSSAPI)
         m_currentAuthMethod == POP3_HAS_AUTH_NONE) {
-      MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("login succeeded")));
+      MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("Login succeeded")));
       m_nsIPop3Sink->SetUserAuthenticated(true);
       ClearFlag(POP3_PASSWORD_FAILED);
       if (m_pop3ConData->verify_logon)
         m_pop3ConData->next_state = POP3_SEND_QUIT;
       else
         m_pop3ConData->next_state =
             (m_pop3ConData->get_url) ? POP3_SEND_GURL : POP3_SEND_STAT;
     } else
@@ -3516,16 +3591,20 @@ nsresult nsPop3Protocol::ProcessProtocol
         UpdateStatus("hostContact");
         status = AuthGSSAPIResponse(true);
         break;
 
       case POP3_AUTH_GSSAPI_STEP:
         status = AuthGSSAPIResponse(false);
         break;
 
+      case POP3_AUTH_OAUTH2_RESPONSE:
+        status = AuthOAuth2Response();
+        break;
+
       case POP3_SEND_USERNAME:
         if (NS_FAILED(
                 StartGetAsyncPassword(POP3_OBTAIN_PASSWORD_BEFORE_USERNAME)))
           status = -1;
         break;
 
       case POP3_OBTAIN_PASSWORD_BEFORE_USERNAME:
         status = -1;
--- a/mailnews/local/src/nsPop3Protocol.h
+++ b/mailnews/local/src/nsPop3Protocol.h
@@ -12,16 +12,17 @@
 #include "nsMsgLineBuffer.h"
 #include "nsMsgProtocol.h"
 #include "nsIPop3Protocol.h"
 #include "MailNewsTypes.h"
 #include "nsIStringBundle.h"
 #include "nsIMsgFolder.h"  // TO include biffState enum. Change to bool later...
 #include "nsIMsgAsyncPrompter.h"
 #include "nsIProtocolProxyCallback.h"
+#include "msgIOAuth2Module.h"
 
 #include "prerror.h"
 #include "plhash.h"
 #include "nsCOMPtr.h"
 
 /* A more guaranteed way of making sure that we never get duplicate messages
    is to always get each message's UIDL (if the server supports it)
    and use these for storing up deletes which were not committed on the
@@ -45,21 +46,16 @@
 #define MK_OUT_OF_MEMORY -207
 #define MK_POP3_PASSWORD_UNDEFINED -313
 #define XP_NO_ANSWER 14401
 #define XP_THE_PREVIOUSLY_ENTERED_PASSWORD_IS_INVALID_ETC 14405
 #define XP_PASSWORD_FOR_POP3_USER 14590
 
 #define OUTPUT_BUFFER_SIZE 8192  // maximum size of command string
 
-/* structure to hold data pertaining to the active state of
- * a transfer in progress.
- *
- */
-
 enum Pop3CapabilityEnum {
   POP3_CAPABILITY_UNDEFINED = 0x00000000,
   POP3_HAS_XSENDER = 0x00000001,
   POP3_GURL_UNDEFINED = 0x00000002,
   POP3_HAS_GURL = 0x00000004,
   POP3_UIDL_UNDEFINED = 0x00000008,
   POP3_HAS_UIDL = 0x00000010,
   POP3_XTND_XLST_UNDEFINED = 0x00000020,
@@ -72,24 +68,29 @@ enum Pop3CapabilityEnum {
   POP3_HAS_AUTH_PLAIN = 0x00001000,
   POP3_HAS_AUTH_CRAM_MD5 = 0x00002000,
   POP3_HAS_AUTH_APOP = 0x00004000,
   POP3_HAS_AUTH_NTLM = 0x00008000,
   POP3_HAS_AUTH_MSN = 0x00010000,
   POP3_HAS_RESP_CODES = 0x00020000,
   POP3_HAS_AUTH_RESP_CODE = 0x00040000,
   POP3_HAS_STLS = 0x00080000,
-  POP3_HAS_AUTH_GSSAPI = 0x00100000
+  POP3_HAS_AUTH_GSSAPI = 0x00100000,
+  POP3_HAS_AUTH_XOAUTH2 = 0x00200000
 };
 
 // TODO use value > 0?
 #define POP3_HAS_AUTH_NONE 0
 #define POP3_HAS_AUTH_ANY 0x00001C00
 #define POP3_HAS_AUTH_ANY_SEC 0x0011E000
 
+/**
+ * Structure to hold data pertaining to the active state of a transfer in
+ * progress.
+ */
 enum Pop3StatesEnum {
   POP3_READ_PASSWORD,                          // 0
   POP3_START_CONNECT,                          // 1
   POP3_FINISH_CONNECT,                         // 2
   POP3_WAIT_FOR_RESPONSE,                      // 3
   POP3_WAIT_FOR_START_OF_CONNECTION_RESPONSE,  // 4
   POP3_SEND_USERNAME,                          // 5
   POP3_SEND_PASSWORD,                          // 6
@@ -137,17 +138,20 @@ enum Pop3StatesEnum {
    * The *PREOBTAIN* states are used for where we try and get the password
    * before we've initiated a connection to the server.
    */
   POP3_OBTAIN_PASSWORD_EARLY,                   // 45
   POP3_FINISH_OBTAIN_PASSWORD_EARLY,            // 46
   POP3_OBTAIN_PASSWORD_BEFORE_USERNAME,         // 47
   POP3_FINISH_OBTAIN_PASSWORD_BEFORE_USERNAME,  // 48
   POP3_OBTAIN_PASSWORD_BEFORE_PASSWORD,         // 49
-  POP3_FINISH_OBTAIN_PASSWORD_BEFORE_PASSWORD   // 50
+  POP3_FINISH_OBTAIN_PASSWORD_BEFORE_PASSWORD,  // 50
+
+  POP3_SUSPENDED,             // 51
+  POP3_AUTH_OAUTH2_RESPONSE,  // 52
 };
 
 #define KEEP 'k'        /* If we want to keep this item on server. */
 #define DELETE_CHAR 'd' /* If we want to delete this item. */
 #define TOO_BIG 'b'     /* item left on server because it was too big */
 #define FETCH_BODY 'f'  /* Fetch full body of a partial msg */
 
 typedef struct Pop3UidlEntry { /* information about this message */
@@ -234,21 +238,23 @@ typedef struct _Pop3ConData {
 #define POP3_PASSWORD_FAILED 0x00000002
 #define POP3_STOPLOGIN 0x00000004 /* error logging in, so stop here */
 #define POP3_AUTH_FAILURE \
   0x00000008 /* extended code said authentication failed */
 
 class nsPop3Protocol : public nsMsgProtocol,
                        public nsIPop3Protocol,
                        public nsIMsgAsyncPromptListener,
+                       public msgIOAuth2ModuleListener,
                        public nsIProtocolProxyCallback {
  public:
   explicit nsPop3Protocol(nsIURI* aURL);
 
   NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_MSGIOAUTH2MODULELISTENER
   NS_DECL_NSIPOP3PROTOCOL
   NS_DECL_NSIMSGASYNCPROMPTLISTENER
   NS_DECL_NSIPROTOCOLPROXYCALLBACK
 
   nsresult Initialize(nsIURI* aURL);
   nsresult InitializeInternal(nsIProxyInfo* proxyInfo);
   virtual nsresult LoadUrl(nsIURI* aURL,
                            nsISupports* aConsumer = nullptr) override;
@@ -354,16 +360,17 @@ class nsPop3Protocol : public nsMsgProto
   int32_t AuthResponse(nsIInputStream* inputStream, uint32_t length);
   int32_t SendCapa();
   int32_t CapaResponse(nsIInputStream* inputStream, uint32_t length);
   int32_t SendTLSResponse();
   int32_t ProcessAuth();
   int32_t NextAuthStep();
   int32_t AuthLogin();
   int32_t AuthLoginResponse();
+  int32_t AuthOAuth2Response();
   int32_t AuthNtlm();
   int32_t AuthNtlmResponse();
   int32_t AuthGSSAPI();
   int32_t AuthGSSAPIResponse(bool first);
   int32_t SendUsername();
   int32_t SendPassword();
   int32_t SendStatOrGurl(bool sendStat);
   int32_t SendStat();
@@ -386,11 +393,15 @@ class nsPop3Protocol : public nsMsgProto
   int32_t RetrResponse(nsIInputStream* inputStream, uint32_t length);
   int32_t TopResponse(nsIInputStream* inputStream, uint32_t length);
   int32_t SendDele();
   int32_t DeleResponse();
   int32_t CommitState(bool remove_last_entry);
 
   Pop3StatesEnum GetNextPasswordObtainState();
   nsresult RerunUrl();
+
+  // The support module for OAuth2 logon, only present if OAuth2 is enabled
+  // and working.
+  nsCOMPtr<msgIOAuth2Module> mOAuth2Support;
 };
 
 #endif /* nsPop3Protocol_h__ */