Bug 1550487 - OTR: Implement per-account preferences UI. r=mkmelin
authorKai Engert <kaie@kuix.de>
Tue, 04 Jun 2019 22:57:54 +0200
changeset 35768 0146e4f2fdaf0c60393d24808e76047e0b7ee32a
parent 35767 653cffcb9c305b8a3db751210cc84c991b9298c8
child 35769 fe88d825d3c671d9b24f56f1477e30302c7cebca
push id392
push userclokep@gmail.com
push dateMon, 02 Sep 2019 20:17:19 +0000
reviewersmkmelin
bugs1550487
Bug 1550487 - OTR: Implement per-account preferences UI. r=mkmelin Contains UI code by Alessandro Castellani <alessandro@thunderbird.net> Differential Revision: https://phabricator.services.mozilla.com/D32837
chat/content/jar.mn
chat/content/otr-add-fingerprint.xul
chat/content/otr-auth.xul
chat/content/otr-finger.js
chat/content/otr-finger.xul
chat/content/otr-generate-key.js
chat/content/otr-generate-key.xul
chat/content/otr/am-im-otr.ftl
chat/content/otr/auth.ftl
chat/content/otr/chat.ftl
chat/content/otr/finger.ftl
chat/content/otr/generate-key.ftl
chat/content/otr/otr.ftl
chat/content/otr/otrUI.ftl
chat/locales/jar.mn
chat/modules/OTR.jsm
chat/modules/OTRLib.jsm
chat/modules/OTRUI.jsm
mail/components/im/content/am-im.js
mail/components/im/content/am-im.xul
mail/components/im/imIncomingServer.js
mail/locales/en-US/messenger/preferences/am-im.ftl
mail/locales/jar.mn
--- a/chat/content/jar.mn
+++ b/chat/content/jar.mn
@@ -11,11 +11,13 @@ chat.jar:
 	content/chat/chat-account-richlistitem.js
 *	content/chat/imtooltip.xml
 	content/chat/conversation-browser.js
 	content/chat/conv.html
 	content/chat/otr-add-fingerprint.js
 	content/chat/otr-add-fingerprint.xul
 	content/chat/otr-auth.js
 	content/chat/otr-auth.xul
+	content/chat/otr-finger.js
+	content/chat/otr-finger.xul
 	content/chat/otr-generate-key.js
 	content/chat/otr-generate-key.xul
 	content/chat/otrWorker.js
--- a/chat/content/otr-add-fingerprint.xul
+++ b/chat/content/otr-add-fingerprint.xul
@@ -16,17 +16,17 @@
         xmlns:html="http://www.w3.org/1999/xhtml"
         buttons="accept,cancel"
         buttondisabledaccept="true">
 
   <linkset>
     <html:link rel="localization" href="messenger/otr/add-finger.ftl"/>
   </linkset>
 
-  <script type="application/javascript" src="chrome://chat/content/otr-add-fingerprint.js"/>
+  <script src="chrome://chat/content/otr-add-fingerprint.js"/>
   <vbox flex="1">
     <label data-l10n-id="otr-add-finger-tooltip" control="name" flex="1"/>
     <hbox id="fingerBox" align="baseline" flex="1">
       <label data-l10n-id="otr-add-finger-fingerprint" control="name"/>
       <textbox id="finger" oninput="otrAddFinger.oninput(this)" flex="1"/>
     </hbox>
   </vbox>
 </dialog>
--- a/chat/content/otr-auth.xul
+++ b/chat/content/otr-auth.xul
@@ -17,17 +17,17 @@
   onload="otrAuth.onload()"
   buttons="accept,cancel,help"
   buttondisabledaccept="true">
 
   <linkset>
     <html:link rel="localization" href="messenger/otr/auth.ftl"/>
   </linkset>
 
-  <script type="application/javascript" src="chrome://chat/content/otr-auth.js" />
+  <script src="chrome://chat/content/otr-auth.js" />
 
   <groupbox id="how" hidden="true">
     <label><html:h4 data-l10n-id="auth-how"/></label>
     <menulist id="howOption" oncommand="otrAuth.how();">
       <menupopup>
         <menuitem data-l10n-id="auth-questionAndAnswer-label" value="questionAndAnswer" />
         <menuitem data-l10n-id="auth-sharedSecret-label" value="sharedSecret" />
         <menuitem data-l10n-id="auth-manualVerification-label" value="manualVerification" />
