Bug 953875 - Use toolkit password manager, r=clokep.
authorFlorian Quèze <florian@instantbird.org>
Wed, 25 Jan 2012 15:25:58 +0100
changeset 18520 a2a44dff8a5ff8e713da001888ace6f1128ed3ee
parent 18519 2f998822f3aad6793c97ba2e6b1306f5d7dab091
child 18521 36621ac71303f0c9eddf487d10419621baa38fde
push idunknown
push userunknown
push dateunknown
reviewersclokep
bugs953875
Bug 953875 - Use toolkit password manager, r=clokep.
chat/chat-prefs.js
chat/components/public/imIAccount.idl
chat/components/src/imAccounts.js
chat/locales/en-US/accounts.properties
chat/locales/jar.mn
chat/modules/jsProtoHelper.jsm
im/app/profile/all-instantbird.js
im/content/account.xml
im/content/account.xul
im/content/accountWizard.js
im/content/accountWizard.xul
im/content/accounts.js
im/content/debug/fake/fake.js
im/content/preferences/privacy.js
im/content/preferences/privacy.xul
im/locales/en-US/chrome/instantbird/account.dtd
im/locales/en-US/chrome/instantbird/accountWizard.dtd
im/locales/en-US/chrome/instantbird/accounts.properties
im/locales/en-US/chrome/instantbird/preferences/privacy.dtd
im/locales/en-US/chrome/instantbird/preferences/security.dtd
im/locales/jar.mn
--- a/chat/chat-prefs.js
+++ b/chat/chat-prefs.js
@@ -1,16 +1,20 @@
 // What to do when starting up
 //  0 = do not connect / show the account manager
 //  1 = connect automatically
 //  Other values will be added later, for example to start minimized
 pref("messenger.startup.action", 1);
 
 pref("messenger.accounts", "");
 
+// Should the accounts service stored in the password manager the
+// passwords that are currently stored in the preferences?
+pref("messenger.accounts.convertOldPasswords", false);
+
 // The intervals in seconds between automatic reconnection attempts
 // The last value will be reused forever.
 // A value of 0 means that there will be no more reconnection attempts.
 pref("messenger.accounts.reconnectTimer", "1,5,30,60,90,300,600,1200,3600");
 
 // List of tags ids whose contacts should be shown in the special
 // "Other contacts" group.
 pref("messenger.buddies.hiddenTags", "");
