imported patch otr-prefs draft
authorKai Engert <kaie@kuix.de>
Mon, 27 May 2019 16:46:05 +0200
changeset 74003 bfc56f3f6e8bab7d433223f9efd7756ad8f60eba
parent 74002 0e04243f8a36f881967f5f07d48f87615604801f
child 74004 76fd3926287f940e2fb238f52883068e6cc9391f
push id8519
push userkaie@kuix.de
push dateMon, 27 May 2019 14:46:26 +0000
treeherdertry-comm-central@c4340e817e5c [default view] [failures only]
imported patch otr-prefs
chat/content/jar.mn
chat/content/otr-finger.js
chat/content/otr-finger.xul
chat/content/otr-generate-key.js
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
new file mode 100644
--- /dev/null
+++ b/chat/content/otr-finger.js
@@ -0,0 +1,95 @@
+/* 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 fingers;
+var fingerTreeView = {
+  selection: null,
+  rowCount: 0,
+  setTree: function(tree) {},
+  getImageSrc: function(row, column) {},
+  getProgressMode: function(row, column) {},
+  getCellValue: function(row, column) {},
+  getCellText: function(row, column) {
+    let finger = fingers[row];
+    switch(column.id) {
+    case "verified": {
+      let id = finger.trust ? "finger-yes" : "finger-no";
+      return syncL10n.formatValue(id);
+    }
+    default:
+      return finger[column.id] || "";
+    }
+  },
+  isSeparator: function(index) { return false; },
+  isSorted: function() { return false; },
+  isContainer: function(index) { return false; },
+  cycleHeader: function(column) {},
+  getRowProperties: function(row) { return ""; },
+  getColumnProperties: function(column) { return ""; },
+  getCellProperties: function(row, column) { return ""; },
+};
+
+function getSelections(tree) {
+  let selections = [];
+  let select = tree.view.selection;
+  if (select) {
+    let count = select.getRangeCount();
+    let min = {};
+    let max = {};
+    for (let i = 0; i < count; i++) {
+      select.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: function() {
+    fingerTree = document.getElementById("fingerTree");
+    fingers = OTR.knownFingerprints(account);
+    fingerTreeView.rowCount = fingers.length;
+    fingerTree.view = fingerTreeView;
+  },
+
+  select: function() {
+    let selections = getSelections(fingerTree);
+    document.getElementById("remove").disabled = !selections.length;
+  },
+
+  remove: function() {
+    fingerTreeView.selection.selectEventsSuppressed = true;
+    // mark fingers for removal
+    getSelections(fingerTree).forEach(function(sel) {
+      fingers[sel].purge = true;
+    });
+    OTR.forgetFingerprints(fingers);  // will null out removed fingers
+    for (let j = 0; j < fingers.length; j++) {
+      if (fingers[j] === null) {
+        let k = j;
+        while (k < fingers.length && fingers[k] === null)
+          k++;
+        fingers.splice(j, k - j);
+        fingerTreeView.rowCount -= k - j;
+        fingerTree.rowCountChanged(j, j - k);  // negative
+      }
+    }
+    fingerTreeView.selection.selectEventsSuppressed = false;
+  },
+};
new file mode 100644
--- /dev/null
+++ b/chat/content/otr-finger.xul
@@ -0,0 +1,55 @@
+<?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 type="application/javascript" src="chrome://chat/content/otr-finger.js"/>
+
+  <label data-l10n-id="finger-intro" />
+  <separator class="thin"/>
+  <vbox id="fingerprints" class="contentPane" flex="1">
+    <tree flex="1"
+          width="800"
+          style="height: 20em;"
+          onselect="otrFinger.select()"
+          id="fingerTree">
+      <treecols>
+        <treecol id="screenname" data-l10n-id="finger-screenName" flex="20" />
+        <splitter class="tree-splitter"/>
+        <treecol id="verified" data-l10n-id="finger-verified" flex="10" />
+        <splitter class="tree-splitter"/>
+        <treecol id="fingerprint" data-l10n-id="finger-fingerprint" flex="120" />
+        <splitter class="tree-splitter"/>
+        <treecol id="status" data-l10n-id="finger-status" flex="10" />
+        <splitter class="tree-splitter"/>
+      </treecols>
+      <treechildren/>
+    </tree>
+    <separator class="thin"/>
+    <label data-l10n-id="finger-info" />
+    <separator class="thin"/>
+    <hbox>
+      <button data-l10n-id="finger-remove"
+              disabled="true"
+              id="remove"
+              oncommand="otrFinger.remove()"/>
+    </hbox>
+  </vbox>
+</dialog>
--- a/chat/content/otr-generate-key.js
+++ b/chat/content/otr-generate-key.js
@@ -1,28 +1,46 @@
 /* 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;
+      }
+    }
+
+    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;
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, you can share your own OTR fingerprint using an outside (out-of-band) communication channel.
+otr-fingerprint-label = Your Fingerprint:
+view-fingerprint-button =
+    .label = 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 an encrypted conversation is with the intended person, and makes it very difficult for a third party to read or manipulate your 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 the telephone or OpenPGP-signed email. Each of you should tell your fingerprint to the other. 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-answer = Enter secret answer here (case sensitive):
 
 auth-secret = Enter secret here:
--- 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,25 @@
+# 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 = Is Verified?
+finger-fingerprint =
+    .label = Fingerprint
+finger-status =
+    .label = Status
+finger-info = You can remove key fingerprints that you no longer trust, and that are currently inactive.
+
+finger-remove =
+    .label = Remove
+
+finger-yes = Yes
+finger-no = No
--- 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,36 @@ 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 = An encrypted conversation started. You should verify the identity of { $name } to ensure nobody can eavesdrop on the conversation.
 
 # 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.
+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.
 
-trust-unused = Unused
-trust-not_private = Not Private
-trust-unverified = Unverified
-trust-private = Private
-trust-finished = Finished
+fp-active = Active
+fp-inactive = Inactive
--- 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,18 +217,21 @@ 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())
+    let hash;
+    try {
+      hash = fingerprint.contents.fingerprint;
+    } catch (e) {}
+    if (!hash || hash.isNull())
       throw 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
@@ -276,42 +258,37 @@ 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() {
+  // Fet 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;
@@ -327,48 +304,50 @@ var OTR = {
             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"),
+          status: used ? _str("fp-active") : _str("fp-inactive"),
           purge: false,
         });
       }
     }
     return fps;
   },
 
   forgetFingerprints(fps) {
     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
       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;
+        ) {
+          return;
+        }
       }
       write = true;
       OTRLib.otrl_context_forget_fingerprint(fingerprint, 1);
       fps[i] = null;  // null out removed fps
     });
     if (write)
       OTR.writeFingerprints();
   },
@@ -486,20 +465,26 @@ 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) {
+    return Services.prefs.getBoolPref(
+      "messenger.account." + accountId + ".options." + prefName);
+  },
+
   sendQueryMsg(conv) {
     let query = OTRLib.otrl_proto_default_query_msg(
       conv.account.normalizedName,
-      this.policy
+      this.getAccountPref("otrRequireEncryption", conv.account.id) ?
+        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,21 +514,51 @@ 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.
   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;
+    try {
+      prefRequire = pb.getBoolPref("options.otrRequireEncryption");
+    } catch (e) {
+      prefRequire = OTR.defaultOtrRequireEncryption();
+    }
+    return prefRequire ? OTRLib.OTRL_POLICY_ALWAYS
+                       : OTRLib.OTRL_POLICY_OPPORTUNISTIC;
   },
 
   // 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(),
     };
@@ -585,35 +600,52 @@ var OTR = {
     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) {
+      try {
+        prefNudge = pb.getBoolPref("options.otrVerifyNudge");
+      } catch (e) {
+      }
+    }
+
     // 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.
   write_fingerprint_cb(opdata) {
     this.writeFingerprints();
   },
 
   // 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) {
+      try {
+        prefNudge = pb.getBoolPref("options.otrVerifyNudge");
+      } catch (e) {}
+    }
+    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.
   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.
--- 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,38 @@ 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"),
-    });
+    try {
+      this.debug = Services.prefs.getBoolPref("chat.otr.trace");
+    } catch (e) {};
+
+    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 +233,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,
@@ -639,17 +614,17 @@ 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);
+      console.log("===> otr: unhandled nsPref:changed " + aMsg);
       break;
     case "conversation-loaded":
       doc = aObject.ownerDocument;
       let windowtype = doc.documentElement.getAttribute("windowtype");
       if (windowtype !== "mail:3pane") {
         return;
       }
       OTRUI.addButton(aObject);
@@ -730,15 +705,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",
+                      "", "", 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)