new file mode 100644
--- /dev/null
+++ b/chat/content/otr-finger.js
@@ -0,0 +1,126 @@
+/* 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 {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
+const {OTR} = ChromeUtils.import("resource:///modules/OTR.jsm");
+const {LocalizationSync} =
+  ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
+
+const syncL10n = new LocalizationSync([
+  "messenger/otr/finger.ftl",
+]);
+
+var [account] = window.arguments;
+
+var gFingers;
+var fingerTreeView = {
+  selection: null,
+  rowCount: 0,
+  setTree(tree) {},
+  getImageSrc(row, column) {},
+  getProgressMode(row, column) {},
+  getCellValue(row, column) {},
+  getCellText(row, column) {
+    let finger = gFingers[row];
+    switch (column.id) {
+      case "verified": {
+        let id = finger.trust ? "finger-yes" : "finger-no";
+        return syncL10n.formatValue(id);
+      }
+      default:
+        return finger[column.id] || "";
+    }
+  },
+  isSeparator(index) { return false; },
+  isSorted() { return false; },
+  isContainer(index) { return false; },
+  cycleHeader(column) {},
+  getRowProperties(row) { return ""; },
+  getColumnProperties(column) { return ""; },
+  getCellProperties(row, column) { return ""; },
+};
+
+function getSelections(tree) {
+  let selections = [];
+  let selection = tree.view.selection;
+  if (selection) {
+    let count = selection.getRangeCount();
+    let min = {};
+    let max = {};
+    for (let i = 0; i < count; i++) {
+      selection.getRangeAt(i, min, max);
+      for (let k = min.value; k <= max.value; k++) {
+        if (k != -1)
+          selections[selections.length] = k;
+      }
+    }
+  }
+  return selections;
+}
+
+var fingerTree;
+var otrFinger = {
+  onload() {
+    fingerTree = document.getElementById("fingerTree");
+    gFingers = OTR.knownFingerprints(account);
+    fingerTreeView.rowCount = gFingers.length;
+    fingerTree.view = fingerTreeView;
+  },
+
+  select() {
+    let selections = getSelections(fingerTree);
+    document.getElementById("remove").disabled = !selections.length;
+  },
+
+  remove() {
+    fingerTreeView.selection.selectEventsSuppressed = true;
+    // mark fingers for removal
+    getSelections(fingerTree).forEach(function(sel) {
+      gFingers[sel].purge = true;
+    });
+    this.commonRemove();
+  },
+
+  removeAll() {
+    let confirmAllTitle = syncL10n.formatValue("finger-remove-all-title");
+    let confirmAllText = syncL10n.formatValue("finger-remove-all-message");
+
+    let buttonPressed =
+      Services.prompt.confirmEx(window, confirmAllTitle, confirmAllText,
+        Services.prompt.BUTTON_POS_1_DEFAULT +
+          Services.prompt.STD_OK_CANCEL_BUTTONS +
+          Services.prompt.BUTTON_DELAY_ENABLE,
+        0, 0, 0, null, {});
+    if (buttonPressed != 0) {
+      return;
+    }
+
+    for (let j = 0; j < gFingers.length; j++) {
+      gFingers[j].purge = true;
+    }
+    this.commonRemove();
+  },
+
+  commonRemove() {
+    // OTR.forgetFingerprints will null out removed fingers.
+    let removalComplete = OTR.forgetFingerprints(gFingers);
+    for (let j = 0; j < gFingers.length; j++) {
+      if (gFingers[j] === null) {
+        let k = j;
+        while (k < gFingers.length && gFingers[k] === null) {
+          k++;
+        }
+        gFingers.splice(j, k - j);
+        fingerTreeView.rowCount -= k - j;
+        fingerTree.rowCountChanged(j, j - k);  // negative
+      }
+    }
+    fingerTreeView.selection.selectEventsSuppressed = false;
+    if (!removalComplete) {
+      let infoTitle = syncL10n.formatValue("finger-subset-title");
+      let infoText = syncL10n.formatValue("finger-subset-message");
+      Services.prompt.alert(window, infoTitle, infoText);
+    }
+  },
+};
new file mode 100644
--- /dev/null
+++ b/chat/content/otr-finger.xul
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css" ?>
+
+<!DOCTYPE dialog>
+
+<dialog
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+  xmlns:html="http://www.w3.org/1999/xhtml"
+  id="otr-fingerwindow"
+  data-l10n-id="otr-finger"
+  data-l10n-attrs="buttonlabelaccept"
+  onload="otrFinger.onload()"
+  buttons="accept">
+
+  <linkset>
+    <html:link rel="localization" href="messenger/otr/finger.ftl"/>
+  </linkset>
+
+  <script src="chrome://chat/content/otr-finger.js"/>
+
+  <label data-l10n-id="finger-intro" />
+  <separator class="thin"/>
+  <vbox id="fingerprints" class="contentPane" flex="1">
+    <tree id="fingerTree"
+          flex="1"
+          width="800"
+          style="height: 20em;"
+          onselect="otrFinger.select()">
+      <treecols>
+        <treecol id="screenname" data-l10n-id="finger-screenName" flex="20" />
+        <splitter class="tree-splitter"/>
+        <treecol id="fingerprint" data-l10n-id="finger-fingerprint" flex="120" />
+        <splitter class="tree-splitter"/>
+        <treecol id="verified" data-l10n-id="finger-verified" flex="10" />
+        <splitter class="tree-splitter"/>
+      </treecols>
+      <treechildren/>
+    </tree>
+    <separator class="thin"/>
+    <hbox>
+      <button id="remove"
+              data-l10n-id="finger-remove"
+              disabled="true"
+              oncommand="otrFinger.remove()"/>
+      <button id="remove-all"
+              data-l10n-id="finger-remove-all"
+              oncommand="otrFinger.removeAll()"/>
+    </hbox>
+  </vbox>
+</dialog>
--- a/chat/content/otr-generate-key.js
+++ b/chat/content/otr-generate-key.js
@@ -1,28 +1,47 @@
 /* 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 {
   XPCOMUtils,
   l10nHelper,
 } = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
+const {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
 const {OTR} = ChromeUtils.import("resource:///modules/OTR.jsm");
 
 var otrPriv = {
 
   async onload() {
     let args = window.arguments[0].wrappedJSObject;
     let priv = document.getElementById("priv");
 
+    let protocolNameToShow;
+
+    // args.protocol is the normalized protocol name.
+    // However, we don't want to show normalized names like "jabber",
+    // but want to show the terms used in the UI like "XMPP".
+
+    let protocols = Services.core.getProtocols();
+    while (protocols.hasMoreElements()) {
+      let protocol = protocols.getNext();
+      if (protocol.normalizedName === args.protocol) {
+        protocolNameToShow = protocol.name;
+        break;
+      }
+    }
+
+    if (!protocolNameToShow) {
+      protocolNameToShow = args.protocol;
+    }
+
     let text = await document.l10n.formatValue(
-      "otr-genkey-account", {name: args.account, protocol: OTR.protocolName(args.protocol)});
+      "otr-genkey-account", {name: args.account, protocol: protocolNameToShow});
     priv.textContent = text;
-console.log("genkey: " + text);
 
     OTR.generatePrivateKey(args.account, args.protocol).then(function() {
       document.documentElement.getButton("accept").disabled = false;
       document.documentElement.acceptDialog();
     }).catch(async function(err) {
       priv.textContent = await document.l10n.formatValue(
           "otr-genkey-failed", {error: String(err)});
       document.documentElement.getButton("accept").disabled = false;
--- a/chat/content/otr-generate-key.xul
+++ b/chat/content/otr-generate-key.xul
@@ -17,12 +17,12 @@
   buttons="accept"
   onload="otrPriv.onload()"
   buttondisabledaccept="true">
 
   <linkset>
     <html:link rel="localization" href="messenger/otr/generate-key.ftl"/>
   </linkset>
 
-  <script type="application/javascript" src="chrome://chat/content/otr-generate-key.js" />
+  <script src="chrome://chat/content/otr-generate-key.js" />
   <description id="priv" style="width: 300px; white-space: pre-wrap;"></description>
 
 </dialog>
new file mode 100644
--- /dev/null
+++ b/chat/content/otr/am-im-otr.ftl
@@ -0,0 +1,21 @@
+# 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/.
+
+account-encryption =
+    .label = End-to-end Encryption
+account-otr-label = Off-the-Record Messaging (OTR)
+account-otr-description = { -brand-short-name } supports end-to-end encryption of one-to-one conversations to prevent third parties from eavesdropping on a conversation. End-to-end encryption is available when your conversation partner also uses software that supports OTR.
+otr-encryption-title = Verified Encryption
+otr-encryption-caption = To enable others to verify your identity in OTR chats, share your own OTR fingerprint using an outside (out-of-band) communication channel.
+otr-fingerprint-label = Your Fingerprint:
+view-fingerprint-button =
+    .label = Manage Fingerprints Of Contacts
+    .accesskey = F
+otr-settings-title = OTR Settings
+otr-requireEncryption =
+    .label = Require end-to-end encryption for one-to-one conversations
+otr-verifyNudge =
+    .label = Always remind me to verify an unverified contact
+
+otr-notYetAvailable = not yet available
--- a/chat/content/otr/auth.ftl
+++ b/chat/content/otr/auth.ftl
@@ -13,17 +13,17 @@ auth-title = Verify the identity of { $n
 # Variables:
 #   $own_name (String) - the user's own screen name
 auth-your-fp-value = Fingerprint for you, { $own_name }:
 
 # Variables:
 #   $their_name (String) - the screen name of a chat contact
 auth-their-fp-value = Purported fingerprint for { $their_name }:
 
-auth-help = Verifying a contact's identity helps ensure that the person you are talking to is who they claim to be.
+auth-help = Verifying a contact's identity helps ensure that the conversation is truly private, making it very difficult for a third party to eavesdrop or manipulate the conversation.
 auth-helpTitle = Verification help
 
 auth-questionReceived = This is the question asked by your contact:
 
 auth-yes =
     .label = Yes
 
 auth-no =
@@ -39,21 +39,21 @@ auth-manualVerification-label =
     .label = { auth-manualVerification }
 
 auth-questionAndAnswer-label =
     .label = { auth-questionAndAnswer }
 
 auth-sharedSecret-label =
     .label = { auth-sharedSecret }
 
-auth-manualInstruction = To verify the fingerprint, contact your conversation partner via some other authenticated channel, such as the telephone or GPG-signed email. Both conversation partners should tell the other person their fingerprint. If the fingerprint matches, you should indicate in the dialog below that you have verified the fingerprint.
+auth-manualInstruction = To verify the fingerprint, contact your intended conversation partner via some other authenticated channel, such as OpenPGP-signed email or over the phone. You should both tell each other your fingerprints. If the fingerprint matches, you should indicate in the dialog below that you have verified the fingerprint.
 
 auth-how = How would you like to verify your contact's identity?
 
 auth-qaInstruction = To verify their identity, pick a question whose answer is known only to you and your contact. Enter this question and answer, then wait for your contact to enter the answer as well. If the answers do not match, then you may be talking to an imposter.
 
-auth-secretInstruction = To verify their identity, pick a secret known only to you and your contact. Enter this secret, then wait for your contact to enter it as well. If the secrets do not match, then you may be talking to an imposter.
+auth-secretInstruction = To verify their identity, pick a secret known only to you and your contact. Don't use the same Internet connection to exchange the secret. Enter this secret, then wait for your contact to enter it as well. If the secrets do not match, then you may be talking to an imposter.
 
-auth-question = Enter question here:
+auth-question = Enter a question:
 
-auth-answer = Enter secret answer here (case sensitive):
+auth-answer = Enter the secret answer (case sensitive):
 
-auth-secret = Enter secret here:
+auth-secret = Enter the secret:
--- a/chat/content/otr/chat.ftl
+++ b/chat/content/otr/chat.ftl
@@ -1,19 +1,19 @@
 # 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/.
 
 state-label = Encryption Status:
 
-start-text = Start private conversation
+start-text = Start an encrypted conversation
 
 start-label =
     .label = { start-text }
 
 start-tooltip =
     .tooltiptext = { start-text }
 
 end-label =
-    .label = End private conversation
+    .label = End the encrypted conversation
 
 auth-label =
     .label = Verify your contact's identity
new file mode 100644
--- /dev/null
+++ b/chat/content/otr/finger.ftl
@@ -0,0 +1,31 @@
+# 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/.
+
+otr-finger =
+    .buttonlabelaccept = Close
+    .title = Previously seen OTR fingerprints
+
+finger-intro = A list of OTR key fingerprints from encrypted chats with your conversation partners.
+
+finger-screenName =
+    .label = Contact
+finger-verified =
+    .label = Verification Status
+finger-fingerprint =
+    .label = Fingerprint
+
+finger-remove =
+    .label = Remove Selected
+
+finger-remove-all =
+    .label = Remove All
+
+finger-yes = Verified
+finger-no = Unverified
+
+finger-subset-title = Removing fingerprints
+finger-subset-message = At least one fingerprint couldn't be removed, because you have an active conversation with the respective conversation partner.
+
+finger-remove-all-title = Removing all fingerprints
+finger-remove-all-message = Are you sure you want to remove all previously seen fingerprints? As a consequence, all previous OTR identity verifications will be lost. This operation cannot be undone.
--- a/chat/content/otr/generate-key.ftl
+++ b/chat/content/otr/generate-key.ftl
@@ -4,13 +4,13 @@
 
 otr-generate-key =
     .title = Generating private key
     .buttonlabelaccept = Done
 
 # Variables:
 #   $name (String) - the name of the user's own chat account
 #   $protocol (String) - the chat communication protocol used by that account
-otr-genkey-account = Generating private key for { $name }({ $protocol }) …
+otr-genkey-account = Generating private key for { $name } ({ $protocol }) …
 
 # Variables:
 #   $error (String) - contains an error message that describes the cause of the failure
 otr-genkey-failed = Generating key failed: { $error }
--- a/chat/content/otr/otr.ftl
+++ b/chat/content/otr/otr.ftl
@@ -6,17 +6,17 @@
 #   $name (String) - the screen name of a chat contact person
 msgevent-encryption_required_part1 = You attempted to send an unencrypted message to { $name }. As a policy, unencrypted messages are not allowed.
 
 msgevent-encryption_required_part2 = Attempting to start a private conversation. Your message will be retransmitted when the private conversation starts.
 msgevent-encryption_error = An error occurred when encrypting your message. The message was not sent.
 
 # Variables:
 #   $name (String) - the screen name of a chat contact person
-msgevent-connection_ended = { $name } has already closed their private connection to you. Your message was not sent. Either end your private conversation, or restart it.
+msgevent-connection_ended = { $name } has already closed their encrypted connection to you. To avoid that you accidentally send a message without encryption, your message was not sent. Please end your encrypted conversation, or restart it.
 
 # Variables:
 #   $name (String) - the screen name of a chat contact person
 msgevent-setup_error = An error occured while setting up a private conversation with { $name }.
 
 # Do not translate 'OTR' (name of an encryption protocol)
 msgevent-msg_reflected = You are receiving your own OTR messages. You are either trying to talk to yourself, or someone is reflecting your messages back at you.
 
@@ -42,17 +42,17 @@ msgevent-rcvdmsg_malformed = We received
 msgevent-log_heartbeat_rcvd = Heartbeat received from { $name }.
 
 # A Heartbeat is a technical message used to keep a connection alive.
 # Variables:
 #   $name (String) - the screen name of a chat contact person
 msgevent-log_heartbeat_sent = Heartbeat sent to { $name }.
 
 # Do not translate 'OTR' (name of an encryption protocol)
-msgevent-rcvdmsg_general_err = An OTR error occured.
+msgevent-rcvdmsg_general_err = An unexpected error occurred while trying to protect your conversation using OTR.
 
 # Variables:
 #   $name (String) - the screen name of a chat contact person
 #   $msg (string) - the message that was received.
 msgevent-rcvdmsg_unencrypted = The following message received from { $name } was not encrypted: { $msg }
 
 # Do not translate 'OTR' (name of an encryption protocol)
 # Variables:
@@ -64,39 +64,33 @@ msgevent-rcvdmsg_unrecognized = We recei
 msgevent-rcvdmsg_for_other_instance = { $name } has sent a message intended for a different session. If you are logged in multiple times, another session may have received the message.
 
 # Variables:
 #   $name (String) - the screen name of a chat contact person
 context-gone_secure_private = Private conversation with { $name } started.
 
 # Variables:
 #   $name (String) - the screen name of a chat contact person
-context-gone_secure_unverified = Private conversation with { $name } started. However, their identity has not been verified.
+context-gone_secure_unverified = Encrypted, but unverified conversation with { $name } started.
 
 # Variables:
 #   $name (String) - the screen name of a chat contact person
-context-still_secure = Successfully refreshed the private conversation with { $name }.
+context-still_secure = Successfully refreshed the encrypted conversation with { $name }.
 
-error-enc = Error occurred encrypting message.
+error-enc = An error occurred while encrypting the message.
 
 # Variables:
 #   $name (String) - the screen name of a chat contact person
 error-not_priv = You sent encrypted data to { $name }, who wasn't expecting it.
 
 error-unreadable = You transmitted an unreadable encrypted message.
 error-malformed = You transmitted a malformed data message.
 
 resent = [resent]
 
 # Variables:
 #   $name (String) - the screen name of a chat contact person
-tlv-disconnected = { $name } has ended their private conversation with you; you should do the same.
+tlv-disconnected = { $name } has ended their encrypted conversation with you; you should do the same.
 
 # Do not translate "Off-the-Record" and "OTR" which is the name of an encryption protocol
 # Variables:
 #   $name (String) - the screen name of a chat contact person
-query-msg = { $name } has requested an Off-the-Record (OTR) private conversation. However, you do not have a plugin to support that. See https://en.wikipedia.org/wiki/Off-the-Record_Messaging for more information.
-
-trust-unused = Unused
-trust-not_private = Not Private
-trust-unverified = Unverified
-trust-private = Private
-trust-finished = Finished
+query-msg = { $name } has requested an Off-the-Record (OTR) encrypted conversation. However, you do not have a plugin to support that. See https://en.wikipedia.org/wiki/Off-the-Record_Messaging for more information.
--- a/chat/content/otr/otrUI.ftl
+++ b/chat/content/otr/otrUI.ftl
@@ -1,14 +1,14 @@
 # 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/.
 
-start-label = Start private conversation
-refresh-label = Refresh private conversation
+start-label = Start an encrypted conversation
+refresh-label = Refresh the encrypted conversation
 auth-label = Verify your contact's identity
 reauth-label = Reverify your contact's identity
 
 auth-cancel = Cancel
 auth-cancelAccessKey = C
 
 auth-error = An error occurred while verifying your contact's identity.
 auth-success = Verifying your contact's identity completed successfully.
@@ -19,43 +19,47 @@ auth-waiting = Waiting for contact to complete verification …
 finger-verify = Verify
 finger-verify-accessKey = V
 
 # Do not translate 'OTR' (name of an encryption protocol)
 buddycontextmenu-label = Add Contact's OTR Fingerprint
 
 # Variables:
 #   $name (String) - the screen name of a chat contact person
-alert-start = Attempting to start a private conversation with { $name }.
+alert-start = Attempting to start an encrypted conversation with { $name }.
 
 # Variables:
 #   $name (String) - the screen name of a chat contact person
-alert-refresh = Attempting to refresh the private conversation with { $name }.
+alert-refresh = Attempting to refresh the encrypted conversation with { $name }.
 
 # Variables:
 #   $name (String) - the screen name of a chat contact person
-alert-gone_insecure = Private conversation with { $name } ended.
+alert-gone_insecure = The encrypted conversation with { $name } ended.
 
 # Variables:
 #   $name (String) - the screen name of a chat contact person
 finger-unseen = The identity of { $name } has not been verified yet. Casual eavesdropping is not possible, but with some effort someone could be listening in. You should verify this contact's identity.
 
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+finger-seen={ $name } is contacting you from an unrecognized computer. Casual eavesdropping is not possible, but with some effort someone could be listening in. You should verify this contact's identity.
+
 state-not_private = The current conversation is not private.
 
 # Variables:
 #   $name (String) - the screen name of a chat contact person
-state-unverified = The current conversation is private but the identity of { $name } has not been verified.
+state-unverified = The current conversation is encrypted but the identity of { $name } has not been verified.
 
 # Variables:
 #   $name (String) - the screen name of a chat contact person
-state-private = The current conversation is private and the identity of { $name } has been verified.
+state-private = The identity of { $name } has been verified. The current conversation is encrypted and private.
 
 # Variables:
 #   $name (String) - the screen name of a chat contact person
-state-finished = { $name } has ended their private conversation with you; you should do the same.
+state-finished = { $name } has ended their encrypted conversation with you; you should do the same.
 
 state-not_private-label = Insecure
 state-unverified-label = Unverified
 state-private-label = Private
 state-finished-label = Finished
 
 # Variables:
 #   $name (String) - the screen name of a chat contact person
--- a/chat/locales/jar.mn
+++ b/chat/locales/jar.mn
@@ -17,16 +17,8 @@
 	locale/@AB_CD@/chat/irc.properties	(%irc.properties)
 	locale/@AB_CD@/chat/logger.properties	(%logger.properties)
 	locale/@AB_CD@/chat/matrix.properties	(%matrix.properties)
 	locale/@AB_CD@/chat/skype.properties	(%skype.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)
 	locale/@AB_CD@/chat/yahoo.properties	(%yahoo.properties)
-#	locale/@AB_CD@/chat/otr-auth.dtd	(%otr-auth.dtd)
-#	locale/@AB_CD@/chat/otr-auth.properties	(%otr-auth.properties)
-#	locale/@AB_CD@/chat/otr-add-finger.dtd	(%otr-add-finger.dtd)
-#	locale/@AB_CD@/chat/otr-add-finger.properties	(%otr-add-finger.properties)
-#	locale/@AB_CD@/chat/otr.properties	(%otr.properties)
-#	locale/@AB_CD@/chat/otr-generate-key.dtd	(%otr-generate-key.dtd)
-#	locale/@AB_CD@/chat/otr-generate-key.properties	(%otr-generate-key.properties)
-#	locale/@AB_CD@/chat/otrUI.properties	(%otrUI.properties)
--- a/chat/modules/OTR.jsm
+++ b/chat/modules/OTR.jsm
@@ -4,17 +4,17 @@
 
 const { LocalizationSync } =
   ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
 const {BasePromiseWorker} = ChromeUtils.import("resource://gre/modules/PromiseWorker.jsm");
 const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 const {ctypes} = ChromeUtils.import("resource://gre/modules/ctypes.jsm");
 const {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
 const {CLib} = ChromeUtils.import("resource:///modules/CLib.jsm");
-const {OTRLib} = ChromeUtils.import("resource:///modules/OTRLib.jsm");
+const {OTRLibLoader} = ChromeUtils.import("resource:///modules/OTRLib.jsm");
 var workerPath = "chrome://chat/content/otrWorker.js";
 const {OTRHelpers} = ChromeUtils.import("resource:///modules/OTRHelpers.jsm");
 
 const syncL10n = new LocalizationSync([
   "messenger/otr/otr.ftl",
 ]);
 
 function _str(id) {
@@ -54,31 +54,16 @@ function trustFingerprint(fingerprint) {
 // they are, 0 if you think they aren't, -1 if you're not sure.
 function isOnline(conv) {
   let ret = -1;
   if (conv.buddy)
     ret = conv.buddy.online ? 1 : 0;
   return ret;
 }
 
-// Use the protocol name in user facing strings. See trac #16490
-var names;
-function protocolName(aNormalizedName) {
-  if (!names) {
-    names = new Map();
-    let protocols = Services.core.getProtocols();
-    while (protocols.hasMoreElements()) {
-      let protocol = protocols.getNext();
-      names.set(protocol.normalizedName, protocol.name);
-    }
-  }
-  return names.get(aNormalizedName) || aNormalizedName;
-}
-
-
 // OTRLib context wrapper
 
 function Context(context) {
   this._context = context;
 }
 
 Context.prototype = {
   constructor: Context,
@@ -88,23 +73,29 @@ Context.prototype = {
   get msgstate() { return this._context.contents.msgstate; },
   get fingerprint() { return this._context.contents.active_fingerprint; },
   get trust() { return trustFingerprint(this.fingerprint); },
 };
 
 
 // otr module
 
+var OTRLib;
+
 var OTR = {
 
   hasRan: false,
   libLoaded: false,
   once() {
     this.hasRan = true;
     try {
+      OTRLib = OTRLibLoader.init();
+      if (!OTRLib) {
+        return;
+      }
       if (OTRLib && OTRLib.init()) {
         this.initUiOps();
         OTR.libLoaded = true;
       }
     } catch (e) {
       console.log(e);
     }
   },
@@ -117,18 +108,16 @@ var OTR = {
     opts = opts || {};
 
     if (!this.hasRan)
       this.once();
 
     if (!OTR.libLoaded)
       return;
 
-    this.verifyNudge = !!opts.verifyNudge;
-    this.setPolicy(opts.requireEncryption);
     this.userstate = OTRLib.otrl_userstate_create();
 
     // A map of UIConvs, keyed on the target.id
     this._convos = new Map();
     this._observers = [];
     this._buffer = [];
     this._poll_timer = null;
 
@@ -159,26 +148,16 @@ var OTR = {
     }
     this._buffer = null;
   },
 
   log(msg) {
     this.notifyObservers(msg, "otr:log");
   },
 
-  protocolName,
-
-  setPolicy(requireEncryption) {
-    if (!OTR.libLoaded)
-      return;
-    this.policy = requireEncryption
-      ? OTRLib.OTRL_POLICY_ALWAYS
-      : OTRLib.OTRL_POLICY_OPPORTUNISTIC;
-  },
-
   // load stored files from my profile
   loadFiles() {
     return Promise.all([
       OS.File.exists(this.privateKeyPath).then((exists) => {
         if (exists && OTRLib.otrl_privkey_read(
           this.userstate, this.privateKeyPath
         )) throw new Error("Failed to read private keys.");
       }),
@@ -238,19 +217,22 @@ var OTR = {
     let fingerprint = OTRLib.otrl_privkey_fingerprint(
       this.userstate, new OTRLib.fingerprint_t(), account, protocol
     );
     return fingerprint.isNull() ? null : fingerprint.readString();
   },
 
   // return a human readable string for a fingerprint
   hashToHuman(fingerprint) {
-    let hash = fingerprint.contents.fingerprint;
-    if (hash.isNull())
-      throw Error("No fingerprint found.");
+    let hash;
+    try {
+      hash = fingerprint.contents.fingerprint;
+    } catch (e) {}
+    if (!hash || hash.isNull())
+      throw new Error("No fingerprint found.");
     let human = new OTRLib.fingerprint_t();
     OTRLib.otrl_privkey_hash_to_human(human, hash);
     return human.readString();
   },
 
   base64encode(data, dataLen) {
     // CData objects are initialized with zeroes.  The plus one gives us
     // our null byte so that readString below is safe.
@@ -276,106 +258,96 @@ var OTR = {
     } else if (level === OTR.trustState.TRUST_UNVERIFIED) {
       return OTR.trustState.TRUST_UNVERIFIED;
     } else if (level === OTR.trustState.TRUST_FINISHED) {
       return OTR.trustState.TRUST_FINISHED;
     }
     return OTR.trustState.TRUST_NOT_PRIVATE;
   },
 
-  getStatus(level) {
-    switch (level) {
-    case OTR.trustState.TRUST_NOT_PRIVATE:
-      return _str("trust-not_private");
-    case OTR.trustState.TRUST_UNVERIFIED:
-      return _str("trust-unverified");
-    case OTR.trustState.TRUST_PRIVATE:
-      return _str("trust-private");
-    case OTR.trustState.TRUST_FINISHED:
-      return _str("trust-finished");
-    }
-    throw new Error("unknown level");
-  },
-
-  // get list of known fingerprints
-  knownFingerprints() {
+  // Fetch list of known fingerprints, either for the given account,
+  // or for all accounts, if parameter is null.
+  knownFingerprints(forAccount) {
     let fps = [];
     for (
       let context = this.userstate.contents.context_root;
       !context.isNull();
       context = context.contents.next
     ) {
       // skip child contexts
       if (!comparePointers(context.contents.m_context, context))
         continue;
       let wContext = new Context(context);
+
+      if (forAccount) {
+        if (forAccount.normalizedName != wContext.account ||
+            forAccount.protocol.normalizedName != wContext.protocol) {
+          continue;
+        }
+      }
+
       for (
         let fingerprint = context.contents.fingerprint_root.next;
         !fingerprint.isNull();
         fingerprint = fingerprint.contents.next
       ) {
         let trust = trustFingerprint(fingerprint);
-        let used = false;
-        let best_level = OTR.trustState.TRUST_NOT_PRIVATE;
-        for (
-          let context_itr = context;
-          !context_itr.isNull() &&
-            comparePointers(context_itr.contents.m_context, context);
-          context_itr = context_itr.contents.next
-        ) {
-          if (comparePointers(
-            context_itr.contents.active_fingerprint, fingerprint
-          )) {
-            used = true;
-            best_level = OTR.getTrustLevel(new Context(context_itr));
-          }
-        }
         fps.push({
           fpointer: fingerprint.contents.address(),
           fingerprint: OTR.hashToHuman(fingerprint),
           screenname: wContext.username,
-          account: wContext.account,
-          protocol: wContext.protocol,
           trust,
-          status: used ? OTR.getStatus(best_level) : _str("trust-unused"),
           purge: false,
         });
       }
     }
     return fps;
   },
 
+  /**
+   * Returns true, if all requested fps were removed.
+   * Returns false, if at least one fps couldn't get removed,
+   * because it's currently actively used.
+   */
   forgetFingerprints(fps) {
+    let result = true;
     let write = false;
     fps.forEach(function(obj, i) {
-      if (!obj.purge)
+      if (!obj.purge) {
         return;
+      }
       obj.purge = false;  // reset early
       let fingerprint = obj.fpointer;
-      if (fingerprint.isNull())
+      if (fingerprint.isNull()) {
         return;
-      // don't do anything if fp is active and we're in an encrypted state
+      }
+      // don't remove if fp is active and we're in an encrypted state
       let context = fingerprint.contents.context.contents.m_context;
       for (
         let context_itr = context;
         !context_itr.isNull() &&
           comparePointers(context_itr.contents.m_context, context);
         context_itr = context_itr.contents.next
       ) {
         if (
           context_itr.contents.msgstate === OTRLib.messageState.OTRL_MSGSTATE_ENCRYPTED &&
           comparePointers(context_itr.contents.active_fingerprint, fingerprint)
-        ) return;
+        ) {
+          result = false;
+          return;
+        }
       }
       write = true;
       OTRLib.otrl_context_forget_fingerprint(fingerprint, 1);
       fps[i] = null;  // null out removed fps
     });