--- a/chat/components/public/imIAccount.idl
+++ b/chat/components/public/imIAccount.idl
@@ -236,17 +236,17 @@ interface imIAccount: prplIAccount {
   /* Set when the account is trying to connect for the first time
      with the current parameters (removed after a successsful connection) */
   const short FIRST_CONNECTION_PENDING = 2;
   /* Set at startup when the previous state was pending */
   const short FIRST_CONNECTION_CRASHED = 4;
 
   attribute short firstConnectionState;
 
-  // FIXME password should be in password manager
+  // Passwords are stored in the toolkit Password Manager.
   attribute AUTF8String password;
 
   attribute AUTF8String alias;
 
   /* While an account is connecting, this attribute contains a message
      indicating the current step of the connection */
   readonly attribute AUTF8String connectionStateMsg;
 
--- a/chat/components/src/imAccounts.js
+++ b/chat/components/src/imAccounts.js
@@ -42,21 +42,34 @@ Cu.import("resource:///modules/imService
 
 const kPrefAutologinPending = "messenger.accounts.autoLoginPending";
 const kPrefMessengerAccounts = "messenger.accounts";
 const kPrefAccountPrefix = "messenger.account.";
 const kAccountKeyPrefix = "account";
 const kAccountOptionPrefPrefix = "options.";
 const kPrefAccountName = "name";
 const kPrefAccountPrpl = "prpl";
-const kPrefAccountPassword = "password";
 const kPrefAccountAutoLogin = "autoLogin";
 const kPrefAccountAlias = "alias";
 const kPrefAccountFirstConnectionState = "firstConnectionState";
 
+const kPrefConvertOldPasswords = "messenger.accounts.convertOldPasswords";
+const kPrefAccountPassword = "password";
+
+XPCOMUtils.defineLazyGetter(this, "LoginManager", function()
+  Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager)
+);
+
+XPCOMUtils.defineLazyGetter(this, "_", function()
+  l10nHelper("chrome://chat/locale/accounts.properties")
+);
+
+var gUserCanceledMasterPasswordPrompt = false;
+var gConvertingOldPasswords = false;
+
 var SavePrefTimer = {
   saveNow: function() {
     if (this._timer) {
       clearTimeout(this._timer);
       this._timer = null;
     }
     Services.prefs.savePrefFile(null);
   },
@@ -162,16 +175,27 @@ function imAccount(aKey, aName, aPrplId)
   // Send status change notifications to the account.
   this.observedStatusInfo = null; // (To execute the setter).
 
   // If we have never finished the first connection attempt for this account,
   // mark the account as having caused a crash.
   if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_PENDING)
     this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_CRASHED;
 
+  // Try to convert old passwords stored in the preferences.
+  // Don't try too hard if the user has canceled a master password prompt:
+  // we don't want to display several of theses prompts at startup.
+  if (gConvertingOldPasswords && !this.protocol.noPassword) {
+    try {
+      let password = this.prefBranch.getCharPref(kPrefAccountPassword);
+      if (password && !this.password)
+        this.password = password;
+    } catch (e) { /* No password saved in the prefs for this account. */ }
+  }
+
   // Check for errors that should prevent connection attempts.
   if (this._passwordRequired && !this.password)
     this._connectionErrorReason = Ci.imIAccount.ERROR_MISSING_PASSWORD;
   else if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_CRASHED)
     this._connectionErrorReason = Ci.imIAccount.ERROR_CRASHED;
 }
 
 imAccount.prototype = {
@@ -182,17 +206,19 @@ imAccount.prototype = {
   numericId: 0,
   protocol: null,
   prplAccount: null,
   connectionState: Ci.imIAccount.STATE_DISCONNECTED,
   connectionStateMsg: "",
   connectionErrorMessage: "",
   _connectionErrorReason: Ci.prplIAccount.NO_ERROR,
   get connectionErrorReason() {
-    if (this._connectionErrorReason != Ci.prplIAccount.NO_ERROR)
+    if (this._connectionErrorReason != Ci.prplIAccount.NO_ERROR &&
+        (this._connectionErrorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD ||
+         !this._password))
       return this._connectionErrorReason;
     else
       return this.prplAccount.connectionErrorReason;
   },
 
   observe: function(aSubject, aTopic, aData) {
     if (aTopic == "account-connect-progress")
       this.connectionStateMsg = aData;
@@ -372,35 +398,102 @@ imAccount.prototype = {
   get alias() {
     try {
       return this.prefBranch.getCharPref(kPrefAccountAlias);
     } catch (e) {
       return "";
     }
   },
 
+  _password: "",
   get password() {
+    if (this._password)
+      return this._password;
+
+    // Avoid prompting the user for the master password more than once at startup.
+    if (gUserCanceledMasterPasswordPrompt)
+      return "";
+
+    let passwordURI = "im://" + this.protocol.id;
+    let logins;
     try {
-      return this.prefBranch.getCharPref(kPrefAccountPassword);
+      logins = LoginManager.findLogins({}, passwordURI, null, passwordURI);
     } catch (e) {
+      this._handleMasterPasswordException(e);
       return "";
     }
+    let normalizedName = this.normalizedName;
+    for each (let login in logins) {
+      if (login.username == normalizedName) {
+        this._password = login.password;
+        if (this._connectionErrorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD) {
+          // We have found a password for an account marked as missing password,
+          // re-check all others accounts missing a password. But first,
+          // remove the error on our own account to avoid re-checking it.
+          delete this._connectionErrorReason;
+          gAccountsService._checkIfPasswordStillMissing();
+        }
+        return this._password;
+      }
+    }
+    return "";
+  },
+  _checkIfPasswordStillMissing: function() {
+    if (this._connectionErrorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD ||
+        !this.password)
+      return;
+
+    delete this._connectionErrorReason;
+    this._sendUpdateNotification();
   },
   get _passwordRequired()
     !this.protocol.noPassword && !this.protocol.passwordOptional,
   set password(aPassword) {
-    this.prefBranch.setCharPref(kPrefAccountPassword, aPassword);
+    this._password = aPassword;
+    if (gUserCanceledMasterPasswordPrompt)
+      return;
+    let newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]
+                   .createInstance(Ci.nsILoginInfo);
+    let passwordURI = "im://" + this.protocol.id;
+    newLogin.init(passwordURI, null, passwordURI, this.normalizedName,
+                  aPassword, "", "");
+    try {
+      let logins = LoginManager.findLogins({}, passwordURI, null, passwordURI);
+      let saved = false;
+      for each (let login in logins) {
+        if (newLogin.matches(login, true)) {
+          if (aPassword)
+            LoginManager.modifyLogin(login, newLogin);
+          else
+            LoginManager.removeLogin(login);
+          saved = true;
+          break;
+        }
+      }
+      if (!saved && aPassword)
+        LoginManager.addLogin(newLogin);
+    } catch (e) {
+      this._handleMasterPasswordException(e);
+    }
+
     this._connectionInfoChanged();
     if (aPassword &&
         this._connectionErrorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD)
       this._connectionErrorReason = Ci.imIAccount.NO_ERROR;
     else if (!aPassword && this._passwordRequired)
       this._connectionErrorReason = Ci.imIAccount.ERROR_MISSING_PASSWORD;
     this._sendUpdateNotification();
   },
+  _handleMasterPasswordException: function(aException) {
+    if (aException.result != Components.results.NS_ERROR_ABORT)
+      throw aException;
+
+    gUserCanceledMasterPasswordPrompt = true;
+    executeSoon(function () { gUserCanceledMasterPasswordPrompt = false; });
+  },
 
   get autoLogin() {
     let autoLogin = true;
     try {
       autoLogin = this.prefBranch.getBoolPref(kPrefAccountAutoLogin);
     } catch (e) { }
     return autoLogin;
   },
@@ -430,16 +523,27 @@ imAccount.prototype = {
   _finishedAutoLogin: function() {
     if (!this.hasOwnProperty("_autoLoginPending"))
       return;
     delete this._autoLoginPending;
     AutoLoginCounter.finishedAutoLogin();
   },
 
   remove: function() {
+    let login = Cc["@mozilla.org/login-manager/loginInfo;1"]
+                .createInstance(Ci.nsILoginInfo);
+    let passwordURI = "im://" + this.protocol.id;
+    login.init(passwordURI, null, passwordURI, this.normalizedName, "", "", "");
+    let logins = LoginManager.findLogins({}, passwordURI, null, passwordURI);
+    for each (let l in logins) {
+      if (login.matches(l, true)) {
+        LoginManager.removeLogin(l);
+        break;
+      }
+    }
     this.unInit();
     Services.contacts.forgetAccount(this.numericId);
     this.prefBranch.deleteBranch("");
   },
   unInit: function() {
     // remove any pending reconnection timer.
     this.cancelReconnection();
 
@@ -460,17 +564,43 @@ imAccount.prototype = {
     delete this.prplAccount;
   },
 
   get _ensurePrplAccount() {
     if (this.prplAccount)
       return this.prplAccount;
     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   },
-  connect: function() { this._ensurePrplAccount.connect(); },
+  connect: function() {
+    if (this._passwordRequired) {
+      // If the previous connection attempt failed because we have a wrong password,
+      // clear the passwor cache so that if there's no password in the password
+      // manager the user gets prompted again.
+      if (this.connectionErrorReason == Ci.prplIAccount.ERROR_AUTHENTICATION_FAILED)
+        delete this._password;
+
+      let password = this.password;
+      if (!password) {
+        let prompts = Services.prompt;
+        let shouldSave = {value: false};
+        password = {value: ""};
+        if (!prompts.promptPassword(null, _("passwordPromptTitle", this.name),
+                                    _("passwordPromptText", this.name),
+                                    password, _("passwordPromptSaveCheckbox"),
+                                    shouldSave))
+          return;
+
+        if (shouldSave.value)
+          this.password = password.value;
+        else
+          this._password = password.value;
+      }
+    }
+    this._ensurePrplAccount.connect();
+  },
   disconnect: function() {
     if (this._statusObserver) {
       this.statusInfo.removeObserver(this._statusObserver);
       delete this._statusObserver;
     }
     this._ensurePrplAccount.disconnect();
   },
 
@@ -540,36 +670,46 @@ imAccount.prototype = {
 
   get proxyInfo() this._ensurePrplAccount.proxyInfo,
   set proxyInfo(val) {
     this._ensurePrplAccount.proxyInfo = val;
     this._connectionInfoChanged();
   }
 };
 
+var gAccountsService = null;
+
 function AccountsService() { }
 AccountsService.prototype = {
   initAccounts: function() {
     this._initAutoLoginStatus();
     this._accounts = [];
     this._accountsById = {};
+    gAccountsService = this;
+    gConvertingOldPasswords =
+      Services.prefs.getBoolPref(kPrefConvertOldPasswords);
     let accountList = this._accountList;
     for each (let account in (accountList ? accountList.split(",") : [])) {
       try {
         account.trim();
         if (!account)
           throw Cr.NS_ERROR_INVALID_ARG;
         let newAccount = new imAccount(account);
         this._accounts.push(newAccount);
         this._accountsById[newAccount.numericId] = newAccount;
       } catch (e) {
         Cu.reportError(e);
         dump(e + " " + e.toSource() + "\n");
       }
     }
+    // If the user has canceled a master password prompt, we haven't
+    // been able to save any password, so the old password conversion
+    // still needs to happen.
+    if (gConvertingOldPasswords && !gUserCanceledMasterPasswordPrompt)
+      Services.prefs.setBoolPref(kPrefConvertOldPasswords, false);
 
     this._prefObserver = this.observe.bind(this);
     Services.prefs.addObserver(kPrefMessengerAccounts, this._prefObserver, false);
   },
 
   _observingAccountListChange: true,
   _prefObserver: null,
   observe: function(aSubject, aTopic, aData) {
@@ -592,16 +732,17 @@ AccountsService.prototype = {
     this._observingAccountListChange = false;
     Services.prefs.setCharPref(kPrefMessengerAccounts, aNewList);
     delete this._observingAccountListChange;
   },
 
   unInitAccounts: function() {
     for each (let account in this._accounts)
       account.unInit();
+    gAccountsService = null;
     delete this._accounts;
     delete this._accountsById;
     Services.prefs.removeObserver(kPrefMessengerAccounts, this._prefObserver);
     delete this._prefObserver;
   },
 
   autoLoginStatus: Ci.imIAccountsService.AUTOLOGIN_ENABLED,
   _initAutoLoginStatus: function() {
@@ -710,16 +851,28 @@ AccountsService.prototype = {
     // should be processed now.
     this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_ENABLED;
 
     // Notify observers so that any message stating that autologin is
     // disabled can be removed
     Services.obs.notifyObservers(this, "autologin-processed", null);
   },
 
+  _checkingIfPasswordStillMissing: false,
+  _checkIfPasswordStillMissing: function() {
+    // Avoid recursion.
+    if (this._checkingIfPasswordStillMissing)
+      return;
+
+    this._checkingIfPasswordStillMissing = true;
+    for each (let account in this._accounts)
+      account._checkIfPasswordStillMissing();
+    delete this._checkingIfPasswordStillMissing;
+  },
+
   getAccountById: function(aAccountId) {
     if (aAccountId.indexOf(kAccountKeyPrefix) != 0)
       throw Cr.NS_ERROR_INVALID_ARG;
 
     let id = parseInt(aAccountId.substr(kAccountKeyPrefix.length));
     return this.getAccountByNumericId(id);
   },
 
new file mode 100644
--- /dev/null
+++ b/chat/locales/en-US/accounts.properties
@@ -0,0 +1,3 @@
+passwordPromptTitle=Password for %S
+passwordPromptText=Please enter your password for your account %S in order to connect it.
+passwordPromptSaveCheckbox=Use Password Manager to remember this password.
--- a/chat/locales/jar.mn
+++ b/chat/locales/jar.mn
@@ -1,10 +1,11 @@
 #filter substitution
 
 @AB_CD@.jar:
 % locale chat @AB_CD@ %locale/@AB_CD@/chat/
+	locale/@AB_CD@/chat/accounts.properties (%accounts.properties)
 	locale/@AB_CD@/chat/commands.properties (%commands.properties)
 	locale/@AB_CD@/chat/conversations.properties (%conversations.properties)
 	locale/@AB_CD@/chat/facebook.properties	(%facebook.properties)
 	locale/@AB_CD@/chat/status.properties	(%status.properties)
 	locale/@AB_CD@/chat/twitter.properties	(%twitter.properties)
 	locale/@AB_CD@/chat/xmpp.properties	(%xmpp.properties)
--- a/chat/modules/jsProtoHelper.jsm
+++ b/chat/modules/jsProtoHelper.jsm
@@ -723,17 +723,17 @@ const GenericProtocolPrototype = {
   get usernameEmptyText() "",
   accountExists: function() false, //FIXME
 
   get uniqueChatName() false,
   get chatHasTopic() false,
   get noPassword() false,
   get newMailNotification() false,
   get imagesInIM() false,
-  get passwordOptional() true,
+  get passwordOptional() false,
   get usePointSize() true,
   get registerNoScreenName() false,
   get slashCommandsNative() false,
   get usePurpleProxy() false,
 
   get classDescription() this.name + " Protocol",
   get contractID() "@instantbird.org/purple/" + this.normalizedName + ";1"
 };
--- a/im/app/profile/all-instantbird.js
+++ b/im/app/profile/all-instantbird.js
@@ -21,16 +21,17 @@ pref("general.autoScroll", true);
 
 // this will automatically enable inline spellchecking (if it is available) for
 // editable elements in HTML
 // 0 = spellcheck nothing
 // 1 = check multi-line controls [default]
 // 2 = check multi/single line controls
 pref("layout.spellcheckDefault", 1);
 
+pref("messenger.accounts.convertOldPasswords", true);
 pref("messenger.accounts.promptOnDelete", true);
 
 pref("messenger.buddies.showOffline", false);
 pref("messenger.buddies.hideTagPrompt", true);
 
 pref("messenger.conversations.openInTabs", true);
 pref("messenger.conversations.useSeparateWindowsForMUCs", false);
 pref("messenger.conversations.doubleClickToReply", true);
--- a/im/content/account.xml
+++ b/im/content/account.xml
@@ -136,22 +136,24 @@
         const key = "account.connection.error";
         var account = this._account;
         var text;
         let errorReason = account.connectionErrorReason;
         if (errorReason == Ci.imIAccount.ERROR_UNKNOWN_PRPL)
           text = bundle.getFormattedString(key + "UnknownPrpl",
                                            [account.protocol.id]);
         else if (errorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD)
-          text = bundle.getString(key + "MissingPassword");
+          text = bundle.getString(key + "EnteringPasswordRequired");
         else if (errorReason == Ci.imIAccount.ERROR_CRASHED)
           text = bundle.getString(key + "CrashedAccount");
         else
           text = account.connectionErrorMessage;
-        text = bundle.getFormattedString(key, [text]);
+
+        if (errorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD)
+          text = bundle.getFormattedString(key, [text]);
 
         this.setAttribute("error", "true");
         var error = document.getAnonymousElementByAttribute(this, "anonid",
                                                             "error");
         error.textContent = text;
 
         var updateReconnect = (function() {
           var date = Math.round((account.timeOfNextReconnect - Date.now()) / 1000);
--- a/im/content/account.xul
+++ b/im/content/account.xul
@@ -69,17 +69,16 @@
       <tab id="advancedTab" label="&account.advanced;"/>
     </tabs>
     <tabpanels id="panels" flex="1">
       <tabpanel orient="vertical" flex="1" style="overflow:auto;">
         <hbox id="passwordBox" equalsize="always" align="baseline">
           <label value="&account.password;" control="password" flex="1"/>
           <textbox id="password" flex="1" type="password"/>
         </hbox>
-        <checkbox id="rememberPassword" label="&account.rememberPassword;" hidden="true"/>
 
         <separator class="groove"/>
 
         <hbox id="aliasBox" equalsize="always" align="baseline">
           <label value="&account.alias;" control="alias" flex="1"/>
           <textbox id="alias" flex="1"/>
         </hbox>
 
--- a/im/content/accountWizard.js
+++ b/im/content/accountWizard.js
@@ -376,21 +376,23 @@ var accountWizard = {
 
     var label = document.getElementById("protoLabel").value;
     rows.appendChild(this.createSummaryRow(label, this.proto.name));
     this.username = this.getUsername();
     label = bundle.getString("accountUsername");
     rows.appendChild(this.createSummaryRow(label, this.username));
     if (!this.proto.noPassword) {
       this.password = this.getValue("password");
-      label = document.getElementById("passwordLabel").value;
-      var pass = "";
-      for (let i = 0; i < this.password.length; ++i)
-        pass += "*";
-      rows.appendChild(this.createSummaryRow(label, pass));
+      if (this.password) {
+        label = document.getElementById("passwordLabel").value;
+        var pass = "";
+        for (let i = 0; i < this.password.length; ++i)
+          pass += "*";
+        rows.appendChild(this.createSummaryRow(label, pass));
+      }
     }
     this.alias = this.getValue("alias");
     if (this.alias) {
       label = document.getElementById("aliasLabel").value;
       rows.appendChild(this.createSummaryRow(label, this.alias));
     }
 
 /* FIXME
@@ -434,17 +436,17 @@ var accountWizard = {
       let opt = this.prefs[i];
       let label = bundle.getFormattedString("accountColon", [opt.opt.label]);
       rows.appendChild(this.createSummaryRow(label, opt.value));
     }
   },
 
   createAccount: function aw_createAccount() {
     var acc = Services.accounts.createAccount(this.username, this.proto.id);
-    if (!this.proto.noPassword)
+    if (!this.proto.noPassword && this.password)
       acc.password = this.password;
     if (this.alias)
       acc.alias = this.alias;
     //FIXME: newMailNotification
 
     for (let i = 0; i < this.prefs.length; ++i) {
       let option = this.prefs[i];
       let opt = option.opt;
--- a/im/content/accountWizard.xul
+++ b/im/content/accountWizard.xul
@@ -93,16 +93,18 @@
   <wizardpage id="accountpassword" pageid="accountpassword" next="accountadvanced"
               label="&accountPasswordTitle.label;">
     <description>&accountPasswordInfo.label;</description>
     <separator/>
     <hbox id="passwordBox" align="baseline">
       <label value="&accountPasswordField.label;" control="password" id="passwordLabel"/>
       <textbox id="password" type="password"/>
     </hbox>
+    <separator/>
+    <description id="passwordManagerDescription">&accountPasswordManager.label;</description>
   </wizardpage>
 
   <wizardpage id="accountadvanced" pageid="accountadvanced" next="accountsummary"
               label="&accountAdvancedTitle.label;"
               onpageshow="accountWizard.showAdvanced();">
     <description>&accountAdvancedInfo.label;</description>
     <separator class="thin"/>
     <groupbox id="aliasGroupbox" class="collapsable"
--- a/im/content/accounts.js
+++ b/im/content/accounts.js
@@ -330,18 +330,17 @@ var gAccountManager = {
 
     let account = selectedItem.account;
     let activeCommandName =
       (this.isOffline || account.disconnected) ? "connect" : "disconnect";
     let activeCommandElt = document.getElementById("cmd_" + activeCommandName);
     let isCommandDisabled =
       (this.isOffline ||
        (account.disconnected &&
-        (account.connectionErrorReason == Ci.imIAccount.ERROR_UNKNOWN_PRPL ||
-         account.connectionErrorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD)));
+        account.connectionErrorReason == Ci.imIAccount.ERROR_UNKNOWN_PRPL));
 
     [[activeCommandElt, isCommandDisabled],
      [document.getElementById("cmd_moveup"), accountList.selectedIndex == 0],
      [document.getElementById("cmd_movedown"),
       accountList.selectedIndex == accountList.itemCount - 1]
     ].forEach(function (aEltArray) {
       let [elt, state] = aEltArray;
       if (state)
--- a/im/content/debug/fake/fake.js
+++ b/im/content/debug/fake/fake.js
@@ -333,17 +333,16 @@ function Account(aName, aProto)
 
   dump("account " + aName + " created\n");
 }
 Account.prototype = {
   __proto__: ClassInfo("imIAccount", "generic account object"),
   protocol: null,
   password: "",
   autoLogin: true,
-  rememberPassword: true,
   alias: "",
   proxyInfo: null,
   connectionStageMsg: "",
   connectionErrorReason: -1,
   timeOfNextReconnect: 0,
   timeOfLastConnect: new Date(),
   connectionErrorMessage: "",
   disconnecting: false,
--- a/im/content/preferences/privacy.js
+++ b/im/content/preferences/privacy.js
@@ -34,16 +34,17 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 var gPrivacyPane = {
   init: function ()
   {
     this.updateDisabledState();
+    this._initMasterPasswordUI();
   },
 
   updateDisabledState: function ()
   {
     let broadcaster = document.getElementById("idleReportingEnabled");
     if (document.getElementById("messenger.status.reportIdle").value) {
       broadcaster.removeAttribute("disabled");
       this.updateMessageDisabledState();
@@ -85,10 +86,117 @@ var gPrivacyPane = {
        // If launch also fails (probably because it's not implemented), let the
        // OS handler try to open the parent
        let uri = Services.io.newFileURI(parent);
        let protocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
                          getService(Ci.nsIExternalProtocolService);
        protocolSvc.loadUrl(uri);
       }
     }
+  },
+
+  /**
+   * Initializes master password UI: the "use master password" checkbox, selects
+   * the master password button to show, and enables/disables it as necessary.
+   * The master password is controlled by various bits of NSS functionality,
+   * so the UI for it can't be controlled by the normal preference bindings.
+   */
+  _initMasterPasswordUI: function ()
+  {
+    var noMP = !this._masterPasswordSet();
+
+    document.getElementById("changeMasterPassword").disabled = noMP;
+
+    document.getElementById("useMasterPassword").checked = !noMP;
+  },
+
+
+  /**
+   * Returns true if the user has a master password set and false otherwise.
+   */
+  _masterPasswordSet: function ()
+  {
+    const Cc = Components.classes, Ci = Components.interfaces;
+    var secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].
+                   getService(Ci.nsIPKCS11ModuleDB);
+    var slot = secmodDB.findSlotByName("");
+    if (slot) {
+      var status = slot.status;
+      var hasMP = status != Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED &&
+                  status != Ci.nsIPKCS11Slot.SLOT_READY;
+      return hasMP;
+    } else {
+      // XXX I have no bloody idea what this means
+      return false;
+    }
+  },
+
+
+  /**
+   * Enables/disables the master password button depending on the state of the
+   * "use master password" checkbox, and prompts for master password removal
+   * if one is set.
+   */
+  updateMasterPasswordButton: function ()
+  {
+    var checkbox = document.getElementById("useMasterPassword");
+    var button = document.getElementById("changeMasterPassword");
+    button.disabled = !checkbox.checked;
+
+    // unchecking the checkbox should try to immediately remove the master
+    // password, because it's impossible to non-destructively remove the master
+    // password used to encrypt all the passwords without providing it (by
+    // design), and it would be extremely odd to pop up that dialog when the
+    // user closes the prefwindow and saves his settings
+    if (!checkbox.checked)
+      this._removeMasterPassword();
+    else
+      this.changeMasterPassword();
+
+    this._initMasterPasswordUI();
+  },
+
+  /**
+   * Displays the "remove master password" dialog to allow the user to remove
+   * the current master password.  When the dialog is dismissed, master password
+   * UI is automatically updated.
+   */
+  _removeMasterPassword: function ()
+  {
+    const Cc = Components.classes, Ci = Components.interfaces;
+    var secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].
+                   getService(Ci.nsIPKCS11ModuleDB);
+    if (secmodDB.isFIPSEnabled) {
+      var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+                          getService(Ci.nsIPromptService);
+      var bundle = document.getElementById("bundlePreferences");
+      promptService.alert(window,
+                          bundle.getString("pw_change_failed_title"),
+                          bundle.getString("pw_change2empty_in_fips_mode"));
+    }
+    else {
+      document.documentElement.openSubDialog("chrome://mozapps/content/preferences/removemp.xul",
+                                             "", null);
+    }
+    this._initMasterPasswordUI();
+  },
+
+  /**
+   * Displays a dialog in which the master password may be changed.
+   */
+  changeMasterPassword: function ()
+  {
+    document.documentElement.openSubDialog("chrome://mozapps/content/preferences/changemp.xul",
+                                           "", null);
+    this._initMasterPasswordUI();
+  },
+
+  /**
+   * Shows the sites where the user has saved passwords and the associated
+   * login information.
+   */
+  showPasswords: function ()
+  {
+    document.documentElement.openWindow("Toolkit:PasswordManager",
+                                        "chrome://passwordmgr/content/passwordManager.xul",
+                                        "", null);
   }
 };
--- a/im/content/preferences/privacy.xul
+++ b/im/content/preferences/privacy.xul
@@ -58,16 +58,17 @@
                   onchange="gPrivacyPane.updateDisabledState();"/>
       <preference id="messenger.status.timeBeforeIdle"     name="messenger.status.timeBeforeIdle"     type="int"/>
       <preference id="messenger.status.awayWhenIdle"       name="messenger.status.awayWhenIdle"       type="bool"
                   onchange="gPrivacyPane.updateMessageDisabledState();"/>
       <preference id="messenger.status.defaultIdleAwayMessage" name="messenger.status.defaultIdleAwayMessage" type="wstring"/>
       <preference id="purple.logging.log_chats"            name="purple.logging.log_chats"            type="bool"/>
       <preference id="purple.logging.log_ims"              name="purple.logging.log_ims"              type="bool"/>
       <preference id="purple.logging.log_system"           name="purple.logging.log_system"           type="bool"/>
+      <preference id="pref.privacy.disable_button.view_passwords" name="pref.privacy.disable_button.view_passwords" type="bool"/>
     </preferences>
 
     <broadcaster id="idleReportingEnabled"/>
 
     <!-- Status -->
     <groupbox id="statusGroup">
       <caption label="&status.label;"/>
       <hbox align="center">
@@ -112,11 +113,37 @@
         <description control="openLogFolder"
                      flex="1">&logShowFolder.description;</description>
         <button id="openLogFolder" label="&logShowFolderButton.label;"
                 accesskey="&logShowFolderButton.accesskey;"
                 oncommand="gPrivacyPane.openLogFolder();"/>
       </hbox>
     </groupbox>
 
+    <!-- Passwords -->
+    <groupbox id="passwordsGroup" orient="vertical">
+      <caption label="&passwords.label;"/>
+
+      <description>&savedPasswords.intro;</description>
+      <hbox>
+        <spacer flex="1"/>
+        <button label="&savedPasswords.label;" accesskey="&savedPasswords.accesskey;"
+                oncommand="gPrivacyPane.showPasswords();"
+                preference="pref.privacy.disable_button.view_passwords"/>
+      </hbox>
+
+      <separator class="thin"/>
+
+      <description>&masterPassword.intro;</description>
+      <hbox>
+        <checkbox id="useMasterPassword" flex="1"
+                  label="&useMasterPassword.label;" accesskey="&useMasterPassword.accesskey;"
+                  oncommand="gPrivacyPane.updateMasterPasswordButton();"/>
+
+        <button id="changeMasterPassword"
+                label="&changeMasterPassword.label;" accesskey="&changeMasterPassword.accesskey;"
+                oncommand="gPrivacyPane.changeMasterPassword();"/>
+      </hbox>
+    </groupbox>
+
   </prefpane>
 
 </overlay>
--- a/im/locales/en-US/chrome/instantbird/account.dtd
+++ b/im/locales/en-US/chrome/instantbird/account.dtd
@@ -1,13 +1,12 @@
 <!ENTITY accountWindow.title           "Account properties">
 <!ENTITY accountWindow.width           "300">
 <!ENTITY account.general               "General">
 <!ENTITY account.advanced              "Advanced Options">
 <!ENTITY account.name                  "Username:">
 <!ENTITY account.password              "Password:">
-<!ENTITY account.rememberPassword      "remember password">
 <!ENTITY account.alias                 "Alias:">
 <!ENTITY account.newMailNotification   "Notify on new Mail">
 <!ENTITY account.autojoin              "Auto-Joined Channels:">
 <!ENTITY account.proxySettings.caption "Proxy Settings:">
 <!ENTITY account.proxySettings.change.label     "Change…">
 <!ENTITY account.proxySettings.change.accessKey "C">
--- a/im/locales/en-US/chrome/instantbird/accountWizard.dtd
+++ b/im/locales/en-US/chrome/instantbird/accountWizard.dtd
@@ -9,16 +9,17 @@
 <!ENTITY accountProtocolGetMore.label "Get more…">
 
 <!ENTITY accountUsernameTitle.label   "Username">
 <!ENTITY accountUsernameDuplicate.label "This account is already configured!">
 
 <!ENTITY accountPasswordTitle.label   "Password">
 <!ENTITY accountPasswordInfo.label    "Please enter your password in the box below.">
 <!ENTITY accountPasswordField.label   "Password:">
+<!ENTITY accountPasswordManager.label "The password entered here will be stored in the Password Manager. Leave this box empty if you want to be prompted for your password each time this account is connected.">
 
 <!ENTITY accountAdvancedTitle.label   "Advanced Options">
 <!ENTITY accountAdvancedInfo.label    "Feel free to skip this step if you want to.">
 <!ENTITY accountAdvanced.newMailNotification.label   "Notify on new Mail">
 <!ENTITY accountAliasGroupbox.caption "Local Alias">
 <!ENTITY accountAliasField.label      "Alias:">
 <!ENTITY accountAliasInfo.label       "This will only be displayed in your conversations when you talk, remote buddies won't see it.">
 <!ENTITY accountProxySettings.caption "Proxy Settings">
--- a/im/locales/en-US/chrome/instantbird/accounts.properties
+++ b/im/locales/en-US/chrome/instantbird/accounts.properties
@@ -2,17 +2,17 @@
 protoOptions=%S Options
 accountUsername=Username:
 accountColon=%S:
 accountUsernameInfo=Please enter the username for your %S account.
 accountUsernameInfoWithDescription=Please enter the username (%S) for your %S account.
 
 account.connection.error=Error: %S
 account.connection.errorUnknownPrpl= No '%S' protocol plugin.
-account.connection.errorMissingPassword=A password is required to connect this account.
+account.connection.errorEnteringPasswordRequired=Entering a password is required to connect this account.
 account.connection.errorCrashedAccount=A crash occurred while connecting this account.
 account.connection.progress=Connecting: %S…
 account.connecting=Connecting…
 account.connectedForSeconds=Connected for a few seconds.
 account.connectedForDouble=Connected for %S %S and %S %S.
 account.connectedForSingle=Connected for about %S %S.
 account.reconnectInDouble=Reconnection in %S %S and %S %S.
 account.reconnectInSingle=Reconnection in %S %S.
--- a/im/locales/en-US/chrome/instantbird/preferences/privacy.dtd
+++ b/im/locales/en-US/chrome/instantbird/preferences/privacy.dtd
@@ -14,8 +14,19 @@
 
 <!ENTITY  logConversations.label        "Keep records of my conversations">
 <!ENTITY  logConversations.accesskey    "r">
 <!ENTITY  logSystem.label               "Record connections, disconnections and status changes">
 <!ENTITY  logSystem.accesskey           "c">
 <!ENTITY  logShowFolder.description     "Open the folder containing the record files">
 <!ENTITY  logShowFolderButton.label     "Show Log Folder…">
 <!ENTITY  logShowFolderButton.accesskey "L">
+
+<!-- Passwords, see mail/chrome/messenger/preferences/security.dtd -->
+<!ENTITY  passwords.label               "Passwords">
+<!ENTITY savedPasswords.intro           "&brandShortName; can remember passwords for all of your accounts.">
+<!ENTITY useMasterPassword.label        "Use a master password">
+<!ENTITY useMasterPassword.accesskey    "U">
+<!ENTITY masterPassword.intro           "A Master Password protects all your passwords, but you must enter it once per session.">
+<!ENTITY changeMasterPassword.label     "Change Master Password…">
+<!ENTITY changeMasterPassword.accesskey "C">
+<!ENTITY savedPasswords.label           "Saved Passwords…">
+<!ENTITY savedPasswords.accesskey       "S">
deleted file mode 100644
--- a/im/locales/en-US/chrome/instantbird/preferences/security.dtd
+++ /dev/null
@@ -1,42 +0,0 @@
-<!ENTITY  warnAddonInstall.label        "Warn me when sites try to install add-ons">
-<!ENTITY  warnAddonInstall.accesskey    "W">
-
-<!-- LOCALIZATION NOTE (blockWebForgeries.label, blockAttackSites.label):
-  The methods by which forged (phished) and attack sites will be detected by
-  phishing providers will vary from human review to machine-based heuristics to a
-  combination of both, so it's important that these strings and
-  useDownloadedList.label convey the meaning "reported" (and not something like
-  "known").
--->
-<!ENTITY  blockAttackSites.label     "Block reported attack sites">
-<!ENTITY  blockAttackSites.accesskey "k">
-
-<!ENTITY  blockWebForgeries.label     "Block reported web forgeries">
-<!ENTITY  blockWebForgeries.accesskey "B">
-
-<!ENTITY  addonExceptions.label         "Exceptions…">
-<!ENTITY  addonExceptions.accesskey     "E">
-
-
-<!ENTITY  passwords.label               "Passwords">
-
-<!ENTITY  rememberPasswords.label       "Remember passwords for sites">
-<!ENTITY  rememberPasswords.accesskey   "R">
-<!ENTITY  passwordExceptions.label      "Exceptions…">
-<!ENTITY  passwordExceptions.accesskey  "x">
-
-<!ENTITY  useMasterPassword.label        "Use a master password">
-<!ENTITY  useMasterPassword.accesskey    "U">
-<!ENTITY  changeMasterPassword.label     "Change Master Password…">
-<!ENTITY  changeMasterPassword.accesskey "M">
-
-<!ENTITY  savedPasswords.label            "Saved Passwords…">
-<!ENTITY  savedPasswords.accesskey        "P">
-
-
-<!ENTITY  warnings.label                "Warning Messages">
-
-<!ENTITY  chooseWarnings.label          "Choose which warning messages you want to see while browsing the web">
-
-<!ENTITY  warningSettings.label         "Settings…">
-<!ENTITY  warningSettings.accesskey     "S">
--- a/im/locales/jar.mn
+++ b/im/locales/jar.mn
@@ -32,12 +32,11 @@
 	locale/@AB_CD@/instantbird/preferences/applications.dtd (%chrome/instantbird/preferences/applications.dtd)
 	locale/@AB_CD@/instantbird/preferences/colors.dtd       (%chrome/instantbird/preferences/colors.dtd)
 	locale/@AB_CD@/instantbird/preferences/connection.dtd   (%chrome/instantbird/preferences/connection.dtd)
 	locale/@AB_CD@/instantbird/preferences/content.dtd      (%chrome/instantbird/preferences/content.dtd)
 	locale/@AB_CD@/instantbird/preferences/main.dtd         (%chrome/instantbird/preferences/main.dtd)
 	locale/@AB_CD@/instantbird/preferences/preferences.dtd  (%chrome/instantbird/preferences/preferences.dtd)
 	locale/@AB_CD@/instantbird/preferences/preferences.properties (%chrome/instantbird/preferences/preferences.properties)
 	locale/@AB_CD@/instantbird/preferences/privacy.dtd            (%chrome/instantbird/preferences/privacy.dtd)
-	locale/@AB_CD@/instantbird/preferences/security.dtd           (%chrome/instantbird/preferences/security.dtd)
 	locale/@AB_CD@/instantbird/preferences/tabs.dtd               (%chrome/instantbird/preferences/tabs.dtd)
 	locale/@AB_CD@/instantbird/preferences/themes.dtd             (%chrome/instantbird/preferences/themes.dtd)
 	locale/@AB_CD@/instantbird/preferences/themes.properties      (%chrome/instantbird/preferences/themes.properties)