Bug 1787766 - Support APOP auth in Pop3Client.jsm. r=mkmelin a=rjl
authorPing Chen <remotenonsense@gmail.com>
Tue, 06 Sep 2022 14:43:42 -0400
changeset 47282 b0ec79ca5fb15ed8c1bfbba53f92124834647bf6
parent 47281 b95e38d7bcc8a55104d8954a4fae1515582ed369
child 47283 26aada799a2853f38ce94fda9d544b25054b1bb3
push id47
push userthunderbird@calypsoblue.org
push dateTue, 06 Sep 2022 19:31:53 +0000
treeherdercomm-esr102@26aada799a28 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmkmelin, rjl
bugs1787766
Bug 1787766 - Support APOP auth in Pop3Client.jsm. r=mkmelin a=rjl Differential Revision: https://phabricator.services.mozilla.com/D155954
mailnews/local/src/Pop3Client.jsm
--- a/mailnews/local/src/Pop3Client.jsm
+++ b/mailnews/local/src/Pop3Client.jsm
@@ -5,16 +5,17 @@
 const EXPORTED_SYMBOLS = ["Pop3Client"];
 
 var { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
 var { AppConstants } = ChromeUtils.import(
   "resource://gre/modules/AppConstants.jsm"
 );
 var { CommonUtils } = ChromeUtils.import("resource://services-common/utils.js");
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { CryptoUtils } = ChromeUtils.import("resource://services-crypto/utils.js");
 var { LineReader } = ChromeUtils.import("resource:///modules/LineReader.jsm");
 var { MailServices } = ChromeUtils.import(
   "resource:///modules/MailServices.jsm"
 );
 var { MailCryptoUtils } = ChromeUtils.import(
   "resource:///modules/MailCryptoUtils.jsm"
 );
 var { Pop3Authenticator } = ChromeUtils.import(
@@ -263,17 +264,23 @@ class Pop3Client {
 
   /**
    * The open event handler.
    */
   _onOpen = () => {
     this._logger.debug("Connected");
     this._socket.ondata = this._onData;
     this._socket.onclose = this._onClose;
-    this._nextAction = () => {
+    this._nextAction = res => {
+      // See if there is an APOP timestamp.
+      // eslint-disable-next-line no-control-regex
+      let matches = res.statusText.match(/<[\x00-\x7F]+@[\x00-\x7F]+>/);
+      if (matches?.[0]) {
+        this._apopTimestamp = matches[0];
+      }
       this.onOpen();
     };
   };
 
   /**
    * Parse the server response.
    * @param {string} str - Response received from the server.
    * @returns {Pop3Response}
@@ -513,25 +520,28 @@ class Pop3Client {
       }
       return;
     }
 
     // If a preferred method is not supported by the server, no need to try it.
     this._possibleAuthMethods = this._preferredAuthMethods.filter(x =>
       this._supportedAuthMethods.includes(x)
     );
+    if (!this._possibleAuthMethods.length) {
+      if (this._server.authMethod == Ci.nsMsgAuthMethod.passwordCleartext) {
+        this._possibleAuthMethods.unshift("USERPASS");
+      } else if (
+        this._server.authMethod == Ci.nsMsgAuthMethod.passwordEncrypted &&
+        this._apopTimestamp
+      ) {
+        this._possibleAuthMethods.unshift("APOP");
+      }
+    }
     this._logger.debug(`Possible auth methods: ${this._possibleAuthMethods}`);
     this._nextAuthMethod = this._nextAuthMethod || this._possibleAuthMethods[0];
-    if (
-      !this._supportedAuthMethods.length &&
-      this._server.authMethod == Ci.nsMsgAuthMethod.passwordCleartext
-    ) {
-      this._possibleAuthMethods.unshift("USERPASS");
-      this._nextAuthMethod = "USERPASS";
-    }
 
     if (this._nextAuthMethod) {
       this._updateStatus("hostContact");
       this._actionAuth();
       return;
     }
 
     // Preferred auth methods don't match any supported auth methods. Give user
@@ -609,16 +619,30 @@ class Pop3Client {
       case "LOGIN":
         this._nextAction = this._actionAuthLoginUser;
         this._send("AUTH LOGIN");
         break;
       case "CRAM-MD5":
         this._nextAction = this._actionAuthCramMd5;
         this._send("AUTH CRAM-MD5");
         break;
+      case "APOP": {
+        let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
+          Ci.nsICryptoHash
+        );
+        hasher.init(hasher.MD5);
+        let data =
+          this._apopTimestamp +
+          (await this._authenticator.getByteStringPassword());
+        let digest = CommonUtils.bytesAsHex(
+          CryptoUtils.digestBytes(data, hasher)
+        );
+        this._send(`APOP ${this._authenticator.username} ${digest}`, true);
+        break;
+      }
       case "GSSAPI": {
         this._authenticator.initGssapiAuth("pop");
         try {
           let token = this._authenticator.getNextGssapiToken("");
           this._nextAction = res => this._actionAuthGssapi(res, token);
         } catch (e) {
           this._logger.error(e);
           this._actionError("pop3GssapiFailure");