summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/base/src/MailAuthenticator.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/base/src/MailAuthenticator.jsm')
-rw-r--r--comm/mailnews/base/src/MailAuthenticator.jsm468
1 files changed, 468 insertions, 0 deletions
diff --git a/comm/mailnews/base/src/MailAuthenticator.jsm b/comm/mailnews/base/src/MailAuthenticator.jsm
new file mode 100644
index 0000000000..cf52a88f17
--- /dev/null
+++ b/comm/mailnews/base/src/MailAuthenticator.jsm
@@ -0,0 +1,468 @@
+/* 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/. */
+
+const EXPORTED_SYMBOLS = [
+ "SmtpAuthenticator",
+ "NntpAuthenticator",
+ "Pop3Authenticator",
+ "ImapAuthenticator",
+];
+
+var { MailCryptoUtils } = ChromeUtils.import(
+ "resource:///modules/MailCryptoUtils.jsm"
+);
+var { MailStringUtils } = ChromeUtils.import(
+ "resource:///modules/MailStringUtils.jsm"
+);
+
+/**
+ * A base class for interfaces when authenticating a mail connection.
+ */
+class MailAuthenticator {
+ /**
+ * Get the hostname for a connection.
+ *
+ * @returns {string}
+ */
+ get hostname() {
+ throw Components.Exception(
+ "hostname getter not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ /**
+ * Get the username for a connection.
+ *
+ * @returns {string}
+ */
+ get username() {
+ throw Components.Exception(
+ "username getter not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ /**
+ * Forget cached password.
+ */
+ forgetPassword() {
+ throw Components.Exception(
+ "forgetPassword not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ /**
+ * Get the password for a connection.
+ *
+ * @returns {string}
+ */
+ getPassword() {
+ throw Components.Exception(
+ "getPassword not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ /**
+ * Get the CRAM-MD5 auth token for a connection.
+ *
+ * @param {string} password - The password, used as HMAC-MD5 secret.
+ * @param {string} challenge - The base64 encoded server challenge.
+ * @returns {string}
+ */
+ getCramMd5Token(password, challenge) {
+ // Hash the challenge.
+ let signature = MailCryptoUtils.hmacMd5(
+ new TextEncoder().encode(password),
+ new TextEncoder().encode(atob(challenge))
+ );
+ // Get the hex form of the signature.
+ let hex = [...signature].map(x => x.toString(16).padStart(2, "0")).join("");
+ return btoa(`${this.username} ${hex}`);
+ }
+
+ /**
+ * Get the OAuth token for a connection.
+ *
+ * @returns {string}
+ */
+ async getOAuthToken() {
+ throw Components.Exception(
+ "getOAuthToken not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ /**
+ * Init a nsIMailAuthModule instance for GSSAPI auth.
+ *
+ * @param {('smtp'|'imap')} protocol - The protocol name.
+ */
+ initGssapiAuth(protocol) {
+ this._authModule = Cc["@mozilla.org/mail/auth-module;1"].createInstance(
+ Ci.nsIMailAuthModule
+ );
+ this._authModule.init(
+ "sasl-gssapi", // Auth module type
+ `${protocol}@${this.hostname}`,
+ 0, // nsIAuthModule::REQ_DEFAULT
+ null, // domain
+ this.username,
+ null // password
+ );
+ }
+
+ /**
+ * Get the next token in a sequence of GSSAPI auth steps.
+ *
+ * @param {string} inToken - A base64 encoded string, usually server challenge.
+ * @returns {string}
+ */
+ getNextGssapiToken(inToken) {
+ return this._authModule.getNextToken(inToken);
+ }
+
+ /**
+ * Init a nsIMailAuthModule instance for NTLM auth.
+ */
+ initNtlmAuth() {
+ this._authModule = Cc["@mozilla.org/mail/auth-module;1"].createInstance(
+ Ci.nsIMailAuthModule
+ );
+ this._authModule.init(
+ "ntlm", // Auth module type
+ null, // Service name
+ 0, // nsIAuthModule::REQ_DEFAULT
+ null, // domain
+ this.username,
+ this.getPassword()
+ );
+ }
+
+ /**
+ * Get the next token in a sequence of NTLM auth steps.
+ *
+ * @param {string} inToken - A base64 encoded string, usually server challenge.
+ * @returns {string}
+ */
+ getNextNtlmToken(inToken) {
+ return this._authModule.getNextToken(inToken);
+ }
+
+ /**
+ * Show a dialog for authentication failure.
+ *
+ * @returns {number} - 0: Retry; 1: Cancel; 2: New password.
+ */
+ promptAuthFailed() {
+ throw Components.Exception(
+ "promptAuthFailed not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ /**
+ * Show a dialog for authentication failure.
+ *
+ * @param {nsIMsgWindow} msgWindow - The associated msg window.
+ * @param {string} accountname - A user defined account name or the server hostname.
+ * @returns {number} 0: Retry; 1: Cancel; 2: New password.
+ */
+ _promptAuthFailed(msgWindow, accountname) {
+ let bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messenger.properties"
+ );
+ let message = bundle.formatStringFromName("mailServerLoginFailed2", [
+ this.hostname,
+ this.username,
+ ]);
+
+ let title = bundle.formatStringFromName(
+ "mailServerLoginFailedTitleWithAccount",
+ [accountname]
+ );
+
+ let retryButtonLabel = bundle.GetStringFromName(
+ "mailServerLoginFailedRetryButton"
+ );
+ let newPasswordButtonLabel = bundle.GetStringFromName(
+ "mailServerLoginFailedEnterNewPasswordButton"
+ );
+ let buttonFlags =
+ Ci.nsIPrompt.BUTTON_POS_0 * Ci.nsIPrompt.BUTTON_TITLE_IS_STRING +
+ Ci.nsIPrompt.BUTTON_POS_1 * Ci.nsIPrompt.BUTTON_TITLE_CANCEL +
+ Ci.nsIPrompt.BUTTON_POS_2 * Ci.nsIPrompt.BUTTON_TITLE_IS_STRING;
+ let dummyValue = { value: false };
+
+ return Services.prompt.confirmEx(
+ msgWindow?.domWindow,
+ title,
+ message,
+ buttonFlags,
+ retryButtonLabel,
+ null,
+ newPasswordButtonLabel,
+ null,
+ dummyValue
+ );
+ }
+}
+
+/**
+ * Collection of helper functions for authenticating an SMTP connection.
+ *
+ * @augments {MailAuthenticator}
+ */
+class SmtpAuthenticator extends MailAuthenticator {
+ /**
+ * @param {nsISmtpServer} server - The associated server instance.
+ */
+ constructor(server) {
+ super();
+ this._server = server;
+ }
+
+ get hostname() {
+ return this._server.hostname;
+ }
+
+ get username() {
+ return this._server.username;
+ }
+
+ forgetPassword() {
+ this._server.forgetPassword();
+ }
+
+ getPassword() {
+ if (this._server.password) {
+ return this._server.password;
+ }
+ let composeBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messengercompose/composeMsgs.properties"
+ );
+ let username = this._server.username;
+ let promptString;
+ if (username) {
+ promptString = composeBundle.formatStringFromName(
+ "smtpEnterPasswordPromptWithUsername",
+ [this._server.hostname, username]
+ );
+ } else {
+ promptString = composeBundle.formatStringFromName(
+ "smtpEnterPasswordPrompt",
+ [this._server.hostname]
+ );
+ }
+ let promptTitle = composeBundle.formatStringFromName(
+ "smtpEnterPasswordPromptTitleWithHostname",
+ [this._server.hostname]
+ );
+ return this._server.getPasswordWithUI(promptString, promptTitle);
+ }
+
+ /**
+ * Get the ByteString form of the current password.
+ *
+ * @returns {string}
+ */
+ getByteStringPassword() {
+ return MailStringUtils.stringToByteString(this.getPassword());
+ }
+
+ /**
+ * Get the PLAIN auth token for a connection.
+ *
+ * @returns {string}
+ */
+ getPlainToken() {
+ // According to rfc4616#section-2, password should be UTF-8 BinaryString
+ // before base64 encoded.
+ return btoa("\0" + this.username + "\0" + this.getByteStringPassword());
+ }
+
+ async getOAuthToken() {
+ let oauth2Module = Cc["@mozilla.org/mail/oauth2-module;1"].createInstance(
+ Ci.msgIOAuth2Module
+ );
+ if (!oauth2Module.initFromSmtp(this._server)) {
+ return Promise.reject(`initFromSmtp failed, hostname: ${this.hostname}`);
+ }
+ return new Promise((resolve, reject) => {
+ oauth2Module.connect(true, {
+ onSuccess: token => {
+ resolve(token);
+ },
+ onFailure: e => {
+ reject(e);
+ },
+ });
+ });
+ }
+
+ promptAuthFailed() {
+ return this._promptAuthFailed(
+ null,
+ this._server.description || this.hostname
+ );
+ }
+}
+
+/**
+ * Collection of helper functions for authenticating an incoming server.
+ *
+ * @augments {MailAuthenticator}
+ */
+class IncomingServerAuthenticator extends MailAuthenticator {
+ /**
+ * @param {nsIMsgIncomingServer} server - The associated server instance.
+ */
+ constructor(server) {
+ super();
+ this._server = server;
+ }
+
+ get hostname() {
+ return this._server.hostName;
+ }
+
+ get username() {
+ return this._server.username;
+ }
+
+ forgetPassword() {
+ this._server.forgetPassword();
+ }
+
+ /**
+ * Get the ByteString form of the current password.
+ *
+ * @returns {string}
+ */
+ async getByteStringPassword() {
+ return MailStringUtils.stringToByteString(await this.getPassword());
+ }
+
+ /**
+ * Get the PLAIN auth token for a connection.
+ *
+ * @returns {string}
+ */
+ async getPlainToken() {
+ // According to rfc4616#section-2, password should be UTF-8 BinaryString
+ // before base64 encoded.
+ return btoa(
+ "\0" + this.username + "\0" + (await this.getByteStringPassword())
+ );
+ }
+
+ async getOAuthToken() {
+ let oauth2Module = Cc["@mozilla.org/mail/oauth2-module;1"].createInstance(
+ Ci.msgIOAuth2Module
+ );
+ if (!oauth2Module.initFromMail(this._server)) {
+ return Promise.reject(`initFromMail failed, hostname: ${this.hostname}`);
+ }
+ return new Promise((resolve, reject) => {
+ oauth2Module.connect(true, {
+ onSuccess: token => {
+ resolve(token);
+ },
+ onFailure: e => {
+ reject(e);
+ },
+ });
+ });
+ }
+}
+
+/**
+ * Collection of helper functions for authenticating a NNTP connection.
+ *
+ * @augments {IncomingServerAuthenticator}
+ */
+class NntpAuthenticator extends IncomingServerAuthenticator {
+ /**
+ * @returns {string} - NNTP server has no userName pref, need to pass it in.
+ */
+ get username() {
+ return this._username;
+ }
+
+ set username(value) {
+ this._username = value;
+ }
+
+ promptAuthFailed() {
+ return this._promptAuthFailed(null, this._server.prettyName);
+ }
+}
+
+/**
+ * Collection of helper functions for authenticating a POP connection.
+ *
+ * @augments {IncomingServerAuthenticator}
+ */
+class Pop3Authenticator extends IncomingServerAuthenticator {
+ async getPassword() {
+ if (this._server.password) {
+ return this._server.password;
+ }
+ let composeBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/localMsgs.properties"
+ );
+ let params = [this._server.username, this._server.hostName];
+ let promptString = composeBundle.formatStringFromName(
+ "pop3EnterPasswordPrompt",
+ params
+ );
+ let promptTitle = composeBundle.formatStringFromName(
+ "pop3EnterPasswordPromptTitleWithUsername",
+ [this._server.hostName]
+ );
+ return this._server.wrappedJSObject.getPasswordWithUIAsync(
+ promptString,
+ promptTitle
+ );
+ }
+
+ promptAuthFailed() {
+ return this._promptAuthFailed(null, this._server.prettyName);
+ }
+}
+
+/**
+ * Collection of helper functions for authenticating an IMAP connection.
+ *
+ * @augments {IncomingServerAuthenticator}
+ */
+class ImapAuthenticator extends IncomingServerAuthenticator {
+ async getPassword() {
+ if (this._server.password) {
+ return this._server.password;
+ }
+ let composeBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/imapMsgs.properties"
+ );
+ let params = [this._server.username, this._server.hostName];
+ let promptString = composeBundle.formatStringFromName(
+ "imapEnterServerPasswordPrompt",
+ params
+ );
+ let promptTitle = composeBundle.formatStringFromName(
+ "imapEnterPasswordPromptTitleWithUsername",
+ [this._server.hostName]
+ );
+ return this._server.wrappedJSObject.getPasswordWithUIAsync(
+ promptString,
+ promptTitle
+ );
+ }
+
+ promptAuthFailed() {
+ return this._promptAuthFailed(null, this._server.prettyName);
+ }
+}