-    if (write)
+    if (write) {
       OTR.writeFingerprints();
+    }
+    return result;
   },
 
   addFingerprint(context, hex) {
     let fingerprint = new OTRLib.hash_t();
     if (hex.length != 40) throw new Error("Invalid fingerprint value.");
     let bytes = hex.match(/.{1,2}/g);
     for (let i = 0; i < 20; i++)
       fingerprint[i] = parseInt(bytes[i], 16);
@@ -486,20 +458,29 @@ var OTR = {
     if (remove) {
       let uiConv = this.getUIConvFromConv(conv);
       if (uiConv)
         this.removeConversation(uiConv);
     } else
       this.notifyObservers(this.getContext(conv), "otr:disconnected");
   },
 
+  getAccountPref(prefName, accountId, defaultVal) {
+    return Services.prefs.getBoolPref(
+      "messenger.account." + accountId + ".options." + prefName,
+      defaultVal);
+  },
+
   sendQueryMsg(conv) {
+    let req = this.getAccountPref("otrRequireEncryption",
+      conv.account.id, OTR.defaultOtrRequireEncryption());
     let query = OTRLib.otrl_proto_default_query_msg(
       conv.account.normalizedName,
-      this.policy
+      req ?
+        OTRLib.OTRL_POLICY_ALWAYS : OTRLib.OTRL_POLICY_OPPORTUNISTIC
     );
     if (query.isNull()) {
       Cu.reportError(new Error("Sending query message failed."));
       return;
     }
     // Use the default msg to format the version.
     // We don't supprt v1 of the protocol so this should be fine.
     let queryMsg = /^\?OTR.*?\?/.exec(query.readString())[0] + "\n";
@@ -529,110 +510,172 @@ var OTR = {
         break;
       case OTRLib.messageState.OTRL_MSGSTATE_FINISHED:
         level = this.trustState.TRUST_FINISHED;
         break;
     }
     return level;
   },
 
+  defaultOtrRequireEncryption() {
+    return false;
+  },
+
+  defaultOtrVerifyNudge() {
+    return true;
+  },
+
+  getPrefBranchFromWrappedContext(wContext) {
+    for (let acc of Services.accounts.getAccounts()) {
+      if (wContext.account == acc.normalizedName &&
+          wContext.protocol == acc.protocol.normalizedName) {
+        return acc.prefBranch;
+      }
+    }
+    return null;
+  },
+
   // uiOps callbacks
 
-  // Return the OTR policy for the given context.
+  /**
+   * Return the OTR policy for the given context.
+   */
   policy_cb(opdata, context) {
-    return this.policy;
+    let wContext = new Context(context);
+    let pb = OTR.getPrefBranchFromWrappedContext(wContext);
+    if (!pb) {
+      return new ctypes.unsigned_int(0);
+    }
+    let prefRequire = pb.getBoolPref("options.otrRequireEncryption",
+      OTR.defaultOtrRequireEncryption());
+    return prefRequire ? OTRLib.OTRL_POLICY_ALWAYS
+                       : OTRLib.OTRL_POLICY_OPPORTUNISTIC;
   },
 
-  // Create a private key for the given accountname/protocol if desired.
+  /**
+   * Create a private key for the given accountname/protocol if desired.
+   */
   create_privkey_cb(opdata, accountname, protocol) {
     let args = {
       account: accountname.readString(),
       protocol: protocol.readString(),
     };
     this.notifyObservers(args, "otr:generate");
   },
 
-  // Report whether you think the given user is online. Return 1 if you think
-  // they are, 0 if you think they aren't, -1 if you're not sure.
+  /**
+   * Report whether you think the given user is online. Return 1 if you
+   * think they are, 0 if you think they aren't, -1 if you're not sure.
+   */
   is_logged_in_cb(opdata, accountname, protocol, recipient) {
     let conv = this.getUIConvForRecipient(
       accountname.readString(),
       protocol.readString(),
       recipient.readString()
     ).target;
     return isOnline(conv);
   },
 
-  // Send the given IM to the given recipient from the given
-  // accountname/protocol.
+  /**
+   * Send the given IM to the given recipient from the given
+   * accountname/protocol.
+   */
   inject_message_cb(opdata, accountname, protocol, recipient, message) {
     let aMsg = message.readString();
     this.log("inject_message_cb (msglen:" + aMsg.length + "): " + aMsg);
     this.getUIConvForRecipient(
       accountname.readString(),
       protocol.readString(),
       recipient.readString()
     ).target.sendMsg(aMsg);
   },
 
-  // A new fingerprint for the given user has been received.
+  /**
+   * new fingerprint for the given user has been received.
+   */
   new_fingerprint_cb(opdata, us, accountname, protocol, username, fingerprint) {
     let context = OTRLib.otrl_context_find(
       us, username, accountname, protocol,
       OTRLib.instag.OTRL_INSTAG_MASTER, 1, null, null, null
     );
 
     let seen = false;
     let fp = context.contents.fingerprint_root.next;
     while (!fp.isNull()) {
       if (CLib.memcmp(fingerprint, fp.contents.fingerprint, new ctypes.size_t(20))) {
         seen = true;
         break;
       }
       fp = fp.contents.next;
     }
 
+    let wContext = new Context(context);
+    let prefNudge = OTR.defaultOtrVerifyNudge();
+    let pb = OTR.getPrefBranchFromWrappedContext(wContext);
+    if (pb) {
+      prefNudge = pb.getBoolPref("options.otrVerifyNudge",
+        OTR.defaultOtrVerifyNudge());
+    }
+
     // Only nudge on new fingerprint, as opposed to always.
-    if (!this.verifyNudge)
-      this.notifyObservers(new Context(context), "otr:unverified",
+    if (!prefNudge)
+      this.notifyObservers(wContext, "otr:unverified",
         (seen ? "seen" : "unseen"));
   },
 
-  // The list of known fingerprints has changed.  Write them to disk.
+  /**
+   * The list of known fingerprints has changed.  Write them to disk.
+   */
   write_fingerprint_cb(opdata) {
     this.writeFingerprints();
   },
 
-  // A ConnContext has entered a secure state.
+  /**
+   * A ConnContext has entered a secure state.
+   */
   gone_secure_cb(opdata, context) {
-    context = new Context(context);
-    let strid = "context-gone_secure_" + (context.trust ? "private" : "unverified");
-    this.notifyObservers(context, "otr:msg-state");
-    this.sendAlert(context, _strArgs(strid, {name: context.username}));
-    if (this.verifyNudge && !context.trust)
-      this.notifyObservers(context, "otr:unverified", "unseen");
+    let wContext = new Context(context);
+    let prefNudge = OTR.defaultOtrVerifyNudge();
+    let pb = OTR.getPrefBranchFromWrappedContext(wContext);
+    if (pb) {
+      prefNudge = pb.getBoolPref("options.otrVerifyNudge",
+        OTR.defaultOtrVerifyNudge());
+    }
+    let strid = "context-gone_secure_" + (wContext.trust ? "private" : "unverified");
+    this.notifyObservers(wContext, "otr:msg-state");
+    this.sendAlert(wContext, _strArgs(strid, {name: wContext.username}));
+    if (prefNudge && !wContext.trust)
+      this.notifyObservers(wContext, "otr:unverified", "unseen");
   },
 
-  // A ConnContext has left a secure state.
+  /**
+   * A ConnContext has left a secure state.
+   */
   gone_insecure_cb(opdata, context) {
     // This isn't used. See: https://bugs.otr.im/lib/libotr/issues/48
   },
 
-  // We have completed an authentication, using the D-H keys we already knew.
-  // is_reply indicates whether we initiated the AKE.
+  /**
+   * We have completed an authentication, using the D-H keys we already
+   * knew.
+   *
+   * @param is_reply    indicates whether we initiated the AKE.
+   */
   still_secure_cb(opdata, context, is_reply) {
     // Indicate the private conversation was refreshed.
     if (!is_reply) {
       context = new Context(context);
       this.notifyObservers(context, "otr:msg-state");
       this.sendAlert(context, _strArgs("context-still_secure", {name: context.username}));
     }
   },
 
-  // Find the maximum message size supported by this protocol.
+  /**
+   * Find the maximum message size supported by this protocol.
+   */
   max_message_size_cb(opdata, context) {
     context = new Context(context);
     // These values are, for the most part, from pidgin-otr's mms_table.
     switch (context.protocol) {
     case "irc":
     case "prpl-irc":
       return 417;
     case "facebook":
@@ -654,23 +697,27 @@ var OTR = {
       return 2343;
     case "prpl-novell":
       return 1792;
     default:
       return 0;
     }
   },
 
-  // We received a request from the buddy to use the current "extra" symmetric
-  // key.
+  /**
+   * We received a request from the buddy to use the current "extra"
+   * symmetric key.
+   */
   received_symkey_cb(opdata, context, use, usedata, usedatalen, symkey) {
     // Ignore until we have a use.
   },
 
-  // Return a string according to the error event.
+  /**
+   * Return a string according to the error event.
+   */
   otr_error_message_cb(opdata, context, err_code) {
     context = new Context(context);
     let msg;
     switch (err_code) {
     case OTRLib.errorCode.OTRL_ERRCODE_ENCRYPTION_ERROR:
       msg = _str("error-enc");
       break;
     case OTRLib.errorCode.OTRL_ERRCODE_MSG_NOT_IN_PRIVATE:
@@ -683,34 +730,42 @@ var OTR = {
       msg = _str("error-malformed");
       break;
     default:
       return null;
     }
     return CLib.strdup(msg);
   },
 
-  // Deallocate a string returned by otr_error_message_cb.
+  /**
+   * Deallocate a string returned by otr_error_message_cb.
+   */
   otr_error_message_free_cb(opdata, err_msg) {
     if (!err_msg.isNull())
       CLib.free(err_msg);
   },
 
-  // Return a string that will be prefixed to any resent message.
+  /**
+   * Return a string that will be prefixed to any resent message.
+   */
   resent_msg_prefix_cb(opdata, context) {
     return CLib.strdup(_str("resent"));
   },
 
-  // Deallocate a string returned by resent_msg_prefix.
+  /**
+   * Deallocate a string returned by resent_msg_prefix.
+   */
   resent_msg_prefix_free_cb(opdata, prefix) {
     if (!prefix.isNull())
       CLib.free(prefix);
   },
 
-  // Update the authentication UI with respect to SMP events.
+  /**
+   * Update the authentication UI with respect to SMP events.
+   */
   handle_smp_event_cb(opdata, smp_event, context, progress_percent, question) {
     context = new Context(context);
     switch (smp_event) {
     case OTRLib.smpEvent.OTRL_SMPEVENT_NONE:
       break;
     case OTRLib.smpEvent.OTRL_SMPEVENT_ASK_FOR_ANSWER:
     case OTRLib.smpEvent.OTRL_SMPEVENT_ASK_FOR_SECRET:
       this.notifyObservers({
@@ -732,18 +787,20 @@ var OTR = {
     case OTRLib.smpEvent.OTRL_SMPEVENT_ERROR:
       OTR.abortSMP(context);
       break;
     default:
       this.log("smp event: " + smp_event);
     }
   },
 
-  // Handle and send the appropriate message(s) to the sender/recipient
-  // depending on the message events.
+  /**
+   * Handle and send the appropriate message(s) to the sender/recipient
+   * depending on the message events.
+   */
   handle_msg_event_cb(opdata, msg_event, context, message, err) {
     context = new Context(context);
     switch (msg_event) {
     case OTRLib.messageEvent.OTRL_MSGEVENT_NONE:
       break;
     case OTRLib.messageEvent.OTRL_MSGEVENT_ENCRYPTION_REQUIRED:
       this.sendAlert(context, _strArgs("msgevent-encryption_required_part1", {name: context.username}));
       this.sendAlert(context, _str("msgevent-encryption_required_part2"));
@@ -791,37 +848,42 @@ var OTR = {
     case OTRLib.messageEvent.OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE:
       this.log(_strArgs("msgevent-rcvdmsg_for_other_instance", {name: context.username}));
       break;
     default:
       this.log("msg event: " + msg_event);
     }
   },
 
-  // Create an instance tag for the given accountname/protocol if desired.
+  /**
+   * Create an instance tag for the given accountname/protocol if
+   * desired.
+   */
   create_instag_cb(opdata, accountname, protocol) {
     this.generateInstanceTag(accountname.readString(), protocol.readString());
   },
 
-  // When timer_control is called, turn off any existing periodic timer.
-  // Additionally, if interval > 0, set a new periodic timer to go off every
-  // interval seconds.
+  /**
+   * When timer_control is called, turn off any existing periodic timer.
+   * Additionally, if interval > 0, set a new periodic timer to go off
+   * every interval seconds.
+   */
   timer_control_cb(opdata, interval) {
     if (this._poll_timer) {
       clearInterval(this._poll_timer);
       this._poll_timer = null;
     }
     if (interval > 0) {
       this._poll_timer = setInterval(() => {
         OTRLib.otrl_message_poll(this.userstate, this.uiOps.address(), null);
       }, interval * 1000);
     }
   },
 
-  // uiOps
+  // end of uiOps
 
   initUiOps() {
     this.uiOps = new OTRLib.OtrlMessageAppOps();
 
     let methods = [
       "policy",
       "create_privkey",
       "is_logged_in",
--- a/chat/modules/OTRLib.jsm
+++ b/chat/modules/OTRLib.jsm
@@ -34,39 +34,51 @@ function tryLoadOTR(name, suffix) {
       libotrPath = filename;
       console.log("===> trying to load " +
         libotrPath + " from system's standard locations");
       libotr = ctypes.open(libotrPath);
     } catch (e) {}
   }
 }
 
-if (!libotr && (systemOS === "winnt" || systemOS === "darwin")) {
-  // otr.5.dll or otr.5.dylib
-  tryLoadOTR("otr.5", "");
-}
+function loadExternalOTRLib() {
+  if (!libotr && (systemOS === "winnt" || systemOS === "darwin")) {
+    // otr.5.dll or otr.5.dylib
+    tryLoadOTR("otr.5", "");
+  }
+
+  if (!libotr && systemOS === "winnt") {
+    // otr-5.dll
+    tryLoadOTR("otr-5", "");
+  }
 
-if (!libotr && systemOS === "winnt") {
-  // otr-5.dll
-  tryLoadOTR("otr-5", "");
+  if (!libotr && systemOS === "winnt") {
+    // libotr-5.dll
+    tryLoadOTR("libotr-5", "");
+  }
+
+  if (!libotr && !(systemOS === "winnt") && !(systemOS === "darwin")) {
+    // libotr.so.5
+    tryLoadOTR("otr", ".5");
+  }
+
+  if (!libotr) {
+    tryLoadOTR("otr", "");
+  }
 }
 
-if (!libotr && systemOS === "winnt") {
-  // libotr-5.dll
-  tryLoadOTR("libotr-5", "");
-}
-
-if (!libotr && !(systemOS === "winnt") && !(systemOS === "darwin")) {
-  // libotr.so.5
-  tryLoadOTR("otr", ".5");
-}
-
-if (!libotr) {
-  tryLoadOTR("otr", "");
-}
+var OTRLibLoader = {
+  init() {
+    loadExternalOTRLib();
+    if (libotr) {
+      enableOTRLibJS();
+    }
+    return OTRLib;
+  },
+};
 
 // Helper function to open files with the path properly encoded.
 var callWithFILEp = function() {
   // Windows filenames are in UTF-16.
   let charType = (systemOS === "winnt") ? "jschar" : "char";
 
   let args = Array.from(arguments);
   let func = args.shift() + "_FILEp";
@@ -379,17 +391,20 @@ const OTRL_POLICY_SEND_WHITESPACE_TAG = 
 const OTRL_POLICY_WHITESPACE_START_AKE = 0x20;
 
 // const OTRL_POLICY_ERROR_START_AKE = 0x40;
 // Disabled to avoid automatic resend and MITM, as explained in
 // https://github.com/arlolra/ctypes-otr/issues/55
 
 var OTRLib;
 
-if (libotr) OTRLib = {
+function enableOTRLibJS() {
+  // this must be delayed until after "libotr" is initialized
+
+  OTRLib = {
 
   path: libotrPath,
 
   // libotr API version
   otrl_version,
 
   init() {
     // console.log("===> OTRLib.init()\n");
@@ -905,14 +920,14 @@ if (libotr) OTRLib = {
   ),
 
   // Deallocate a chain of TLVs.
   otrl_tlv_free: libotr.declare(
     "otrl_tlv_free", abi, ctypes.void_t,
     OtrlTLV.ptr
   ),
 
-};
-
+  };
+}
 
 // exports
 
-this.EXPORTED_SYMBOLS = ["OTRLib"];
+this.EXPORTED_SYMBOLS = ["OTRLibLoader"];
--- a/chat/modules/OTRUI.jsm
+++ b/chat/modules/OTRUI.jsm
@@ -86,41 +86,28 @@ function initStrings() {
       class: "finished",
     }],
   ]);
 }
 
 var windowRefs = new Map();
 
 var OTRUI = {
+  enabled: false,
   stringsLoaded: false,
   globalDoc: null,
   visibleConv: null,
 
-  debug: true,
+  debug: false,
   logMsg(msg) {
     if (!OTRUI.debug)
       return;
     Services.console.logStringMessage(msg);
   },
 
-  prefs: null,
-  setPrefs() {
-    let branch = "chat.otr.";
-    let prefs = {
-      requireEncryption: false,
-      verifyNudge: true,
-    };
-    let defaults = Services.prefs.getDefaultBranch(branch);
-    Object.keys(prefs).forEach(function(key) {
-      defaults.setBoolPref(key, prefs[key]);
-    });
-    OTRUI.prefs = Services.prefs.getBranch(branch);
-  },
-
   addMenuObserver() {
     let iter = Services.ww.getWindowEnumerator();
     while (iter.hasMoreElements())
       OTRUI.addMenus(iter.getNext());
     Services.obs.addObserver(OTRUI, "domwindowopened");
   },
 
   removeMenuObserver() {
@@ -196,37 +183,36 @@ var OTRUI = {
   },
 
   async init() {
     if (!OTRUI.stringsLoaded) {
       initStrings();
       OTRUI.stringsLoaded = true;
     }
 
-    // console.log("====> OTRUI init\n");
-    OTRUI.setPrefs();
-    OTR.init({
-      requireEncryption: OTRUI.prefs.getBoolPref("requireEncryption"),
-      verifyNudge: OTRUI.prefs.getBoolPref("verifyNudge"),
-    });
+    this.debug = Services.prefs.getBoolPref("chat.otr.trace", false);
+
+    OTR.init({});
     if (!OTR.libLoaded) {
       return;
     }
+
+    this.enabled = true;
+
     OTR.addObserver(OTRUI);
     OTR.loadFiles().then(function() {
       Services.obs.addObserver(OTR, "new-ui-conversation");
       // Disabled until #76 is resolved.
       // Services.obs.addObserver(OTRUI, "contact-added", false);
       Services.obs.addObserver(OTRUI, "account-added");
       // Services.obs.addObserver(OTRUI, "contact-signed-off", false);
       Services.obs.addObserver(OTRUI, "conversation-loaded");
       Services.obs.addObserver(OTRUI, "conversation-closed");
       Services.obs.addObserver(OTRUI, "prpl-quit");
 
-      OTRUI.prefs.addObserver("", OTRUI);
       let conversations = Services.conversations.getConversations();
       while (conversations.hasMoreElements()) {
       let aConv = conversations.getNext();
       OTRUI.initConv(aConv);
       }
       OTRUI.addMenuObserver();
     }).catch(function(err) {
       // console.log("===> " + err + "\n");
@@ -245,29 +231,16 @@ var OTRUI = {
         continue;
       if (!OTR.disconnect(conv, true)) {
         allGood = false;
       }
     }
     return allGood;
   },
 
-  changePref(aMsg) {
-    switch (aMsg) {
-    case "requireEncryption":
-      OTR.setPolicy(OTRUI.prefs.getBoolPref("requireEncryption"));
-      break;
-    case "verifyNudge":
-      OTR.verifyNudge = OTRUI.prefs.getBoolPref("verifyNudge");
-      break;
-    default:
-      OTRUI.logMsg(aMsg);
-    }
-  },
-
   openAuth(window, name, mode, uiConv, contactInfo) {
     let otrAuth = this.globalDoc.querySelector(".otr-auth");
     otrAuth.disabled = true;
     let win = window.openDialog(
       authDialog,
       "auth=" + name,
       "centerscreen,resizable=no,minimizable=no",
       mode,
@@ -652,76 +625,75 @@ var OTRUI = {
   },
 
   observe(aObject, aTopic, aMsg) {
     let doc;
     // console.log("====> observing topic: " + aTopic + " with msg: " + aMsg);
     // console.log(aObject);
 
     switch (aTopic) {
-    case "nsPref:changed":
-      OTRUI.changePref(aMsg);
-      break;
-    case "conversation-loaded":
-      doc = aObject.ownerDocument;
-      let windowtype = doc.documentElement.getAttribute("windowtype");
-      if (windowtype !== "mail:3pane") {
-        return;
-      }
-      OTRUI.addButton(aObject);
-      break;
-    case "conversation-closed":
-      if (aObject.isChat)
-        return;
-      this.globalBox.removeAllNotifications();
-      OTRUI.closeAuth(OTR.getContext(aObject));
-      OTRUI.disconnect(aObject);
-      break;
-    // case "contact-signed-off":
-    //  break;
-    case "prpl-quit":
-      OTRUI.disconnect(null);
-      break;
-    case "domwindowopened":
-      OTRUI.addMenus(aObject);
-      break;
-    case "otr:generate":
-      OTRUI.generate(aObject);
-      break;
-    case "otr:disconnected":
-    case "otr:msg-state":
-      if (aTopic === "otr:disconnected" ||
-          OTR.trust(aObject) !== OTR.trustState.TRUST_UNVERIFIED) {
-        OTRUI.closeAuth(aObject);
-        OTRUI.closeUnverified(aObject);
-        OTRUI.closeVerification(aObject);
-      }
-      OTRUI.setMsgState(null, aObject, this.globalDoc, false);
-      break;
-    case "otr:unverified":
-      OTRUI.notifyUnverified(aObject, aMsg);
-      break;
-    case "otr:trust-state":
-      OTRUI.alertTrust(aObject);
-      break;
-    case "otr:log":
-      OTRUI.logMsg("otr: " + aObject);
-      break;
-    case "account-added":
-      OTRUI.onAccountCreated(aObject);
-      break;
-    case "contact-added":
-      OTRUI.onContactAdded(aObject);
-      break;
-    case "otr:auth-ask":
-      OTRUI.askAuth(aObject);
-      break;
-    case "otr:auth-update":
-      OTRUI.updateAuth(aObject);
-      break;
+      case "nsPref:changed":
+        break;
+      case "conversation-loaded":
+        doc = aObject.ownerDocument;
+        let windowtype = doc.documentElement.getAttribute("windowtype");
+        if (windowtype !== "mail:3pane") {
+          return;
+        }
+        OTRUI.addButton(aObject);
+        break;
+      case "conversation-closed":
+        if (aObject.isChat)
+          return;
+        this.globalBox.removeAllNotifications();
+        OTRUI.closeAuth(OTR.getContext(aObject));
+        OTRUI.disconnect(aObject);
+        break;
+      // case "contact-signed-off":
+      //  break;
+      case "prpl-quit":
+        OTRUI.disconnect(null);
+        break;
+      case "domwindowopened":
+        OTRUI.addMenus(aObject);
+        break;
+      case "otr:generate":
+        OTRUI.generate(aObject);
+        break;
+      case "otr:disconnected":
+      case "otr:msg-state":
+        if (aTopic === "otr:disconnected" ||
+            OTR.trust(aObject) !== OTR.trustState.TRUST_UNVERIFIED) {
+          OTRUI.closeAuth(aObject);
+          OTRUI.closeUnverified(aObject);
+          OTRUI.closeVerification(aObject);
+        }
+        OTRUI.setMsgState(null, aObject, this.globalDoc, false);
+        break;
+      case "otr:unverified":
+        OTRUI.notifyUnverified(aObject, aMsg);
+        break;
+      case "otr:trust-state":
+        OTRUI.alertTrust(aObject);
+        break;
+      case "otr:log":
+        OTRUI.logMsg("otr: " + aObject);
+        break;
+      case "account-added":
+        OTRUI.onAccountCreated(aObject);
+        break;
+      case "contact-added":
+        OTRUI.onContactAdded(aObject);
+        break;
+      case "otr:auth-ask":
+        OTRUI.askAuth(aObject);
+        break;
+      case "otr:auth-update":
+        OTRUI.updateAuth(aObject);
+        break;
     }
   },
 
   initConv(binding) {
     OTR.addConversation(binding._conv);
     OTRUI.addButton(binding);
   },
 
@@ -744,15 +716,14 @@ var OTRUI = {
     Services.obs.removeObserver(OTRUI, "conversation-loaded");
     Services.obs.removeObserver(OTRUI, "conversation-closed");
     Services.obs.removeObserver(OTRUI, "prpl-quit");
 
     let conversations = Services.conversations.getConversations();
     while (conversations.hasMoreElements()) {
       OTRUI.resetConv(conversations.getNext());
     }
-    OTRUI.prefs.removeObserver("", OTRUI);
     OTR.removeObserver(OTRUI);
     OTR.close();
     OTRUI.removeMenuObserver();
   },
 
 };
--- a/mail/components/im/content/am-im.js
+++ b/mail/components/im/content/am-im.js
@@ -1,25 +1,40 @@
 /* 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/. */
 
 // chat/content/imAccountOptionsHelper.js
 /* globals accountOptionsHelper */
 
-var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.defineModuleGetter(this, "OTRUI", "resource:///modules/OTRUI.jsm");
+ChromeUtils.defineModuleGetter(this, "OTR", "resource:///modules/OTR.jsm");
 
 var autoJoinPref = "autoJoin";
 
 function onPreInit(aAccount, aAccountValue) {
   account.init(aAccount.incomingServer.wrappedJSObject.imAccount);
 }
 
 var account = {
-  init(aAccount) {
+  async init(aAccount) {
+    let title = document.querySelector(".dialogheader .dialogheader-title");
+    let defaultTitle = title.getAttribute("defaultTitle");
+    let titleValue;
+
+    if (aAccount.name) {
+      titleValue = defaultTitle + " - <" + aAccount.name + ">";
+    } else {
+      titleValue = defaultTitle;
+    }
+
+    title.setAttribute("value", titleValue);
+    document.title = titleValue;
+
     this.account = aAccount;
     this.proto = this.account.protocol;
     document.getElementById("accountName").value = this.account.name;
     document.getElementById("protocolName").value = this.proto.name || this.proto.id;
     document.getElementById("protocolIcon").src =
       this.proto.iconBaseURI + "icon48.png";
 
     let password = document.getElementById("server.password");
@@ -37,20 +52,35 @@ var account = {
       } catch (e) {
         passwordBox.hidden = true;
         password.removeAttribute("wsm_persist");
       }
     }
 
     document.getElementById("server.alias").value = this.account.alias;
 
+    if (OTRUI.enabled) {
+      document.getElementById("imTabOTR").hidden = false;
+      document.getElementById("server.otrVerifyNudge").value = this.account.otrVerifyNudge;
+      document.getElementById("server.otrRequireEncryption").value = this.account.otrRequireEncryption;
+
+      let fpa = this.account.normalizedName;
+      let fpp = this.account.protocol.normalizedName;
+      let fp = OTR.privateKeyFingerprint(fpa, fpp);
+      if (!fp) {
+        OTR.generatePrivateKey(fpa, fpp);
+        fp = await document.l10n.formatValue("otr-notYetAvailable");
+      }
+      document.getElementById("otrFingerprint").value = fp;
+    }
+
     let protoId = this.proto.id;
-    let canAutoJoin =
-      protoId == "prpl-irc" || protoId == "prpl-jabber" || protoId == "prpl-gtalk";
-    document.getElementById("optionalSeparator").hidden = !canAutoJoin;
+    let canAutoJoin = protoId == "prpl-irc" ||
+                      protoId == "prpl-jabber" ||
+                      protoId == "prpl-gtalk";
     document.getElementById("autojoinBox").hidden = !canAutoJoin;
     let autojoin = document.getElementById("server.autojoin");
     if (canAutoJoin)
       autojoin.setAttribute("wsm_persist", "true");
     else
       autojoin.removeAttribute("wsm_persist");
 
     this.prefs = Services.prefs.getBranch("messenger.account." +
@@ -90,9 +120,22 @@ var account = {
       // Force textbox XBL binding attachment by forcing layout,
       // otherwise setFormElementValue from AccountManager.js sets
       // properties that don't exist when restoring values.
       document.getElementById("protoSpecific").getBoundingClientRect();
     } else if (!haveOptions) {
       advanced.hidden = true;
     }
   },
+
+  viewFingerprintKeys() {
+    window.openDialog("chrome://chat/content/otr-finger.xul", "",
+                      "chrome,modal,titlebar,centerscreen", this.account);
+  },
+
+  updateRequireEncryption() {
+    console.log("require");
+  },
+
+  updateVerifyNudge() {
+    console.log("verify");
+  },
 };
--- a/mail/components/im/content/am-im.xul
+++ b/mail/components/im/content/am-im.xul
@@ -1,52 +1,128 @@
 <?xml version="1.0"?>
 <!-- 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/. -->
 
-<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
-<?xml-stylesheet href="chrome://messenger/content/bindings.css" type="text/css"?>
-
-<!DOCTYPE window SYSTEM "chrome://messenger/locale/am-im.dtd">
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
 
-<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
-  id="account"
-  title="&accountWindow.title;"
-  buttons="accept,cancel"
-  onload="parent.onPanelLoaded('am-im.xul');">
+<!DOCTYPE window [
+  <!ENTITY % imDTD SYSTEM "chrome://messenger/locale/am-im.dtd">
+  <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+  %imDTD;
+  %brandDTD;
+]>
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+      xmlns:html="http://www.w3.org/1999/xhtml"
+      id="account"
+      title="&accountWindow.title;"
+      buttons="accept,cancel"
+      onload="parent.onPanelLoaded('am-im.xul');">
   <script src="chrome://chat/content/imAccountOptionsHelper.js"/>
   <script src="chrome://messenger/content/am-im.js"/>
 
-  <hbox>
-    <image id="protocolIcon"/>
-    <vbox flex="1">
-      <label id="accountName" crop="end" class="header"/>
-      <label id="protocolName"/>
-    </vbox>
-  </hbox>
-  <separator/>
+  <linkset>
+    <html:link rel="localization" href="branding/brand.ftl"/>
+    <html:link rel="localization" href="messenger/preferences/am-im.ftl"/>
+    <html:link rel="localization" href="messenger/otr/am-im-otr.ftl"/>
+  </linkset>
 
-  <hbox id="passwordBox" equalsize="always" align="baseline">
-    <label value="&account.password;" control="server.password" flex="1"/>
-    <textbox id="server.password" flex="1" type="password"
-             preftype="wstring" genericattr="true"/>
-  </hbox>
-  <separator class="groove"/>
-
-  <hbox id="aliasBox" equalsize="always" align="baseline">
-    <label value="&account.alias;" control="server.alias" flex="1"/>
-    <textbox id="server.alias" flex="1" preftype="wstring"
-             wsm_persist="true" genericattr="true"/>
+  <hbox class="dialogheader">
+    <label class="dialogheader-title" defaultTitle="&accountWindow.title;"/>
   </hbox>
 
-  <separator class="groove" hidden="true" id="optionalSeparator"/>
+  <groupbox>
+    <hbox align="center">
+      <image id="protocolIcon"/>
+      <vbox flex="1">
+        <label id="accountName" crop="end" class="header"/>
+        <label id="protocolName"/>
+      </vbox>
+    </hbox>
+  </groupbox>
+  <groupbox>
+    <tabbox id="imTabbox" flex="1">
+      <tabs>
+          <tab id="imTabGeneral" label="&account.general;"/>
+          <tab id="imTabOTR" data-l10n-id="account-encryption" hidden="true"/>
+      </tabs>
+      <tabpanels flex="1">
+        <tabpanel orient="vertical">
+          <label class="header" data-l10n-id="account-settingsTitle" />
+          <hbox id="passwordBox" equalsize="always" align="baseline">
+            <label value="&account.password;" control="server.password" flex="1"/>
+            <textbox id="server.password" flex="1" type="password"
+                     preftype="wstring" genericattr="true"/>
+          </hbox>
+          <hbox id="aliasBox" equalsize="always" align="baseline">
+            <label value="&account.alias;" control="server.alias" flex="1"/>
+            <textbox id="server.alias" flex="1" preftype="wstring"
+                     wsm_persist="true" genericattr="true"/>
+          </hbox>
+          <separator class="thin"/>
+
+          <vbox id="autojoinBox" hidden="true">
+            <label class="header" data-l10n-id="account-channelTitle" />
+            <vbox>
+              <label value="&account.autojoin;" control="server.autojoin" flex="1"/>
+              <textbox id="server.autojoin" flex="1" preftype="wstring" genericattr="true"/>
+            </vbox>
+            <separator class="thin"/>
+          </vbox>
+          <vbox id="advanced">
+            <label class="header">&account.advanced;</label>
+            <caption label="&account.advanced;"/>
+            <vbox id="protoSpecific" flex="1"/>
+          </vbox>
+        </tabpanel>
 
-  <vbox id="autojoinBox" hidden="true">
-    <label value="&account.autojoin;" control="server.autojoin" flex="1"/>
-    <textbox id="server.autojoin" flex="1" preftype="wstring" genericattr="true"/>
-  </vbox>
+        <tabpanel orient="vertical">
+          <label class="header" data-l10n-id="account-otr-label" />
+          <description data-l10n-id="account-otr-description" />
+
+          <separator/>
 
-  <groupbox id="advanced">
-    <caption label="&account.advanced;"/>
-    <vbox id="protoSpecific" flex="1"/>
+          <vbox>
+            <label class="header" data-l10n-id="otr-settings-title" />
+            <checkbox id="server.otrRequireEncryption"
+                      data-l10n-id="otr-requireEncryption"
+                      crop="end"
+                      oncommand="account.updateRequireEncryption();"
+                      wsm_persist="true"
+                      preftype="bool"
+                      genericattr="true"
+                      />
+            <checkbox id="server.otrVerifyNudge"
+                      data-l10n-id="otr-verifyNudge"
+                      crop="end"
+                      oncommand="account.updateVerifyNudge();"
+                      wsm_persist="true"
+                      preftype="bool"
+                      genericattr="true"
+                      />
+          </vbox>
+
+          <separator/>
+
+          <vbox>
+            <label class="header" data-l10n-id="otr-encryption-title" />
+            <label data-l10n-id="otr-encryption-caption" />
+            <separator class="thin"/>
+            <hbox align="baseline">
+              <label data-l10n-id="otr-fingerprint-label" />
+              <textbox id="otrFingerprint"
+                          readonly="true"
+                          flex="1"/>
+            </hbox>
+            <separator class="thin"/>
+            <hbox pack="start">
+              <button id="viewFingerprintButton"
+                      data-l10n-id="view-fingerprint-button"
+                      oncommand="account.viewFingerprintKeys();"/>
+            </hbox>
+          </vbox>
+        </tabpanel>
+      </tabpanels>
+    </tabbox>
   </groupbox>
 </page>
--- a/mail/components/im/imIncomingServer.js
+++ b/mail/components/im/imIncomingServer.js
@@ -160,19 +160,26 @@ imIncomingServer.prototype = {
         "messenger.account." + this.imAccount.id + ".options." + aPrefName;
       return Services.prefs.getIntPref(prefName);
     } catch (x) {
       return this._getDefault(aPrefName);
     }
   },
   _defaultOptionValues: null,
   _getDefault(aPrefName) {
+    if (aPrefName == "otrVerifyNudge") {
+      return true;
+    }
+    if (aPrefName == "otrRequireEncryption") {
+      return false;
+    }
     if (this._defaultOptionValues)
       return this._defaultOptionValues[aPrefName];
 
+
     this._defaultOptionValues = {};
     let options = this.imAccount.protocol.getOptions();
     while (options.hasMoreElements()) {
       let opt = options.getNext();
       let type = opt.type;
       if (type == opt.typeBool)
         this._defaultOptionValues[opt.name] = opt.getBool();
       else if (type == opt.typeInt)
new file mode 100644
--- /dev/null
+++ b/mail/locales/en-US/messenger/preferences/am-im.ftl
@@ -0,0 +1,6 @@
+# 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/.
+
+account-settingsTitle = Authentication Settings
+account-channelTitle = Default Channels
--- a/mail/locales/jar.mn
+++ b/mail/locales/jar.mn
@@ -7,16 +7,18 @@
 # having to create the same entry for each locale.
 
 #filter substitution
 
 [localization] @AB_CD@.jar:
   # When ready for l10n, move chat/content/otr/*.ftl
   #                        to mail/locales/en-US/messenger/otr/
   # and remove the following otr/ lines.
+  messenger/otr/am-im-otr.ftl                                           (../../chat/content/otr/am-im-otr.ftl)
+  messenger/otr/finger.ftl                                              (../../chat/content/otr/finger.ftl)
   messenger/otr/add-finger.ftl                                          (../../chat/content/otr/add-finger.ftl)
   messenger/otr/auth.ftl                                                (../../chat/content/otr/auth.ftl)
   messenger/otr/chat.ftl                                                (../../chat/content/otr/chat.ftl)
   messenger/otr/generate-key.ftl                                        (../../chat/content/otr/generate-key.ftl)
   messenger/otr/otr.ftl                                                 (../../chat/content/otr/otr.ftl)
   messenger/otr/otrUI.ftl                                               (../../chat/content/otr/otrUI.ftl)
   messenger                                                             (%messenger/**/*.ftl)