diff options
Diffstat (limited to 'mailnews/base/prefs/content/accountcreation/verifyConfig.js')
-rw-r--r-- | mailnews/base/prefs/content/accountcreation/verifyConfig.js | 347 |
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; + } +} |