Compare commits

..

1 Commits

Author SHA1 Message Date
Jörg Knobloch 0420e62567 Hotfix version 1.1.101 2 years ago

@ -1,24 +1,5 @@
MOSTLY OUT-OF-DATE (Feb. 2021)
# Guide to the code
The control flow starts from `chrome/bootstrap.js` where we specify
window scripts like `pepmessengercompose.js` and
`pepmsghdrview.js`. These scripts are executed when Thunderbird opens
a window with the matching type.
These "window scripts" are in in `chrome/content` like most of the
code. When executed they import other modules with
`ChromeUtils.import`, passing the window and other global objects if
needed. Outside of this context we cannot assume that `window` is
accessible.
The three-pane window is always running when Thunderbird is running,
so its `pepmsghdrview.js` script performs most of the application
initialisation. One of the main objects to be initialised is the
controller within `chrome/content/p4tb.js`.
# The `window` object
## The `window` object
In `bootstrap.js` we subscribe to notifications from a `ww` service
that calls a `paint` method when the `window` object is globally
@ -29,99 +10,14 @@ it. Outside of this context we cannot assume that `window` is
accessible. An `unpaint` notification will be sent when the window is
closed.
## pEpController and `p4tb.js`
The `pEpController` is a module defined in
`chrome/content/modules/pEp.js` and is meant to be independent from
Thunderbird. Within `p4tb.js`, the controller gets augmented with
Thunderbird-specific logic.
`p4tb.js` also contains most of the initialisation logic. There the
server gets a `detectJsonAdapter` which populates the
`server.connectionInfo` on initialisation.
Note that this initialisation sequence is confined to the window where
the script has run, so it's executed again for every message
composition window when the matching `pepmessengercompose.js` script
is executed.
## Types of modules
Above we mentioned the `pEpController` module. Here we have mainly two
types of modules:
- `ChromeUtils` modules
- `ChromeUtils` & Node modules
Most files in `chrome/content` define a module of the first type. The
files in `chrome/content/modules` are `ChromeUtils` _and_ Node
modules, so they can be tested with our unit tests.
Our Node modules cannot import other Node modules because then they
wouldn't work as `ChromeUtils` modules. These modules can be imported
once and that's it. They can be combined with each other using
dependency injection.
## Encryption & Decryption
Encryption and decryption are performed via components and factories
defined in `pEpMimeEncrypt.js` and `pEpMimeDecrypt.js` (branch
`P4TB-43-2` to be merged).
We define component objects and wrap them with factories that get
registered on startup and deregistered on teardown. You might want to
deregister factories for testing purposes, since only one factory can
be registered for every component id.
Component objects are created in isolation when some events happen,
for example the encryption component is created when there is an
outgoing message. Since they have this ad-hoc, isolated lifecycle,
communication between our components and our views is not
straightforward.
# Other notes
## Environment setup
We suggest to use [different
profiles](https://support.mozilla.org/en-US/kb/using-multiple-profiles),
one for your regular mail use and one for extension development.
Testing can be done using one of the [test accounts (need VPN
access)](intern.pep.security/service/QA/testaccounts).
## Relevant entry points to the engine code
Most of the extension code is written under the assumption that the
extension developer can read the engine code as a form of
documentation and keep the extension synchronised with the engine's
(and adapter's) interfaces.
Many interfaces are defined in the following engine's files:
- `pEpEngine.h`
- `message_api.h`
- `message.h`
- `pEp_internal.h`
After that, grep is your friend/enemy (`darthmama`'s quote)
## Adapter
The extension connects to the JSON adapter server using a token which
is found in `~/.pEp/json-token`. The whole purpose of the token is to
be sure that the client runs with the same user rights than the server
(`Roker`'s quote).
## Updating the options
When we want to change the plugin preferences we can do so by
modifying different areas in the code:
modifying three areas in the code:
- `options.xul` the markup, here you define the identifier for the option that will be used also elsewhere
- `prefsFactory.js` update getter and setter functions corresponding to the option and the default value
- `options.js` update `addAll`, the window load handler, the dialogAccept handler
- `bootstrap.js` update the defaults
## Compat and Prefs

@ -1,19 +1,11 @@
MOSTLY OUT-OF-DATE (Feb. 2021)
# p≡p for Thunderbird
This is a Thunderbird extension providing p≡privacy to your mail. It
relies on the [p≡p JSON adapter][adapter] to access the key
management, synchronisation and automation logic provided by the p≡p
engine.
## HOW TO BUILD
Using docker
## How To Build
Using bash
```docker build -t <image_tag> . && docker run -v build:/usr/src/app/build <image_tag> ```
Just run `make` to build from the current source, producing a file in
`build/p4t.xpi` that can be installed from Thunderbird's add-ons
manager page clicking on "Install from file". For information about
building and running the engine adapter see [its repo][adapter].
Using bash
```cd addon ; zip -r ../build/pEp4Tb@pEp.security.xpi . ; cd ..```
@ -46,10 +38,3 @@ To run tests first is needed to install all dependencies
npm run test
npm run linttest
## More info
Check the `DEVELOPMENT.md` file here for a collection of development
notes. See also [our dev wiki](https://dev.pep.security/thunderbird/).
[adapter]: https://pep.foundation/dev/repos/pEpJSONServerAdapter "JSON Adapter"

@ -14,21 +14,6 @@
"pleaseRestart": {
"message": "Bitte Thunderbird neu starten"
},
"importRNP1Title": {
"message": "Import von OpenPGP in Thunderbird"
},
"importRNP1": {
"message": "p≡p ersetzt Thunderbird's OpenPGP. Wollen Sie die OpenPGP-Konfiguration einschließlich aller Schlüssel importieren?"
},
"importRNP1Button": {
"message": "Importieren"
},
"importRNP2Title": {
"message": "Import-Ergebnis"
},
"importRNP2": {
"message": "{0} private(r) und {1} öffentliche(r) Schlüssel importiert"
},
"optionsLabel": {
"message": "p≡p Einstellungen"
},
@ -132,7 +117,7 @@
"message": "Nachrichtenbetreff schützen"
},
"optionsProtectSubjectsTooltip": {
"message": "p≡p ersetzt alle Betreffe für die Nachrichtenübertragung auch für OpenPGP-Kommunikationspartner"
"message": "p≡p ersetzt alle Betreffs für die Nachrichtenübertragung"
},
"optionsEnableSync": {
"message": "p≡p Sync aktivieren"

@ -14,21 +14,6 @@
"pleaseRestart": {
"message": "Please restart Thunderbird"
},
"importRNP1Title": {
"message": "Import from Thunderbird's OpenPGP"
},
"importRNP1": {
"message": "p≡p is replacing Thunderbird's OpenPGP. Would you like to import the OpenPGP configuration including all keys?"
},
"importRNP1Button": {
"message": "Import"
},
"importRNP2Title": {
"message": "Import Result"
},
"importRNP2": {
"message": "Imported {0} private and {1} public keys"
},
"optionsLabel": {
"message": "p≡p Preferences"
},
@ -132,7 +117,7 @@
"message": "Protect message subject"
},
"optionsProtectSubjectsTooltip": {
"message": "p≡p will replace all subjects for message transmission, also for OpenPGP communication partners"
"message": "p≡p will replace all subjects for message transmission"
},
"optionsEnableSync": {
"message": "Enable p≡p Sync"
@ -144,7 +129,7 @@
"message": "Show a warning when a message loses security through reply or forward"
},
"optionsWarnUnencryptedTooltip": {
"message": "p≡p will ask you for confirmation before sending an unsecure message in a conversation which was previously encrypted"
"message": "p≡p will ask you for confirmation before sending an unsecure message in a conversation which was formerly encrypted"
},
"optionsPassiveMode": {
"message": "Enable Passive Mode"

@ -2,14 +2,14 @@
var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");
var { pEp } = ChromeUtils.import("chrome://pEp4Tb/content/modules/pEp.js");
var { fixIterator } = ChromeUtils.import("resource:///modules/iteratorUtils.jsm");
const TbContacts = {
getCardUserIdForEmail(emailAddress) {
// copied from msgHdrViewOverlay.js
const books = MailServices.ab.directories;
let userId = null;
for (let ab of fixIterator(books, Ci.nsIAbDirectory)) {
while (books.hasMoreElements()) {
const ab = books.getNext().QueryInterface(Ci.nsIAbDirectory);
// Thunderbird supports four different types of address book:
// 1) Its own local address book.
// 2) LDAP address book.

@ -1,6 +1,5 @@
// This file is under GNU General Public License 3.0
var { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");
var { fixIterator } = ChromeUtils.import("resource:///modules/iteratorUtils.jsm");
@ -106,13 +105,8 @@ const TbHelper = {
if (deletedOld) return;
try {
controller.log.info(`Deleting old message with key ${msgHdr.messageKey}`);
let msgArray;
if (parseInt(AppConstants.MOZ_APP_VERSION, 10) >= 85) {
msgArray = [msgHdr];
} else {
msgArray = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
msgArray.appendElement(msgHdr);
}
const msgArray = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
msgArray.appendElement(msgHdr, false);
// Passing `win` as the second parameter instead of `null` results in
// NS_ERROR_XPC_BAD_CONVERT_JS. So we seem to have the wrong type of window here.
msgFolder.deleteMessages(msgArray, null, true, false, deleteListener, false);

@ -1,6 +1,7 @@
// This file is under GNU General Public License 3.0
/* global Preferences */
var { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");
var { fixIterator } = ChromeUtils.import("resource:///modules/iteratorUtils.jsm");
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
@ -33,7 +34,7 @@ var pEpOptions = {
},
initializeStrings() {
let label = pEpController.platform == "win"
let label = AppConstants.platform == "win"
? "optionsLabelWin"
: "optionsLabel";
document.getElementById("appPreferences").setAttribute("title", this.getLocaleMessage(label));
@ -65,10 +66,8 @@ var pEpOptions = {
protectSubjectsChange() {
if (Services.prefs.getBoolPref("extensions.pEp.protectSubjects", true)) {
pEpController.enableprotectSubjects();
pEpController.protectSubjects = true;
} else {
pEpController.disableprotectSubjects();
pEpController.protectSubjects = false;
}
},
@ -225,9 +224,7 @@ var pEpOptions = {
resetIdentity(item) {
let name = item.getAttribute("fullName");
let email = item.getAttribute("email");
// According to instructions received on 10th Feb. 2021, no FPR should be passed in here.
let id = new pEp.Identity(email, "pEp_own_userId", name, "");
pEpController.key_reset_identity(id);
pEpController.key_reset_identity(name, email, null);
},
resetAll() {
@ -380,14 +377,9 @@ var pEpOptions = {
return;
}
let location = Preferences.get("extensions.pEp.keyLocation").value;
let secFlag;
if (pEpController.MOZ_APP_VERSION >= 80) {
secFlag = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
} else {
secFlag = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL;
}
let channel = Services.io.newChannelFromURI(Services.io.newURI(location), null,
Services.scriptSecurityManager.getSystemPrincipal(), null, secFlag,
Services.scriptSecurityManager.getSystemPrincipal(), null,
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER);
let inputStream = channel.open();
let stream = Cc["@mozilla.org/binaryinputstream;1"]

@ -36,10 +36,12 @@
preference="extensions.pEp.storeAllSecurely"
label-localekey="optionsStoreAllSecurely"
tooltiptext="optionsStoreAllSecurelyTooltip" />
<!--
<checkbox id="protectSubjects"
preference="extensions.pEp.protectSubjects"
label-localekey="optionsProtectSubjects"
tooltiptext="optionsProtectSubjectsTooltip" />
-->
<checkbox id="pEpSync"
preference="extensions.pEp.pEpSync"
label-localekey="optionsEnableSync"

@ -1,131 +0,0 @@
// This file is under GNU General Public License 3.0
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");
var { fixIterator } = ChromeUtils.import("resource:///modules/iteratorUtils.jsm");
var { RNP } = ChromeUtils.import("chrome://openpgp/content/modules/RNP.jsm");
var { PgpSqliteDb2 } = ChromeUtils.import("chrome://openpgp/content/modules/sqliteDb.jsm");
var { pEp } = ChromeUtils.import("chrome://pEp4Tb/content/modules/pEp.js");
var ImportRNP = {
getLocaleMessage(key) {
return this.controller.localeMessagesMap.get(key.toLowerCase()) || "";
},
importWithUI(controller, win) {
this.controller = controller;
const flags = Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1 +
Services.prompt.BUTTON_POS_0_DEFAULT;
if (Services.prompt.confirmEx(
win,
this.getLocaleMessage("importRNP1Title"),
this.getLocaleMessage("importRNP1"),
flags,
this.getLocaleMessage("importRNP1Button"),
null, // Cancel. Also chosen when alert window is closed.
null, // no third button.
null, // no checkbox.
{}, // checkbox states.
) != 0) return;
let { priv, pub } = this.controller.synchronise(this.import());
let msg = this.getLocaleMessage("importRNP2");
msg = msg.replace("{0}", priv.toString(10)).replace("{1}", pub.toString(10));
Services.prompt.alert(
win,
this.getLocaleMessage("importRNP2Title"),
msg,
);
},
/* eslint-disable no-await-in-loop */
async import() {
// Since OpenPGP is switched off, we first need to initialise.
await RNP.init();
let priv = 0;
let pub = 0;
let keys = await RNP.getKeys();
for (let key of keys) {
if (key.keyTrust == "r" || key.keyTrust == "e") {
// Skip revoked or expired keys.
this.controller.log.info(`Skipping key with FPR ${key.fpr}, trust=${key.keyTrust}`);
}
this.controller.log.info(`Importing key with FPR ${key.fpr}, private=${key.secretAvailable}`);
try {
if (key.secretAvailable) {
// eslint-disable-next-line prefer-template
let keyData = await RNP.backupSecretKeys(["0x" + key.fpr], "");
let result = await this.controller.importKey(btoa(keyData));
let keyUsed = false;
for (let identity of fixIterator(MailServices.accounts.allIdentities, Ci.nsIMsgIdentity)) {
let idKeyId = Services.prefs.getStringPref(`mail.identity.${identity.key}.openpgp_key_id`, "");
if (idKeyId && key.keyId == idKeyId) {
this.controller.log.info(
`Private key used for ${identity.key}: User "${identity.fullName}" and address "${identity.email}"`,
);
// Use the values from the identity, not the values from the key.
result[0].address = identity.email;
result[0].username = identity.fullName;
result[0].user_id = "pEp_own_userId";
await this.controller.setOwnKey(result[0]);
keyUsed = true;
}
}
if (!keyUsed) {
// Just store the key with all the user IDs.
for (let i = 0; i < result.length; i++) {
this.controller.log.info(`Private key with user "${result[i].username}" and address "${result[i].address}"`);
result[i].user_id = "pEp_own_userId";
await this.controller.setOwnKey(result[i]);
}
}
priv++;
} else {
// eslint-disable-next-line prefer-template
let keyData = await RNP.getPublicKey("0x" + key.fpr);
let result = await this.controller.importKey(btoa(keyData));
let acceptanceResult = {};
await PgpSqliteDb2.getFingerprintAcceptance(null, key.fpr, acceptanceResult);
if (!("fingerprintAcceptance" in acceptanceResult) ||
acceptanceResult.fingerprintAcceptance == "rejected") {
this.controller.log.info(`Non-accepted public key with FPR "${key.fpr}"`);
pub++;
continue;
}
for (let i = 0; i < key.userIds.length; i++) {
let userId = key.userIds[i].userId;
let ind = userId.lastIndexOf("<");
let username;
let email;
if (ind >= 0) {
username = userId.substring(0, ind).trim();
email = userId.substring(ind + 1).replace(/>.*/, "").trim();
} else {
username = "";
email = userId.trim();
}
this.controller.log.info(`Public key with user "${username}" and address "${email}"`);
let id = new pEp.Identity(email, "", username, "");
id = await this.controller.update_identity(id);
id.fpr = key.fpr;
await this.controller.set_identity(id);
}
pub++;
}
} catch (ex) {
this.controller.log.error(ex);
}
}
return { priv, pub };
},
/* eslint-enable no-await-in-loop */
};
const EXPORTED_SYMBOLS = ["ImportRNP"];

@ -24,10 +24,7 @@ class Handshake {
}
async actionResetUser(identity) {
// According to instructions received on 10th Feb. 2021, no FPR should be passed in here.
let copy = { ...identity };
copy.fpr = "";
return this.controller.key_reset_user(copy);
return this.controller.key_reset_user(identity);
}
fullTrustwords(ev, controller, ownIdentity, otherIdentity) {
@ -80,7 +77,7 @@ class Handshake {
}
updateAllIdentities(identitiesArray) {
return Promise.all(identitiesArray.map((identity) => this.controller.update_identity(identity)));
return Promise.all(identitiesArray.map((identity) => this.controller.update_identity(identity, "mockId")));
}
identityRatings(identitiesArray) {

@ -83,9 +83,10 @@ class pEp {
/**
* Function called on send mail
*/
async cache_encrypt_message(message, encodingFormat, isDraftOrTemplate = false) {
async cache_encrypt_message(message, encodingFormat = ENC_FORMAT_PEP, isDraftOrTemplate = false) {
message.dir = DIR_OUTGOING;
let encryptedMessage = await this.adapter.cache_encrypt_message(message, encodingFormat, isDraftOrTemplate);
message.enc_format = encodingFormat;
let encryptedMessage = await this.adapter.cache_encrypt_message(message, isDraftOrTemplate);
return pEp.Message.fromJSON(encryptedMessage);
}
@ -116,21 +117,6 @@ class pEp {
return this.adapter.outgoing_message_rating(message, preview);
}
async getOutgoingRatingWithPartnerInfo(from, to = [], cc) {
this.log.info("getOutgoingRatingWithPartnerInfo: ", from, to, cc);
if (to.length == 0 && (!cc || cc.length == 0)) return { rating: 0, onlyPEP: false };
const msgId = PEP_PREFIX + String(this.requestId++);
const message = new pEp.Message(msgId, "test", "test", from, to);
if (cc) {
message.setCc(cc);
}
return this.adapter.outgoing_message_rating_with_partner_info(message);
}
async cache_mime_decode_message(message) {
return this.adapter.cache_mime_decode_message(message, message.length);
}
@ -153,8 +139,8 @@ class pEp {
return Promise.resolve(myselfPopulated);
}
async identity_rating(identity) {
this.log.info("pEp: identity_rating()", identity);
async identity_rating(email, id, name, fp) {
const identity = new pEp.Identity(email, id, name, fp);
return this.adapter.identity_rating(identity).then((rating) => {
identity.rating = rating;
return identity;
@ -169,35 +155,36 @@ class pEp {
if (!this.languageList.has(language)) language = "en";
}
this.log.info("pEp: get_trustwords()", identity1, identity2);
return this.adapter.get_trustwords(identity1, identity2, language, full);
const user1 = new pEp.Identity(identity1.address, identity1.user_id, identity1.username, identity1.fpr);
const user2 = new pEp.Identity(identity2.address, identity2.user_id, identity2.username, identity2.fpr);
return this.adapter.get_trustwords(user1, user2, language, full);
}
async get_languagelist() {
return this.adapter.get_languagelist();
}
async update_identity(identity) {
this.log.info("pEp: update_identity()", identity);
async update_identity(address, id, name, fingerprint) {
this.log.info("pEp: update_identity()");
const identity = new pEp.Identity(address, id, name, fingerprint);
return this.adapter.update_identity(identity);
}
async set_identity(identity) {
this.log.info("pEp: set_identity()", identity);
return this.adapter.set_identity(identity);
}
async key_reset_trust(identity) {
this.log.info("pEp: key_reset_trust()", identity);
async key_reset_trust(address, id, name, fingerprint) {
this.log.info("pEp: key_reset_trust()");
const identity = new pEp.Identity(address, id, name, fingerprint);
return this.adapter.key_reset_trust(identity);
}
async key_reset_user(identity) {
this.log.info("pEp: key_reset_user()", identity);
async key_reset_user(address, id, name, fingerprint) {
this.log.info("pEp: key_reset_user()");
const identity = new pEp.Identity(address, id, name, fingerprint);
return this.adapter.key_reset_user(identity);
}
async key_reset_identity(identity) {
this.log.info("pEp: key_reset_identity()", identity);
async key_reset_identity(address, name, fingerprint) {
this.log.info("pEp: key_reset_identity()");
const identity = new pEp.Identity(address, "pEp_own_userId", name, fingerprint);
return this.adapter.key_reset_identity(identity);
}
@ -206,18 +193,21 @@ class pEp {
return this.adapter.key_reset_all_own_keys();
}
async trust_personal_key(identity) {
this.log.info("pEp: trust_personal_key()", identity);
async trust_personal_key(address, id, name, fingerprint) {
this.log.info("pEp: trust_personal_key()");
const identity = new pEp.Identity(address, id, name, fingerprint);
return this.adapter.trust_personal_key(identity);
}
async key_mistrusted(identity) {
this.log.info("pEp: key_mistrusted()", identity);
async key_mistrusted(address, id, name, fingerprint) {
this.log.info("pEp: key_mistrusted()");
const identity = new pEp.Identity(address, id, name, fingerprint);
return this.adapter.key_mistrusted(identity);
}
async is_pEp_user(identity) {
this.log.info("pEp: is_pEp_user()", identity);
async is_pEp_user(address, id, name, fingerprint) {
this.log.info("pEp: is_pEp_user()");
const identity = new pEp.Identity(address, id, name, fingerprint);
return this.adapter.is_pEp_user(identity).then((is_pEp) => {
identity.is_pEp = is_pEp;
return identity;

@ -9,14 +9,12 @@ const API_METHOD_CACHE_ENCRYPT_MESSAGE = "cache_encrypt_message";
const API_METHOD_CACHE_ENCRYPT_MESSAGE_FOR_SELF = "cache_encrypt_message_for_self";
const API_METHOD_OUTGOING_MESSAGE_RATING = "outgoing_message_rating";
const API_METHOD_OUTGOING_MESSAGE_RATING_PREVIEW = "outgoing_message_rating_preview";
const API_METHOD_OUTGOING_MESSAGE_RATING_WPI = "outgoing_message_rating_with_partner_info";
const API_METHOD_RE_EVALUATE_MESSAGE_RATING = "re_evaluate_message_rating";
const API_METHOD_IDENTITY_RATING = "identity_rating";
const API_METHOD_CACHE_MIME_DECODE_MESSAGE = "cache_mime_decode_message";
const API_METHOD_CACHE_MIME_ENCODE_MESSAGE = "cache_mime_encode_message";
const API_METHOD_CACHE_RELEASE = "cache_release";
const API_METHOD_MYSELF = "myself";
const API_METHOD_SET_IDENTITY = "set_identity";
const API_METHOD_UPDATE_IDENTITY = "update_identity";
const API_METHOD_IS_PEP_USER = "is_pEp_user";
const API_METHOD_GET_TRUSTWORDS = "get_trustwords";
@ -158,7 +156,7 @@ class pEpAdapter {
async outgoing_message_rating(message, preview) {
this.log.info("pEpAdapter.js: outgoing_message_rating()");
const params = [message, "OUT"];
const params = [message, "0"];
return this.delegateCallPepAdapter(
SERVER_TYPE_CALL_FUNC,
preview ? API_METHOD_OUTGOING_MESSAGE_RATING_PREVIEW : API_METHOD_OUTGOING_MESSAGE_RATING,
@ -175,25 +173,6 @@ class pEpAdapter {
});
}
async outgoing_message_rating_with_partner_info(message) {
this.log.info("pEpAdapter.js: outgoing_message_rating_with_partner_info()");
const params = [message, "OUT", "OUT"];
return this.delegateCallPepAdapter(
SERVER_TYPE_CALL_FUNC,
API_METHOD_OUTGOING_MESSAGE_RATING_WPI,
params,
).then((response) => {
this.log.debug("pEpAdapter.js: outgoing_message_rating_with_partner_info()", response);
if (response.result.return.hasOwnProperty("status") && response.result.return.status != PEP_STATUS_OK) {
this.log.error(`returned status ${response.result.return.status}`);
}
return { rating: response.result.outParams[1].rating, onlyPEP: response.result.outParams[0] };
}).catch((err) => {
this.log.error(err.message);
throw err;
});
}
async re_evaluate_message_rating(message, keyList, oldRating) {
this.log.info("pEpAdapter.js: re_evaluate_message_rating()");
const params = [message, keyList, { rating: oldRating }, "OUT"];
@ -232,25 +211,6 @@ class pEpAdapter {
});
}
async set_identity(identity) {
this.log.info("pEpAdapter.js: set_identity()", identity);
const params = [identity];
return this.delegateCallPepAdapter(
SERVER_TYPE_CALL_FUNC,
API_METHOD_SET_IDENTITY,
params,
).then((response) => {
this.log.debug("pEpAdapter.js: set_identity()", response);
if (response.result.return.hasOwnProperty("status") && response.result.return.status != PEP_STATUS_OK) {
this.log.error(`returned status ${response.result.return.status}`);
}
return null;
}).catch((err) => {
this.log.error(err.message);
throw err;
});
}
async update_identity(identity) {
this.log.info("pEpAdapter.js: update_identity()", identity);
const params = [identity];
@ -640,14 +600,14 @@ class pEpAdapter {
});
}
async cache_encrypt_message(message, encFormat, isDraftOrTemplate) {
async cache_encrypt_message(message, isDraftOrTemplate) {
let params = isDraftOrTemplate ? [message.from] : [];
params = params.concat([
message, // Outgoing encrypted message
[], // Extra keys
["OP"], // Output message
encFormat, // Encoding Format
0, // Flags
message, // Outgoing encrypted message
[], // Extra keys
["OP"], // Output message
message.enc_format, // Encoding Format
0, // Flags
]);
this.log.info("pEpAdapter.js: cache_encrypt_message()", message);
return this.delegateCallPepAdapter(
@ -668,8 +628,6 @@ class pEpAdapter {
this.log.debug("callPepAdapter: 'cache_encrypt_message' returned with success: ", response);
break;
// It's unclear whether anything except `PEP_UNENCRYPTED` will be returned here.
// Looks like `PEP_KEY_HAS_AMBIG_NAME` isn't generated any more (Feb. 2021).
case PEP_GET_KEY_FAILED:
case PEP_UNENCRYPTED:
case PEP_KEY_NOT_FOUND:

@ -14,7 +14,6 @@
* along with pp For Thunderbird. If not, see <https://www.gnu.org/licenses/>.
*/
var { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
var { pEp } = ChromeUtils.import("chrome://pEp4Tb/content/modules/pEp.js");
var { pEpFactory } = ChromeUtils.import("chrome://pEp4Tb/content/pEpFactory.js");
var { TbHelper } = ChromeUtils.import("chrome://pEp4Tb/content/TbHelper.js");
@ -59,10 +58,6 @@ class pEpForThunderbird extends pEp {
this.newMessageKeys = [];
this.pollSession = null;
this.clientID = null;
this.protectSubjects = true;
this.MOZ_APP_VERSION = parseInt(AppConstants.MOZ_APP_VERSION, 10);
this.platform = AppConstants.platform;
// Note that the superclass pEp populates this.logger and this.adapter.
}
@ -106,11 +101,11 @@ class pEpForThunderbird extends pEp {
MailServices.mfn.addListener(this, Ci.nsIMsgFolderNotificationService.msgAdded);
// Check adapter version.
const MIN_REQUIRED_VERSION = "0.22.0";
const MIN_REQUIRED_VERSION = "0.21.0";
let version = this.synchronise(this.serverVersion());
this.log.info(`Adapter version ${version.api_version} (${version.name})`);
let win = Services.wm.getMostRecentWindow("mail:3pane");
if (Services.vc.compare(version.api_version, MIN_REQUIRED_VERSION) < 0) {
let win = Services.wm.getMostRecentWindow("mail:3pane");
Services.prompt.alert(
win,
this.localeMessagesMap.get("alerttitle"),
@ -129,25 +124,9 @@ class pEpForThunderbird extends pEp {
// Setup protect subjects at startup
if (Services.prefs.getBoolPref("extensions.pEp.protectSubjects", true)) {
this.enableprotectSubjects();
this.protectSubjects = true;
} else {
this.disableprotectSubjects();
this.protectSubjects = false;
}
// Import keys from RNP starting at Thunderbird 78.
if (this.MOZ_APP_VERSION >= 78 &&
Services.prefs.getBoolPref("extensions.pEp.importRNP", false)) {
let { ImportRNP } = ChromeUtils.import("chrome://pEp4Tb/content/importRNP.js");
for (let identity of fixIterator(MailServices.accounts.allIdentities, Ci.nsIMsgIdentity)) {
if (Services.prefs.getStringPref(`mail.identity.${identity.key}.openpgp_key_id`, "")) {
// Looks like OpenPGP was configured, so let's import.
ImportRNP.importWithUI(this, win);
break;
}
}
}
Services.prefs.setBoolPref("extensions.pEp.importRNP", false);
// The following doesn't start the Sync, it just sets up polling, etc.
TbSync.setupSync(this);
@ -246,11 +225,7 @@ class pEpForThunderbird extends pEp {
this.searchListener = {
onSearchHit(msgHdr, folder) {
if (new Date(Date.now() - msgHdr.date / 1000) >= 600 * 1000) {
if (this.MOZ_APP_VERSION >= 85) {
this.msgs.push(msgHdr);
} else {
this.msgs.appendElement(msgHdr);
}
this.msgs.appendElement(msgHdr);
this.count++;
}
},
@ -259,11 +234,7 @@ class pEpForThunderbird extends pEp {
if (this.count) inbox.deleteMessages(this.msgs, null, true, false, null, false);
},
onNewSearch() {
if (this.MOZ_APP_VERSION >= 85) {
this.msgs = [];
} else {
this.msgs = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
}
this.msgs = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
this.count = 0;
},
};
@ -286,20 +257,11 @@ class pEpForThunderbird extends pEp {
if (!pEpFolder) continue;
// Check for message older than 10 minutes.
let msgs;
if (this.MOZ_APP_VERSION >= 85) {
msgs = [];
} else {
msgs = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
}
let msgs = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
let count = 0;
for (let msgHdr of pEpFolder.messages) {
if (new Date(Date.now() - msgHdr.date / 1000) < 600 * 1000) continue;
if (this.MOZ_APP_VERSION >= 85) {
msgs.push(msgHdr);
} else {
msgs.appendElement(msgHdr);
}
msgs.appendElement(msgHdr);
count++;
}
this.log.info(`Deleting ${count} message(s) from pEp folder of ${msgRoot.name}`);
@ -475,13 +437,8 @@ class pEpForThunderbird extends pEp {
moveMsg(folder, msgHdr, pEpFolder) {
this.movedSyncMessages.push(msgHdr.messageId);
let message;
if (this.MOZ_APP_VERSION >= 85) {
message = [msgHdr];
} else {
message = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
message.appendElement(msgHdr);
}
let message = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
message.appendElement(msgHdr);
// isMove== true, no listener or window, allowUndo==false.
MailServices.copy.CopyMessages(folder, message, pEpFolder, true, null, null, false);
}
@ -524,7 +481,6 @@ class pEpForThunderbird extends pEp {
if (tree) tree.invalidate();
if (isSyncMsg) {
this.log.info("Processing Sync messsage");
this.cache_release(decryptedMessage.id);
// Mark the message read.
@ -545,7 +501,8 @@ class pEpForThunderbird extends pEp {
if (!folder.containsChildNamed("pEp")) {
try {
let self = this;
// For IMAP this is async, so attach listener before creating the folder.
folder.createSubfolder("pEp", null);
// For IMAP this is async.
MailServices.mfn.addListener(
{
folderAdded(childFolder) {
@ -554,13 +511,11 @@ class pEpForThunderbird extends pEp {
}
MailServices.mfn.removeListener(this);
childFolder.setFlag(Ci.nsMsgFolderFlags.CheckNew);
self.log.info("Moving Sync messsage to pEp folder after creating it");
self.moveMsg(folder, msgHdr, childFolder);
},
},
MailServices.mfn.folderAdded,
);
folder.createSubfolder("pEp", null);
} catch (ex) {}
return;
}
@ -569,7 +524,6 @@ class pEpForThunderbird extends pEp {
try {
let pEpFolder = folder.getChildNamed("pEp");
pEpFolder.setFlag(Ci.nsMsgFolderFlags.CheckNew);
this.log.info("Moving Sync messsage to pEp folder now");
this.moveMsg(folder, msgHdr, pEpFolder);
} catch (ex) {}
return;
@ -681,11 +635,10 @@ class pEpForThunderbird extends pEp {
// Copy some headers from the original to the new message.
// Starting with https://pep-security.lu/dev/repos/pEp_for_Thunderbird/rev/d3c752abce8c
// on 7th Sep 2020 in verion 1.1.006-beta we store the references and in-reply-to also
// in the inner message. Starting on 18th Feb. 2021 in version 1.1.101 we store the
// messageId in the inner message.
// we store the references and in-reply-to also in the inner message.
// Sadly at encryption time, the message ID is not yet available, so it can't be used
// as ID for the inner message :-(
if (this.currentMessage.enc_format == ENC_FORMAT_DECRYPTED) {
// Remove this code soon: Begin >>>>>
if (message.id) this.currentMessage.id = message.id;
// Only use original in_reply_to if decrypted (inner) message has none.
@ -699,7 +652,6 @@ class pEpForThunderbird extends pEp {
(typeof this.currentMessage.references !== "object" || this.currentMessage.references.length == 0)) {
this.currentMessage.references = message.references;
}
// Remove this code soon: End <<<<<
// Append decryption details to internet headers in original opt_fields.
if (message.opt_fields) {

@ -40,7 +40,7 @@ MimeDecrypt.prototype = {
},
onStopRequest(request, status) {
if (this.uri && this.uri.spec.match(/[?&]filename=.*\.eml(&|$)/) &&
if (this.uri && this.uri.spec.includes("filename=") &&
!this.uri.spec.includes("type=application/x-message-display")) {
// This is a save operation of a message/rfc822 attachment. Just return the original text.
this.proxy.outputDecryptedData(this.inBuffer, this.inBuffer.length);

@ -42,10 +42,8 @@ MimeEncrypt.prototype = {
bccRecipients: null,
replyToRecipients: null,
originalSubject: null,
references: null,
messageId: null,
originalReferences: null,
isDraftOrTemplate: false,
requireEncryption: false,
passThrough: false,
// Private variables.
@ -56,23 +54,16 @@ MimeEncrypt.prototype = {
outBuffer: "",
// nsIMsgComposeSecure interface
requiresCryptoEncapsulation(msgIdentity, msgCompFields) {
requiresCryptoEncapsulation() {
// Only if our handler was registered in pepmessengercompose.js,
// we get here and we require encryption.
// Note that if `msgCompFields.subject` is set here, it will affect the
// subject in the message that is sent, but that is too early for us.
// Setting it any later has no effect.
// So we set the subject even earlier in pepmessengercompose.js.
return true;
},
beginCryptoEncapsulation(outStream, recipientList, msgCompFields, msgIdentity, sendReport, isDraft) {
// Note that `isDraft` is false when saving as a template, so this is not useful.
beginCryptoEncapsulation(outStream, recipientList, compFields, msgIdentity, sendReport, isDraft) {
this.outStream = outStream;
this.outStringStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
this.inspector = Cc["@mozilla.org/jsinspector;1"].createInstance(Ci.nsIJSInspector);
this.references = msgCompFields.references;
this.messageId = msgCompFields.messageId;
return null;
},
@ -119,31 +110,25 @@ MimeEncrypt.prototype = {
let message = this.controller.synchronise(messagePromise);
// Fill in some headers.
message.id = this.messageId.replace(/^<(.*)>$/, "$1");
message.shortmsg = this.originalSubject;
message.from = this.fromSender;
message.to = this.toRecipients;
message.cc = this.ccRecipients;
message.bcc = this.bccRecipients;
message.reply_to = this.replyToRecipients;
if (this.references) {
message.references = this.references.split(" ").map((r) => r.replace(/^<(.*)>$/, "$1"));
if (this.originalReferences) {
message.references = this.originalReferences.split(" ").map((r) => r.replace(/^<(.*)>$/, "$1"));
message.in_reply_to = [message.references[message.references.length - 1]];
}
let format = this.requireEncryption ? ENC_FORMAT_PEP : ENC_FORMAT_NONE;
let encryptedPromise = this.controller.cache_encrypt_message(message, format, this.isDraftOrTemplate);
// BCC: Temporary measure while engine is not handling Bcc.
let encryptedPromise = this.controller.cache_encrypt_message(message,
(message.bcc && !this.isDraftOrTemplate) ? ENC_FORMAT_NONE : ENC_FORMAT_PEP,
this.isDraftOrTemplate);
let encryptedMessage = this.controller.synchronise(encryptedPromise);
if (format != ENC_FORMAT_NONE && encryptedMessage.enc_format == ENC_FORMAT_NONE) {
// Something went badly wrong here, we asked for encryption and didn't get it.
throw new Error("Message not encrypted although encryption was requested");
}
// The pEp engine assigns a new message ID to the encrypted message, but Thunderbird
// ignores that. So `encryptedMessage.id = message.id;` has no effect here.
// The desired message ID is already in the inner message.
// If it wasn't encrypted after all (ENC_FORMAT_NONE), get the source, otherwise the destination.
encryptedMessage.id = message.id; // Use the same ID, so we can access the message from the cache.
let mimePromise = this.controller.cache_mime_encode_message(encryptedMessage.enc_format == ENC_FORMAT_NONE ? 0 : 1,
encryptedMessage, true);
this.outBuffer = this.controller.synchronise(mimePromise);

@ -1,5 +1,6 @@
// This file is under GNU General Public License 3.0
var { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var { pEp } = ChromeUtils.import("chrome://pEp4Tb/content/modules/pEp.js");
var { pEpController } = ChromeUtils.import("chrome://pEp4Tb/content/pEpController.js");
@ -30,7 +31,7 @@ function _parseAddress(address) {
function isPrivate(rating) {
// starting from `PEP_rating_reliable` in
// `_PEP_rating` in the engine message_api.h.
return rating && rating >= 6;
return rating && rating > 5;
}
// Needs to be a `var` since it's used in bootstrap.js.
@ -54,9 +55,9 @@ var pEpComposer = {
pEpController.init(localeMessagesMap);
// The new recipient pills started in TB 74.
this.preTB74 = pEpController.MOZ_APP_VERSION < 74;
this.preTB74 = parseInt(AppConstants.MOZ_APP_VERSION, 10) < 74;
if (pEpController.MOZ_APP_VERSION >= 77) {
if (parseInt(AppConstants.MOZ_APP_VERSION, 10) >= 77) {
// Restore old S/MIME button.
let button_security = win.document.getElementById("button-security");
if (button_security) button_security.setAttribute("label", "S/MIME");
@ -333,13 +334,7 @@ var pEpComposer = {
// Remove special pEp attachments.
const msgCompFields = this.win.gMsgCompose.compFields;
let bucket;
// This changed in https://bugzilla.mozilla.org/show_bug.cgi?id=1688331.
if (pEpController.MOZ_APP_VERSION >= 87) {
bucket = this.win.gAttachmentBucket;
} else {
bucket = this.win.GetMsgAttachmentElement();
}
let bucket = this.win.GetMsgAttachmentElement();
if (bucket) {
let removedCount = 0;
let rowCount = bucket.getRowCount();
@ -483,8 +478,7 @@ var pEpComposer = {
// from (auto-)saving an encrypted draft.
const compFields = this.win.gMsgCompose.compFields;
// From TB 78 onwards we need to fill with a real object.
compFields.composeSecure =
Cc["@mozilla.org/messengercompose/composesecure;1"].createInstance(Ci.nsIMsgComposeSecure);
compFields.composeSecure = this.win.gSMFields;
// Check that when (auto-)saving a draft or template, we check whether
// to trust the server and not encrypt.
@ -544,27 +538,18 @@ var pEpComposer = {
this.getAddresses(this.win.gCurrentIdentity, compFields);
compSec.isDraftOrTemplate = isDraftOrTemplate;
compSec.originalSubject = compFields.subject;
// We deal with messageId and references in pEpMimeEncrypt.js.
// Note that `compFields.messageId` is empty at this point in time.
compSec.originalReferences = compFields.references;
compFields.composeSecure = compSec;
// For a draft we always encrypt since encrypt_message_for_self() rejects PEP_enc_none.
if (isPrivate(this.rating) || isDraftOrTemplate) {
compSec.requireEncryption = true;
// This is a hack. We tell the engine whether we want protected subjects,
// but when we pass the MIME tree back in pEpMimeEncrypt.js, the engine-provided
// headers are just ignored. So we manipulate the header here manually which isn't ideal.
if (pEpController.protectSubjects) {
// We unconditionally replace the subject.
compFields.subject = PEP_ENCRYPTED_SUBJECT;
} else {
// We need to check whether there are any non-pEp comm partners.
let { dummy, onlyPEP } = pEpController.synchronise(
// eslint-disable-next-line comma-dangle
pEpController.getOutgoingRatingWithPartnerInfo(compSec.fromSender, compSec.toRecipients, compSec.ccRecipients)
);
if (onlyPEP) compFields.subject = PEP_ENCRYPTED_SUBJECT;
}
// This is a hack. We tell the engine whether we want protected subjects,
// but when we pass the MIME tree back in pEpMimeEncrypt.js, the engine-provided
// headers are just ignored. So we manipulate the header here manually which isn't ideal.
// If we encrypt, `isPrivate(this.rating)` is true, we always hide the subject, even if
// the user chose not to protect subjects, since the subject is transmitted in the
// inner message and not the visible headers.
if (isPrivate(this.rating)) {
compFields.subject = PEP_ENCRYPTED_SUBJECT;
}
}
},

@ -1,5 +1,6 @@
// This file is under GNU General Public License 3.0
var { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var { pEp } = ChromeUtils.import("chrome://pEp4Tb/content/modules/pEp.js");
var { pEpController } = ChromeUtils.import("chrome://pEp4Tb/content/pEpController.js");
@ -121,7 +122,7 @@ const columnOverlay = {
for (let attribute of attributes) {
let value = Services.xulStore.getValue(this.win.document.URL, PEP_COLUMN_NAME, attribute);
// See Thunderbird bug 1607575 and bug 1612055.
if (attribute != "ordinal" || pEpController.MOZ_APP_VERSION < 74) {
if (attribute != "ordinal" || parseInt(AppConstants.MOZ_APP_VERSION, 10) < 74) {
treeCol.setAttribute(attribute, value);
} else {
treeCol.ordinal = value;
@ -191,7 +192,7 @@ var pEpHdrView = {
if (win.location.href.startsWith("chrome://messenger/content/messenger.")) {
// Add options menu after account settings (and before the normal options
// on Windows).
let label = pEpController.platform == "win" ? "optionsLabelWin" : "optionsLabel";
let label = AppConstants.platform == "win" ? "optionsLabelWin" : "optionsLabel";
label = this.getLocaleMessage(label);
/* eslint-disable max-len */
let xul = win.MozXULElement.parseXULToFragment(`
@ -209,8 +210,8 @@ var pEpHdrView = {
/* eslint-enable max-len */
let menuAccountmgr = win.document.getElementById("menu_accountmgr");
menuAccountmgr.parentNode.insertBefore(xul, menuAccountmgr.nextSibling);
let appmenuPreferences = win.document.getElementById("appmenu_preferences");
appmenuPreferences.parentNode.insertBefore(xul2, appmenuPreferences.nextSibling);
let appmenuCustomize = win.document.getElementById("appmenu_customize");
appmenuCustomize.parentNode.insertBefore(xul2, appmenuCustomize);
}
},
@ -449,8 +450,7 @@ var pEpHdrView = {
}
pEpController.log.debug(
// eslint-disable-next-line quotes, max-len
`updatePrivacy: folder="${msgHdr.folder ? msgHdr.folder.name : '(no folder)'}", key=${msgHdr.messageKey}, rating=${rating}, keylist=${keyList}`,
`updatePrivacy: folder="${msgHdr.folder.name}", key=${msgHdr.messageKey}, rating=${rating}, keylist=${keyList}`,
);
if (!rating || rating == 3) {
@ -518,8 +518,7 @@ var pEpHdrView = {
} else {
// Maybe this was sent to me being part of a mailing list.
for (let addr of allAdresses) {
let id = new pEp.Identity(addr.email, "", addr.name, "");
id = pEpController.synchronise(pEpController.update_identity(id));
let id = pEpController.synchronise(pEpController.update_identity(addr.email, "", addr.name, ""));
if (id.me) {
id.username = addr.name; // Engine invents some name if it was empty.
pEpMyself = id;

@ -104,9 +104,10 @@ function makeSender(compFields) {
return { send() {} };
}
// Terminate body to prevent SMTP error in case no attachments follow.
// We used to check for attachments, but we can do it unconditionally.
compFields.body += "\r\n";
// Terminate body to prevent SMTP error if no attachments follow.
if (!compFields.attachments.hasMoreElements()) {
compFields.body += "\r\n";
}
return {
send() {
console.info(`Sending message from ${compFields.from} to ${compFields.to}`);
@ -127,6 +128,8 @@ function makeSender(compFields) {
let sendMessage = {
messageToCompFields,
makeSender,
locationForContent,
convertAttachment,
};
const EXPORTED_SYMBOLS = ["sendMessage"];

@ -57,7 +57,6 @@ function setPrefs(prefBranch) {
searchEncrypted: true,
showTutorial: true,
showFPR: false,
importRNP: false, // Will be set by the installer.
},
String: {
// keyFingerprint: "",
@ -108,25 +107,6 @@ var pEp4Tb = class extends ExtensionCommon.ExtensionAPI {
if (Services.prefs.getBoolPref("extensions.pEp.searchEncrypted", true)) {
GlodaMsgIndexer._MsgHdrToMimeMessageFunc = pEpMsgHdrToMimeMessage;
}
// Make sure the pEp compose button is visible.
let composeURL;
if (parseInt(AppConstants.MOZ_APP_VERSION, 10) >= 73) {
composeURL = "chrome://messenger/content/messengercompose/messengercompose.xhtml";
} else {
composeURL = "chrome://messenger/content/messengercompose/messengercompose.xul";
}
let set = Services.xulStore.getValue(composeURL, "composeToolbar2", "currentset");
if (set && !set.includes("pep4tb_pep_security-composeAction-toolbarbutton")) {
if (set.includes("spring,button-attach")) {
set = set.replace("spring,button-attach", "pep4tb_pep_security-composeAction-toolbarbutton,spring,button-attach");
} else if (set == "__empty") {
set = "pep4tb_pep_security-composeAction-toolbarbutton";
} else {
set += ",pep4tb_pep_security-composeAction-toolbarbutton";
}
Services.xulStore.setValue(composeURL, "composeToolbar2", "currentset", set);
}
}
onShutdown(isAppShutdown) {
@ -159,7 +139,6 @@ var pEp4Tb = class extends ExtensionCommon.ExtensionAPI {
Cu.unload("chrome://pEp4Tb/content/pEpMimeEncrypt.js");
Cu.unload("chrome://pEp4Tb/content/TbContacts.js");
Cu.unload("chrome://pEp4Tb/content/TbHelper.js");
Cu.unload("chrome://pEp4Tb/content/importRNP.js");
// Sync.
Cu.unload("chrome://pEp4Tb/content/sendMessage.js");
Cu.unload("chrome://pEp4Tb/content/syncWizard.js");