summaryrefslogtreecommitdiffstats
path: root/mailnews/base/prefs/content/accountcreation/verifyConfig.js
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/base/prefs/content/accountcreation/verifyConfig.js')
-rw-r--r--mailnews/base/prefs/content/accountcreation/verifyConfig.js347
1 files changed, 347 insertions, 0 deletions
diff --git a/mailnews/base/prefs/content/accountcreation/verifyConfig.js b/mailnews/base/prefs/content/accountcreation/verifyConfig.js
new file mode 100644
index 000000000..a2afbdad8
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/verifyConfig.js
@@ -0,0 +1,347 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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/. */
+
+/**
+ * This checks a given config, by trying a real connection and login,
+ * with username and password.
+ *
+ * TODO
+ * - give specific errors, bug 555448
+ * - return a working |Abortable| to allow cancel
+ *
+ * @param accountConfig {AccountConfig} The guessed account config.
+ * username, password, realname, emailaddress etc. are not filled out,
+ * but placeholders to be filled out via replaceVariables().
+ * @param alter {boolean}
+ * Try other usernames and login schemes, until login works.
+ * Warning: Modifies |accountConfig|.
+ *
+ * This function is async.
+ * @param successCallback function(accountConfig)
+ * Called when we could guess the config.
+ * For accountConfig, see below.
+ * @param errorCallback function(ex)
+ * Called when we could guess not the config, either
+ * because we have not found anything or
+ * because there was an error (e.g. no network connection).
+ * The ex.message will contain a user-presentable message.
+ */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/OAuth2Providers.jsm");
+
+if (typeof gEmailWizardLogger == "undefined") {
+ Cu.import("resource:///modules/gloda/log4moz.js");
+ var gEmailWizardLogger = Log4Moz.getConfiguredLogger("mail.wizard");
+}
+
+function verifyConfig(config, alter, msgWindow, successCallback, errorCallback)
+{
+ ddump(debugObject(config, "config", 3));
+ assert(config instanceof AccountConfig,
+ "BUG: Arg 'config' needs to be an AccountConfig object");
+ assert(typeof(alter) == "boolean");
+ assert(typeof(successCallback) == "function");
+ assert(typeof(errorCallback) == "function");
+
+ if (MailServices.accounts.findRealServer(config.incoming.username,
+ config.incoming.hostname,
+ sanitize.enum(config.incoming.type,
+ ["pop3", "imap", "nntp"]),
+ config.incoming.port)) {
+ errorCallback("Incoming server exists");
+ return;
+ }
+
+ // incoming server
+ let inServer =
+ MailServices.accounts.createIncomingServer(config.incoming.username,
+ config.incoming.hostname,
+ sanitize.enum(config.incoming.type,
+ ["pop3", "imap", "nntp"]));
+ inServer.port = config.incoming.port;
+ inServer.password = config.incoming.password;
+ if (config.incoming.socketType == 1) // plain
+ inServer.socketType = Ci.nsMsgSocketType.plain;
+ else if (config.incoming.socketType == 2) // SSL
+ inServer.socketType = Ci.nsMsgSocketType.SSL;
+ else if (config.incoming.socketType == 3) // STARTTLS
+ inServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
+
+ gEmailWizardLogger.info("Setting incoming server authMethod to " +
+ config.incoming.auth);
+ inServer.authMethod = config.incoming.auth;
+
+ try {
+ // Lookup issuer if needed.
+ if (config.incoming.auth == Ci.nsMsgAuthMethod.OAuth2 ||
+ config.outgoing.auth == Ci.nsMsgAuthMethod.OAuth2) {
+ if (!config.oauthSettings)
+ config.oauthSettings = {};
+ if (!config.oauthSettings.issuer || !config.oauthSettings.scope) {
+ // lookup issuer or scope from hostname
+ let hostname = (config.incoming.auth == Ci.nsMsgAuthMethod.OAuth2) ?
+ config.incoming.hostname : config.outgoing.hostname;
+ let hostDetails = OAuth2Providers.getHostnameDetails(hostname);
+ if (hostDetails)
+ [config.oauthSettings.issuer, config.oauthSettings.scope] = hostDetails;
+ if (!config.oauthSettings.issuer || !config.oauthSettings.scope)
+ throw "Could not get issuer for oauth2 authentication";
+ }
+ gEmailWizardLogger.info("Saving oauth parameters for issuer " +
+ config.oauthSettings.issuer);
+ inServer.setCharValue("oauth2.scope", config.oauthSettings.scope);
+ inServer.setCharValue("oauth2.issuer", config.oauthSettings.issuer);
+ gEmailWizardLogger.info("OAuth2 issuer, scope is " +
+ config.oauthSettings.issuer + ", " + config.oauthSettings.scope);
+ }
+
+ if (inServer.password ||
+ inServer.authMethod == Ci.nsMsgAuthMethod.OAuth2)
+ verifyLogon(config, inServer, alter, msgWindow,
+ successCallback, errorCallback);
+ else {
+ // Avoid pref pollution, clear out server prefs.
+ MailServices.accounts.removeIncomingServer(inServer, true);
+ successCallback(config);
+ }
+ return;
+ }
+ catch (e) {
+ gEmailWizardLogger.error("ERROR: verify logon shouldn't have failed");
+ }
+ // Avoid pref pollution, clear out server prefs.
+ MailServices.accounts.removeIncomingServer(inServer, true);
+ errorCallback(e);
+}
+
+function verifyLogon(config, inServer, alter, msgWindow, successCallback,
+ errorCallback)
+{
+ gEmailWizardLogger.info("verifyLogon for server at " + inServer.hostName);
+ // hack - save away the old callbacks.
+ let saveCallbacks = msgWindow.notificationCallbacks;
+ // set our own callbacks - this works because verifyLogon will
+ // synchronously create the transport and use the notification callbacks.
+ let listener = new urlListener(config, inServer, alter, msgWindow,
+ successCallback, errorCallback);
+ // our listener listens both for the url and cert errors.
+ msgWindow.notificationCallbacks = listener;
+ // try to work around bug where backend is clearing password.
+ try {
+ inServer.password = config.incoming.password;
+ let uri = inServer.verifyLogon(listener, msgWindow);
+ // clear msgWindow so url won't prompt for passwords.
+ uri.QueryInterface(Ci.nsIMsgMailNewsUrl).msgWindow = null;
+ }
+ catch (e) { gEmailWizardLogger.error("verifyLogon failed: " + e); throw e;}
+ finally {
+ // restore them
+ msgWindow.notificationCallbacks = saveCallbacks;
+ }
+}
+
+/**
+ * The url listener also implements nsIBadCertListener2. Its job is to prevent
+ * "bad cert" security dialogs from being shown to the user. Currently it puts
+ * up the cert override dialog, though we'd like to give the user more detailed
+ * information in the future.
+ */
+
+function urlListener(config, server, alter, msgWindow, successCallback,
+ errorCallback)
+{
+ this.mConfig = config;
+ this.mServer = server;
+ this.mAlter = alter;
+ this.mSuccessCallback = successCallback;
+ this.mErrorCallback = errorCallback;
+ this.mMsgWindow = msgWindow;
+ this.mCertError = false;
+ this._log = Log4Moz.getConfiguredLogger("mail.wizard");
+}
+urlListener.prototype =
+{
+ OnStartRunningUrl: function(aUrl)
+ {
+ this._log.info("Starting to test username");
+ this._log.info(" username=" + (this.mConfig.incoming.username !=
+ this.mConfig.identity.emailAddress) +
+ ", have savedUsername=" +
+ (this.mConfig.usernameSaved ? "true" : "false"));
+ this._log.info(" authMethod=" + this.mServer.authMethod);
+ },
+
+ OnStopRunningUrl: function(aUrl, aExitCode)
+ {
+ this._log.info("Finished verifyConfig resulted in " + aExitCode);
+ if (Components.isSuccessCode(aExitCode))
+ {
+ this._cleanup();
+ this.mSuccessCallback(this.mConfig);
+ }
+ // Logon failed, and we aren't supposed to try other variations.
+ else if (!this.mAlter)
+ {
+ this._cleanup();
+ var errorMsg = getStringBundle(
+ "chrome://messenger/locale/accountCreationModel.properties")
+ .GetStringFromName("cannot_login.error");
+ this.mErrorCallback(new Exception(errorMsg));
+ }
+ // Try other variations, unless there's a cert error, in which
+ // case we'll see what the user chooses.
+ else if (!this.mCertError)
+ {
+ this.tryNextLogon()
+ }
+ },
+
+ tryNextLogon: function()
+ {
+ this._log.info("tryNextLogon()");
+ this._log.info(" username=" + (this.mConfig.incoming.username !=
+ this.mConfig.identity.emailAddress) +
+ ", have savedUsername=" +
+ (this.mConfig.usernameSaved ? "true" : "false"));
+ this._log.info(" authMethod=" + this.mServer.authMethod);
+ // check if we tried full email address as username
+ if (this.mConfig.incoming.username != this.mConfig.identity.emailAddress)
+ {
+ this._log.info(" Changing username to email address.");
+ this.mConfig.usernameSaved = this.mConfig.incoming.username;
+ this.mConfig.incoming.username = this.mConfig.identity.emailAddress;
+ this.mConfig.outgoing.username = this.mConfig.identity.emailAddress;
+ this.mServer.username = this.mConfig.incoming.username;
+ this.mServer.password = this.mConfig.incoming.password;
+ verifyLogon(this.mConfig, this.mServer, this.mAlter, this.mMsgWindow,
+ this.mSuccessCallback, this.mErrorCallback);
+ return;
+ }
+
+ if (this.mConfig.usernameSaved)
+ {
+ this._log.info(" Re-setting username.");
+ // If we tried the full email address as the username, then let's go
+ // back to trying just the username before trying the other cases.
+ this.mConfig.incoming.username = this.mConfig.usernameSaved;
+ this.mConfig.outgoing.username = this.mConfig.usernameSaved;
+ this.mConfig.usernameSaved = null;
+ this.mServer.username = this.mConfig.incoming.username;
+ this.mServer.password = this.mConfig.incoming.password;
+ }
+
+ // sec auth seems to have failed, and we've tried both
+ // varieties of user name, sadly.
+ // So fall back to non-secure auth, and
+ // again try the user name and email address as username
+ assert(this.mConfig.incoming.auth == this.mServer.authMethod);
+ this._log.info(" Using SSL: " +
+ (this.mServer.socketType == Ci.nsMsgSocketType.SSL ||
+ this.mServer.socketType == Ci.nsMsgSocketType.alwaysSTARTTLS));
+ if (this.mConfig.incoming.authAlternatives &&
+ this.mConfig.incoming.authAlternatives.length)
+ // We may be dropping back to insecure auth methods here,
+ // which is not good. But then again, we already warned the user,
+ // if it is a config without SSL.
+ {
+ this._log.info(" auth alternatives = " +
+ this.mConfig.incoming.authAlternatives.join(","));
+ this._log.info(" Decreasing auth.");
+ this._log.info(" Have password: " +
+ (this.mServer.password ? "true" : "false"));
+ let brokenAuth = this.mConfig.incoming.auth;
+ // take the next best method (compare chooseBestAuthMethod() in guess)
+ this.mConfig.incoming.auth =
+ this.mConfig.incoming.authAlternatives.shift();
+ this.mServer.authMethod = this.mConfig.incoming.auth;
+ // Assume that SMTP server has same methods working as incoming.
+ // Broken assumption, but we currently have no SMTP verification.
+ // TODO implement real SMTP verification
+ if (this.mConfig.outgoing.auth == brokenAuth &&
+ this.mConfig.outgoing.authAlternatives.indexOf(
+ this.mConfig.incoming.auth) != -1)
+ this.mConfig.outgoing.auth = this.mConfig.incoming.auth;
+ this._log.info(" outgoing auth: " + this.mConfig.outgoing.auth);
+ verifyLogon(this.mConfig, this.mServer, this.mAlter, this.mMsgWindow,
+ this.mSuccessCallback, this.mErrorCallback);
+ return;
+ }
+
+ // Tried all variations we can. Give up.
+ this._log.info("Giving up.");
+ this._cleanup();
+ let errorMsg = getStringBundle(
+ "chrome://messenger/locale/accountCreationModel.properties")
+ .GetStringFromName("cannot_login.error");
+ this.mErrorCallback(new Exception(errorMsg));
+ return;
+ },
+
+ _cleanup : function()
+ {
+ try {
+ // Avoid pref pollution, clear out server prefs.
+ if (this.mServer) {
+ MailServices.accounts.removeIncomingServer(this.mServer, true);
+ this.mServer = null;
+ }
+ } catch (e) { this._log.error(e); }
+ },
+
+ // Suppress any certificate errors
+ notifyCertProblem: function(socketInfo, status, targetSite) {
+ this.mCertError = true;
+ this._log.error("cert error");
+ let self = this;
+ setTimeout(function () {
+ try {
+ self.informUserOfCertError(socketInfo, status, targetSite);
+ } catch (e) { logException(e); }
+ }, 0);
+ return true;
+ },
+
+ informUserOfCertError : function(socketInfo, status, targetSite) {
+ var params = {
+ exceptionAdded : false,
+ sslStatus : status,
+ prefetchCert : true,
+ location : targetSite,
+ };
+ window.openDialog("chrome://pippki/content/exceptionDialog.xul",
+ "","chrome,centerscreen,modal", params);
+ this._log.info("cert exception dialog closed");
+ this._log.info("cert exceptionAdded = " + params.exceptionAdded);
+ if (!params.exceptionAdded) {
+ this._cleanup();
+ let errorMsg = getStringBundle(
+ "chrome://messenger/locale/accountCreationModel.properties")
+ .GetStringFromName("cannot_login.error");
+ this.mErrorCallback(new Exception(errorMsg));
+ }
+ else {
+ // Retry the logon now that we've added the cert exception.
+ verifyLogon(this.mConfig, this.mServer, this.mAlter, this.mMsgWindow,
+ this.mSuccessCallback, this.mErrorCallback);
+ }
+ },
+
+ // nsIInterfaceRequestor
+ getInterface: function(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsISupports
+ QueryInterface: function(iid) {
+ if (!iid.equals(Components.interfaces.nsIBadCertListener2) &&
+ !iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
+ !iid.equals(Components.interfaces.nsIUrlListener) &&
+ !iid.equals(Components.interfaces.nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+
+ return this;
+ }
+}