Dactyloidae/mailnews/base/prefs/content/accountcreation/verifyConfig.js
2019-11-17 00:12:37 +08:00

353 lines
14 KiB
JavaScript

/* -*- 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");
#ifdef MOZ_MAILNEWS_OAUTH2
Components.utils.import("resource://gre/modules/OAuth2Providers.jsm");
#endif
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 {
#ifdef MOZ_MAILNEWS_OAUTH2
// 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)
#else
if (inServer.password)
#endif
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;
}
}