diff --git a/package/Makefile b/package/Makefile index 407e374d..574815dd 100644 --- a/package/Makefile +++ b/package/Makefile @@ -1,111 +1,112 @@ # 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/. DEPTH = .. include $(DEPTH)/config/autoconf.mk PREF_JS_EXPORTS = $(srcdir)/prefs/enigmail.js COMPFILES = \ enigmail.js \ pgpmimeHandler.js \ mimeEncrypt.js \ prefs-service.js \ msgCompFields.js PREFFILES = prefs/enigmail.js MODFILES = \ addrbook.jsm \ app.jsm \ armor.jsm \ attachment.jsm \ autocrypt.jsm \ card.jsm \ clipboard.jsm \ commandLine.jsm \ configBackup.jsm \ configure.jsm \ constants.jsm \ core.jsm \ data.jsm \ decryption.jsm \ decryptPermanently.jsm \ + encryptPermanently.jsm \ dialog.jsm \ encryption.jsm \ errorHandling.jsm \ events.jsm \ execution.jsm \ funcs.jsm \ files.jsm \ filters.jsm \ fixExchangeMsg.jsm \ gpgAgent.jsm \ glodaMime.jsm \ glodaUtils.jsm \ gpg.jsm \ hash.jsm \ httpProxy.jsm \ installGnuPG.jsm \ installPep.jsm \ key.jsm \ keyEditor.jsm \ keyRing.jsm \ keyUsability.jsm \ keyRefreshService.jsm \ keyserver.jsm \ keyserverUris.jsm \ lazy.jsm \ locale.jsm \ log.jsm \ mime.jsm \ mimeDecrypt.jsm \ mimeVerify.jsm \ os.jsm \ openpgp.jsm \ passwordCheck.jsm \ passwords.jsm \ pEp.jsm \ pEpAdapter.jsm \ pEpDecrypt.jsm \ pEpFilter.jsm \ pEpListener.jsm \ pEpKeySync.jsm \ pipeConsole.jsm \ prefs.jsm \ protocolHandler.jsm \ rng.jsm \ rules.jsm \ send.jsm \ socks5Proxy.jsm \ stdlib.jsm \ streams.jsm \ system.jsm \ time.jsm \ timer.jsm \ tor.jsm \ trust.jsm \ uris.jsm \ verify.jsm \ versioning.jsm \ webKey.jsm \ wkdLookup.jsm \ windows.jsm \ wksMimeHandler.jsm \ zbase32.jsm all: deploy deploy: $(PREFFILES) $(COMPFILES) $(MODFILES) $(DEPTH)/util/install -m 644 $(DIST)/components $(COMPFILES) $(DEPTH)/util/install -m 644 $(DIST)/defaults/preferences $(PREFFILES) $(DEPTH)/util/install -m 644 $(DIST)/modules $(MODFILES) clean: $(DEPTH)/util/install -u $(DIST)/components $(COMPFILES) $(DEPTH)/util/install -u $(DIST)/defaults/preferences $(PREFFILES) $(DEPTH)/util/install -u $(DIST)/modules $(MODFILES) diff --git a/package/constants.jsm b/package/constants.jsm index 3f6f427e..93967c34 100644 --- a/package/constants.jsm +++ b/package/constants.jsm @@ -1,54 +1,55 @@ /*global Components: false */ /*jshint -W097 */ /* * 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/. */ "use strict"; var EXPORTED_SYMBOLS = ["EnigmailConstants"]; const EnigmailConstants = { POSSIBLE_PGPMIME: -2081, // possible values for // - encryptByRule, signByRules, pgpmimeByRules // - encryptForced, signForced, pgpmimeForced (except CONFLICT) // NOTE: // - values 0/1/2 are used with this fixed semantics in the persistent rules // - see also enigmailEncryptionDlg.xul ENIG_NEVER: 0, ENIG_UNDEF: 1, ENIG_ALWAYS: 2, ENIG_FORCE_SMIME: 3, ENIG_AUTO_ALWAYS: 22, ENIG_CONFLICT: 99, ENIG_FINAL_UNDEF: -1, ENIG_FINAL_NO: 0, ENIG_FINAL_YES: 1, ENIG_FINAL_FORCENO: 10, ENIG_FINAL_FORCEYES: 11, ENIG_FINAL_SMIME: 97, // use S/MIME (automatically chosen) ENIG_FINAL_FORCESMIME: 98, // use S/MIME (forced by user) ENIG_FINAL_CONFLICT: 99, MIME_HANDLER_UNDEF: 0, MIME_HANDLER_SMIME: 1, MIME_HANDLER_PGPMIME: 2, ICONTYPE_INFO: 1, ICONTYPE_QUESTION: 2, ICONTYPE_ALERT: 3, ICONTYPE_ERROR: 4, FILTER_MOVE_DECRYPT: "enigmail@enigmail.net#filterActionMoveDecrypt", FILTER_COPY_DECRYPT: "enigmail@enigmail.net#filterActionCopyDecrypt", + FILTER_ENCRYPT: "enigmail@enigmail.net#filterActionEncrypt", nsIEnigmail: Components.interfaces.nsIEnigmail }; diff --git a/package/decryptPermanently.jsm b/package/decryptPermanently.jsm index 296b5866..55ef0a8b 100644 --- a/package/decryptPermanently.jsm +++ b/package/decryptPermanently.jsm @@ -1,1254 +1,1256 @@ /*global Components: false*/ /*jshint -W097 */ /* * 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/. */ "use strict"; var EXPORTED_SYMBOLS = ["EnigmailDecryptPermanently"]; const Cu = Components.utils; Cu.import("resource://enigmail/lazy.jsm"); /*global EnigmailLazy: false */ Cu.import("resource://gre/modules/AddonManager.jsm"); /*global AddonManager: false */ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); /*global XPCOMUtils: false */ Cu.import("resource://enigmail/log.jsm"); /*global EnigmailLog: false */ Cu.import("resource://enigmail/armor.jsm"); /*global EnigmailArmor: false */ Cu.import("resource://enigmail/locale.jsm"); /*global EnigmailLocale: false */ Cu.import("resource://enigmail/execution.jsm"); /*global EnigmailExecution: false */ Cu.import("resource://enigmail/dialog.jsm"); /*global EnigmailDialog: false */ Cu.import("resource://enigmail/glodaUtils.jsm"); /*global GlodaUtils: false */ Cu.import("resource:///modules/MailUtils.js"); /*global MailUtils: false */ Cu.import("resource://enigmail/core.jsm"); /*global EnigmailCore: false */ Cu.import("resource://enigmail/gpg.jsm"); /*global EnigmailGpg: false */ Cu.import("resource://enigmail/streams.jsm"); /*global EnigmailStreams: false */ Cu.import("resource://enigmail/passwords.jsm"); /*global EnigmailPassword: false */ Cu.import("resource://enigmail/mime.jsm"); /*global EnigmailMime: false */ Cu.import("resource://enigmail/data.jsm"); /*global EnigmailData: false */ Cu.import("resource://enigmail/attachment.jsm"); /*global EnigmailAttachment: false */ Cu.import("resource://enigmail/timer.jsm"); /*global EnigmailTimer: false */ Cu.import("resource:///modules/jsmime.jsm"); /*global jsmime: false*/ /*global MimeBody: false, MimeUnknown: false, MimeMessageAttachment: false */ /*global msgHdrToMimeMessage: false, MimeMessage: false, MimeContainer: false */ Cu.import("resource://enigmail/glodaMime.jsm"); const getGpgAgent = EnigmailLazy.loader("enigmail/gpgAgent.jsm", "EnigmailGpgAgent"); var EC = EnigmailCore; const Cc = Components.classes; const Ci = Components.interfaces; const nsIEnigmail = Components.interfaces.nsIEnigmail; const STATUS_OK = 0; const STATUS_FAILURE = 1; const STATUS_NOT_REQUIRED = 2; const IOSERVICE_CONTRACTID = "@mozilla.org/network/io-service;1"; /* * Decrypt a message and copy it to a folder * * @param nsIMsgDBHdr hdr Header of the message * @param String destFolder Folder URI * @param Boolean move If true the original message will be deleted * * @return a Promise that we do that */ const EnigmailDecryptPermanently = { /*** * dispatchMessages * * Because Thunderbird throws all messages at once at us thus we have to rate limit the dispatching * of the message processing. Because there is only a negligible performance gain when dispatching * several message at once we serialize to not overwhelm low power devices. * * The function is implemented asynchronously. * * Parameters * aMsgHdrs: Array of nsIMsgDBHdr * targetFolder: String; target folder URI * copyListener: listener for async request (nsIMsgCopyServiceListener) * move: Boolean: type of action; true = "move" / false = "copy" * **/ dispatchMessages: function(aMsgHdrs, targetFolder, copyListener, move) { EnigmailLog.DEBUG("decryptPermanently.jsm: dispatchMessages()\n"); if (copyListener) { copyListener.OnStartCopy(); } let promise = EnigmailDecryptPermanently.decryptMessage(aMsgHdrs[0], targetFolder, move); var processNext = function(data) { aMsgHdrs.splice(0, 1); if (aMsgHdrs.length > 0) { EnigmailDecryptPermanently.dispatchMessages(aMsgHdrs, targetFolder, move); } else { // last message was finished processing if (copyListener) { copyListener.OnStopCopy(0); } EnigmailLog.DEBUG("decryptPermanently.jsm: dispatchMessages - DONE\n"); } }; promise.then(processNext); promise.catch(function(err) { processNext(null); }); }, decryptMessage: function(hdr, destFolder, move) { return new Promise( function(resolve, reject) { let msgUriSpec = hdr.folder.getUriForMsg(hdr); const msgSvc = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger).messageServiceFromURI(msgUriSpec); const decrypt = new DecryptMessageIntoFolder(destFolder, move, resolve); try { msgHdrToMimeMessage(hdr, decrypt, decrypt.messageParseCallback, true, { examineEncryptedParts: false, partsOnDemand: false }); } catch (ex) { reject("msgHdrToMimeMessage failed"); } return; } ); } }; function DecryptMessageIntoFolder(destFolder, move, resolve) { this.destFolder = destFolder; this.move = move; this.resolve = resolve; this.foundPGP = 0; this.mime = null; this.hdr = null; this.decryptionTasks = []; this.subject = ""; } DecryptMessageIntoFolder.prototype = { messageParseCallback: function(hdr, mime) { this.hdr = hdr; this.mime = mime; var self = this; try { if (!mime) { this.resolve(true); return; } if (!("content-type" in mime.headers)) { mime.headers["content-type"] = ["text/plain"]; } var ct = getContentType(getHeaderValue(mime, 'content-type')); var pt = getProtocol(getHeaderValue(mime, 'content-type')); this.subject = GlodaUtils.deMime(getHeaderValue(mime, 'subject')); if (!ct) { this.resolve(true); return; } this.walkMimeTree(this.mime, this.mime); this.decryptINLINE(this.mime); if (this.foundPGP < 0) { // decryption failed this.resolve(true); return; } for (let i in this.mime.allAttachments) { let a = this.mime.allAttachments[i]; let suffixIndexEnd = a.name.toLowerCase().lastIndexOf('.pgp'); if (suffixIndexEnd < 0) { suffixIndexEnd = a.name.toLowerCase().lastIndexOf('.asc'); } if (suffixIndexEnd > 0 && a.contentType.search(/application\/pgp-signature/i) < 0) { // possible OpenPGP attachment let p = self.decryptAttachment(a, a.name.substring(0, suffixIndexEnd)); this.decryptionTasks.push(p); } else { let p = this.readAttachment(a); this.decryptionTasks.push(p); } } Promise.all(this.decryptionTasks).then( function(tasks) { self.allTasks = tasks; for (let a in tasks) { switch (tasks[a].status) { case STATUS_NOT_REQUIRED: tasks[a].name = tasks[a].origName; break; case STATUS_OK: ++self.foundPGP; break; case STATUS_FAILURE: // attachment did not decrypt successfully self.resolve(true); return; default: // no valid result?! tasks[a].name = tasks[a].origName; } } if (self.foundPGP === 0) { self.resolve(true); return; } var msg = self.mimeToString(self.mime, true); if (!msg || msg === "") { // no message data found self.resolve(true); return; } //XXX Do we wanna use the tmp for this? var tempFile = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile); tempFile.append("message.eml"); tempFile.createUnique(0, 384); // == 0600, octal is deprecated // ensure that file gets deleted on exit, if something goes wrong ... var extAppLauncher = Cc["@mozilla.org/mime;1"].getService(Ci.nsPIExternalAppLauncher); var foStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream); foStream.init(tempFile, 2, 0x200, false); // open as "write only" foStream.write(msg, msg.length); foStream.close(); extAppLauncher.deleteTemporaryFileOnExit(tempFile); // // This was taken from the HeaderToolsLite Example Addon "original by Frank DiLecce" // // this is interesting: nsIMsgFolder.copyFileMessage seems to have a bug on Windows, when // the nsIFile has been already used by foStream (because of Windows lock system?), so we // must initialize another nsIFile object, pointing to the temporary file var fileSpec = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); fileSpec.initWithPath(tempFile.path); const copySvc = Cc["@mozilla.org/messenger/messagecopyservice;1"].getService(Ci.nsIMsgCopyService); var copyListener = { + newMessageKey: null, QueryInterface: function(iid) { if (iid.equals(Ci.nsIMsgCopyServiceListener) || iid.equals(Ci.nsISupports)) { return this; } EnigmailLog.DEBUG("decryptPermanently.jsm: copyListener error\n"); throw Components.results.NS_NOINTERFACE; }, GetMessageId: function(messageId) {}, OnProgress: function(progress, progressMax) {}, OnStartCopy: function() { EnigmailLog.DEBUG("decryptPermanently.jsm: copyListener: OnStartCopy()\n"); }, SetMessageKey: function(key) { EnigmailLog.DEBUG("decryptPermanently.jsm: copyListener: SetMessageKey(" + key + ")\n"); + copyListener.newMessageKey = key; }, OnStopCopy: function(statusCode) { EnigmailLog.DEBUG("decryptPermanently.jsm: copyListener: OnStopCopy()\n"); if (statusCode !== 0) { EnigmailLog.DEBUG("decryptPermanently.jsm: Error copying message: " + statusCode + "\n"); try { tempFile.remove(false); } catch (ex) { try { fileSpec.remove(false); } catch (e2) { EnigmailLog.DEBUG("decryptPermanently.jsm: Could not delete temp file\n"); } } - self.resolve(true); + self.resolve(copyListener.newMessageKey); return; } EnigmailLog.DEBUG("decryptPermanently.jsm: Copy complete\n"); if (self.move) { deleteOriginalMail(self.hdr); } try { tempFile.remove(false); } catch (ex) { try { fileSpec.remove(false); } catch (e2) { EnigmailLog.DEBUG("decryptPermanently.jsm: Could not delete temp file\n"); } } EnigmailLog.DEBUG("decryptPermanently.jsm: Cave Johnson. We're done\n"); - self.resolve(true); + self.resolve(copyListener.newMessageKey); } }; EnigmailLog.DEBUG("decryptPermanently.jsm: copySvc ready for copy\n"); try { if (self.mime.headers.subject) { self.hdr.subject = self.mime.headers.subject.join(); } } catch (ex) {} copySvc.CopyFileMessage(fileSpec, MailUtils.getFolderForURI(self.destFolder, false), self.hdr, false, 0, "", copyListener, null); } ).catch( function catchErr(errorMsg) { EnigmailLog.DEBUG("decryptPermanently.jsm: Promise.catchErr: " + errorMsg + "\n"); self.resolve(false); } ); } catch (ex) { EnigmailLog.DEBUG("decryptPermanently.jsm: messageParseCallback: caught error " + ex.toString() + "\n"); self.resolve(false); } }, readAttachment: function(attachment, strippedName) { return new Promise( function(resolve, reject) { EnigmailLog.DEBUG("decryptPermanently.jsm: readAttachment\n"); let o; var f = function _cb(data) { EnigmailLog.DEBUG("decryptPermanently.jsm: readAttachment - got data (" + data.length + ")\n"); o = { type: "attachment", data: data, name: strippedName ? strippedName : attachment.name, partName: attachment.partName, origName: attachment.name, status: STATUS_NOT_REQUIRED }; resolve(o); }; try { var bufferListener = EnigmailStreams.newStringStreamListener(f); var ioServ = Cc[IOSERVICE_CONTRACTID].getService(Components.interfaces.nsIIOService); var msgUri = ioServ.newURI(attachment.url, null, null); var channel = EnigmailStreams.createChannelFromURI(msgUri); channel.asyncOpen(bufferListener, msgUri); } catch (ex) { reject(o); } } ); }, decryptAttachment: function(attachment, strippedName) { var self = this; return new Promise( function(resolve, reject) { EnigmailLog.DEBUG("decryptPermanently.jsm: decryptAttachment\n"); self.readAttachment(attachment, strippedName).then( function(o) { var attachmentHead = o.data.substr(0, 30); if (attachmentHead.match(/-----BEGIN PGP \w+ KEY BLOCK-----/)) { // attachment appears to be a PGP key file, we just go-a-head resolve(o); return; } var enigmailSvc = EnigmailCore.getService(); var args = EnigmailGpg.getStandardArgs(true); args = args.concat(EnigmailPassword.command()); args.push("-d"); var statusMsgObj = {}; var cmdLineObj = {}; var exitCode = -1; var statusFlagsObj = {}; var errorMsgObj = {}; statusFlagsObj.value = 0; var listener = EnigmailExecution.newSimpleListener( function _stdin(pipe) { // try to get original file name if file does not contain suffix if (strippedName.indexOf(".") < 0) { let s = EnigmailAttachment.getFileName(null, o.data); if (s) o.name = s; } pipe.write(o.data); pipe.close(); } ); do { var proc = EnigmailExecution.execStart(getGpgAgent().agentPath, args, false, null, listener, statusFlagsObj); if (!proc) { resolve(o); return; } // Wait for child STDOUT to close proc.wait(); EnigmailExecution.execEnd(listener, statusFlagsObj, statusMsgObj, cmdLineObj, errorMsgObj); if ((listener.stdoutData && listener.stdoutData.length > 0) || (statusFlagsObj.value & nsIEnigmail.DECRYPTION_OKAY)) { EnigmailLog.DEBUG("decryptPermanently.jsm: decryptAttachment: decryption OK\n"); exitCode = 0; } else if (statusFlagsObj.value & nsIEnigmail.DECRYPTION_FAILED) { EnigmailLog.DEBUG("decryptPermanently.jsm: decryptAttachment: decryption failed\n"); // since we cannot find out if the user wants to cancel // we should ask let msg = EnigmailLocale.getString("converter.decryptAtt.failed", [attachment.name, self.subject]); if (!EnigmailDialog.confirmDlg(null, msg, EnigmailLocale.getString("dlg.button.retry"), EnigmailLocale.getString("dlg.button.skip"))) { o.status = STATUS_FAILURE; resolve(o); return; } } else if (statusFlagsObj.value & nsIEnigmail.DECRYPTION_INCOMPLETE) { // failure; message not complete EnigmailLog.DEBUG("decryptPermanently.jsm: decryptAttachment: decryption incomplete\n"); o.status = STATUS_FAILURE; resolve(o); return; } else { // there is nothing to be decrypted EnigmailLog.DEBUG("decryptPermanently.jsm: decryptAttachment: no decryption required\n"); o.status = STATUS_NOT_REQUIRED; resolve(o); return; } } while (exitCode !== 0); EnigmailLog.DEBUG("decryptPermanently.jsm: decryptAttachment: decrypted to " + listener.stdoutData.length + " bytes\n"); o.data = listener.stdoutData; o.status = STATUS_OK; resolve(o); } ); } ); }, /* * The following functions walk the MIME message structure and decrypt if they find something to decrypt */ // the sunny world of PGP/MIME walkMimeTree: function(mime, parent) { EnigmailLog.DEBUG("decryptPermanently.jsm: walkMimeTree:\n"); let ct = getContentType(getHeaderValue(mime, 'content-type')); EnigmailLog.DEBUG("decryptPermanently.jsm: walkMimeTree: part=" + mime.partName + " - " + ct + "\n"); // assign part name on lowest possible level -> that's where the attachment // really belongs to for (let i in mime.allAttachments) { mime.allAttachments[i].partName = mime.partName; } if (this.isPgpMime(mime) || this.isSMime(mime)) { let p = this.decryptPGPMIME(parent, mime.partName); this.decryptionTasks.push(p); } else if (this.isBrokenByExchange(mime)) { let p = this.decryptAttachment(mime.parts[0].parts[2], "decrypted.txt"); mime.isBrokenByExchange = true; mime.parts[0].parts[2].name = "ignore.txt"; this.decryptionTasks.push(p); } else if (typeof(mime.body) == "string") { EnigmailLog.DEBUG(" body size: " + mime.body.length + "\n"); } for (var i in mime.parts) { this.walkMimeTree(mime.parts[i], mime); } }, /*** * * Detect if mime part is PGP/MIME message that got modified by MS-Exchange: * * - multipart/mixed Container with * - application/pgp-encrypted Attachment with name "PGPMIME Version Identification" * - application/octet-stream Attachment with name "encrypted.asc" having the encrypted content in base64 * - see: * - http://www.mozilla-enigmail.org/forum/viewtopic.php?f=4&t=425 * - http://sourceforge.net/p/enigmail/forum/support/thread/4add2b69/ */ isBrokenByExchange: function(mime) { EnigmailLog.DEBUG("decryptPermanently.jsm: isBrokenByExchange:\n"); try { if (mime.parts && mime.parts.length && mime.parts.length == 1 && mime.parts[0].parts && mime.parts[0].parts.length && mime.parts[0].parts.length == 3 && mime.parts[0].headers["content-type"][0].indexOf("multipart/mixed") >= 0 && mime.parts[0].parts[0].size === 0 && mime.parts[0].parts[0].headers["content-type"][0].search(/multipart\/encrypted/i) < 0 && mime.parts[0].parts[0].headers["content-type"][0].indexOf("text/plain") >= 0 && mime.parts[0].parts[1].headers["content-type"][0].indexOf("application/pgp-encrypted") >= 0 && mime.parts[0].parts[1].headers["content-type"][0].search(/multipart\/encrypted/i) < 0 && mime.parts[0].parts[1].headers["content-type"][0].search(/PGPMIME Versions? Identification/i) >= 0 && mime.parts[0].parts[2].headers["content-type"][0].indexOf("application/octet-stream") >= 0 && mime.parts[0].parts[2].headers["content-type"][0].indexOf("encrypted.asc") >= 0) { EnigmailLog.DEBUG("decryptPermanently.jsm: isBrokenByExchange: found message broken by MS-Exchange\n"); return true; } } catch (ex) {} return false; }, isPgpMime: function(mime) { EnigmailLog.DEBUG("decryptPermanently.jsm: isPgpMime:\n"); try { var ct = mime.contentType; if (!ct) return false; if (!('content-type' in mime.headers)) return false; var pt = getProtocol(getHeaderValue(mime, 'content-type')); if (!pt) return false; if (ct.toLowerCase() == "multipart/encrypted" && pt == "application/pgp-encrypted") { return true; } } catch (ex) { //EnigmailLog.DEBUG("decryptPermanently.jsm: isPgpMime:"+ex+"\n"); } return false; }, // smime-type=enveloped-data isSMime: function(mime) { EnigmailLog.DEBUG("decryptPermanently.jsm: isSMime:\n"); try { var ct = mime.contentType; if (!ct) return false; if (!('content-type' in mime.headers)) return false; var pt = getSMimeProtocol(getHeaderValue(mime, 'content-type')); if (!pt) return false; if (ct.toLowerCase() == "application/pkcs7-mime" && pt == "enveloped-data") { return true; } } catch (ex) { EnigmailLog.DEBUG("decryptPermanently.jsm: isSMime:" + ex + "\n"); } return false; }, decryptPGPMIME: function(mime, part) { EnigmailLog.DEBUG("decryptPermanently.jsm: decryptPGPMIME: part=" + part + "\n"); var self = this; return new Promise( function(resolve, reject) { var m = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(Ci.nsIMimeHeaders); var messenger = Cc["@mozilla.org/messenger;1"].getService(Ci.nsIMessenger); let msgSvc = messenger.messageServiceFromURI(self.hdr.folder.getUriForMsg(self.hdr)); let u = {}; msgSvc.GetUrlForUri(self.hdr.folder.getUriForMsg(self.hdr), u, null); let op = (u.value.spec.indexOf("?") > 0 ? "&" : "?"); let url = u.value.spec + op + 'part=' + part + "&header=enigmailConvert"; EnigmailLog.DEBUG("decryptPermanently.jsm: getting data from URL " + url + "\n"); let s = EnigmailStreams.newStringStreamListener( function analyzeDecryptedData(data) { EnigmailLog.DEBUG("decryptPermanently.jsm: analyzeDecryptedData: got " + data.length + " bytes\n"); if (EnigmailLog.getLogLevel() > 5) { EnigmailLog.DEBUG("*** start data ***\n'" + data + "'\n***end data***\n"); } let subpart = mime.parts[0]; let o = { type: "mime", name: "", origName: "", data: "", partName: part, status: STATUS_OK }; if (data.length === 0) { // fail if no data found o.status = STATUS_FAILURE; resolve(o); return; } let bodyIndex = data.search(/\n\s*\r?\n/); if (bodyIndex < 0) { bodyIndex = 0; } else { ++bodyIndex; } if (data.substr(bodyIndex).search(/\r?\n$/) === 0) { o.status = STATUS_FAILURE; resolve(o); return; } m.initialize(data.substr(0, bodyIndex)); let ct = m.extractHeader("content-type", false) || ""; if (part.length > 0 && part.search(/[^01.]/) < 0) { if (ct.search(/protected-headers/i) >= 0) { if (m.hasHeader("subject")) { let subject = m.extractHeader("subject", false) || ""; self.mime.headers.subject = [subject]; } } else if (self.mime.headers.subject.join("") === "pEp") { let subject = getPepSubject(data); if (subject) { self.mime.headers.subject = [subject]; } } } let boundary = getBoundary(getHeaderValue(mime, 'content-type')); if (!boundary) boundary = EnigmailMime.createBoundary(); // append relevant headers mime.headers['content-type'] = "multipart/mixed; boundary=\"" + boundary + "\""; o.data = "--" + boundary + "\n"; o.data += "Content-Type: " + ct + "\n"; let h = m.headerNames; while (h.hasMore()) { let hdr = h.getNext(); if (hdr.search(/^content-type$/i) < 0) { try { EnigmailLog.DEBUG("decryptPermanently.jsm: getUnstructuredHeader: hdr=" + hdr + "\n"); let hdrVal = m.getUnstructuredHeader(hdr.toLowerCase()); o.data += hdr + ": " + hdrVal + "\n"; } catch (ex) { EnigmailLog.DEBUG("decryptPermanently.jsm: getUnstructuredHeader: exception " + ex.toString() + "\n"); } } } EnigmailLog.DEBUG("decryptPermanently.jsm: getUnstructuredHeader: done\n"); o.data += data.substr(bodyIndex); if (subpart) { subpart.body = undefined; subpart.headers['content-type'] = ct; } resolve(o); } ); try { var channel = EnigmailStreams.createChannel(url); channel.asyncOpen(s, null); } catch (e) { EnigmailLog.DEBUG("decryptPermanently.jsm: decryptPGPMIME: exception " + e.toString() + "\n"); } } ); }, //inline wonderland decryptINLINE: function(mime) { EnigmailLog.DEBUG("decryptPermanently.jsm: decryptINLINE:\n"); if (typeof mime.body !== 'undefined') { let ct = getContentType(getHeaderValue(mime, 'content-type')); if (ct == "text/html") { mime.body = this.stripHTMLFromArmoredBlocks(mime.body); } var enigmailSvc = EnigmailCore.getService(); var exitCodeObj = {}; var statusFlagsObj = {}; var userIdObj = {}; var sigDetailsObj = {}; var errorMsgObj = {}; var keyIdObj = {}; var blockSeparationObj = { value: "" }; var encToDetailsObj = {}; var signatureObj = {}; signatureObj.value = ""; var uiFlags = nsIEnigmail.UI_INTERACTIVE | nsIEnigmail.UI_UNVERIFIED_ENC_OK; var plaintexts = []; var blocks = EnigmailArmor.locateArmoredBlocks(mime.body); var tmp = []; for (let i = 0; i < blocks.length; i++) { if (blocks[i].blocktype == "MESSAGE") { tmp.push(blocks[i]); } } blocks = tmp; if (blocks.length < 1) { return 0; } let charset = "utf-8"; for (let i = 0; i < blocks.length; i++) { let plaintext = null; do { let ciphertext = mime.body.substring(blocks[i].begin, blocks[i].end + 1); if (ciphertext.length === 0) { break; } let hdr = ciphertext.search(/(\r\r|\n\n|\r\n\r\n)/); if (hdr > 0) { let chset = ciphertext.substr(0, hdr).match(/^(charset:)(.*)$/mi); if (chset && chset.length == 3) { charset = chset[2].trim(); } } plaintext = enigmailSvc.decryptMessage(null, uiFlags, ciphertext, signatureObj, exitCodeObj, statusFlagsObj, keyIdObj, userIdObj, sigDetailsObj, errorMsgObj, blockSeparationObj, encToDetailsObj); if (!plaintext || plaintext.length === 0) { if (statusFlagsObj.value & nsIEnigmail.DISPLAY_MESSAGE) { EnigmailDialog.alert(null, errorMsgObj.value); this.foundPGP = -1; return -1; } if (statusFlagsObj.value & nsIEnigmail.DECRYPTION_FAILED) { // since we cannot find out if the user wants to cancel // we should ask let msg = EnigmailLocale.getString("converter.decryptBody.failed", this.subject); if (!EnigmailDialog.confirmDlg(null, msg, EnigmailLocale.getString("dlg.button.retry"), EnigmailLocale.getString("dlg.button.skip"))) { this.foundPGP = -1; return -1; } } else if (statusFlagsObj.value & nsIEnigmail.DECRYPTION_INCOMPLETE) { this.foundPGP = -1; return -1; } } if (ct == "text/html") { plaintext = plaintext.replace(/\n/ig, "
\n"); } if (i == 0 && this.mime.headers.subject && this.mime.headers.subject[0] === "pEp" && mime.partName.length > 0 && mime.partName.search(/[^01.]/) < 0) { let m = EnigmailMime.extractSubjectFromBody(plaintext); if (m) { plaintext = m.messageBody; this.mime.headers.subject = [m.subject]; } } if (plaintext) { plaintexts.push(plaintext); } } while (!plaintext || plaintext === ""); } var decryptedMessage = mime.body.substring(0, blocks[0].begin) + plaintexts[0]; for (let i = 1; i < blocks.length; i++) { decryptedMessage += mime.body.substring(blocks[i - 1].end + 1, blocks[i].begin + 1) + plaintexts[i]; } decryptedMessage += mime.body.substring(blocks[(blocks.length - 1)].end + 1); // enable base64 encoding if non-ASCII character(s) found let j = decryptedMessage.search(/[^\x01-\x7F]/); // eslint-disable-line no-control-regex if (j >= 0) { mime.headers['content-transfer-encoding'] = ['base64']; mime.body = EnigmailData.encodeBase64(decryptedMessage); } else { mime.body = decryptedMessage; mime.headers['content-transfer-encoding'] = ['8bit']; } let origCharset = getCharset(getHeaderValue(mime, 'content-type')); if (origCharset) { mime.headers['content-type'] = getHeaderValue(mime, 'content-type').replace(origCharset, charset); } else { mime.headers['content-type'] = getHeaderValue(mime, 'content-type') + "; charset=" + charset; } this.foundPGP = 1; return 1; } if (typeof mime.parts !== 'undefined' && mime.parts.length > 0) { var ret = 0; for (let part in mime.parts) { ret += this.decryptINLINE(mime.parts[part]); } return ret; } let ct = getContentType(getHeaderValue(mime, 'content-type')); EnigmailLog.DEBUG("decryptPermanently.jsm: Decryption skipped: " + ct + "\n"); return 0; }, stripHTMLFromArmoredBlocks: function(text) { var index = 0; var begin = text.indexOf("-----BEGIN PGP"); var end = text.indexOf("-----END PGP"); while (begin > -1 && end > -1) { let sub = text.substring(begin, end); sub = sub.replace(/(<([^>]+)>)/ig, ""); sub = sub.replace(/&[A-z]+;/ig, ""); text = text.substring(0, begin) + sub + text.substring(end); index = end + 10; begin = text.indexOf("-----BEGIN PGP", index); end = text.indexOf("-----END PGP", index); } return text; }, /****** * * We have the technology we can rebuild. * * Function to reassemble the message from the MIME Tree * into a String. * ******/ mimeToString: function(mime, topLevel) { EnigmailLog.DEBUG("decryptPermanently.jsm: mimeToString: part: '" + mime.partName + "', is of type '" + typeof(mime) + "'\n"); let ct = getContentType(getHeaderValue(mime, 'content-type')); if (!ct) { return ""; } let boundary = getBoundary(getHeaderValue(mime, 'content-type')); let msg = ""; if (mime.isBrokenByExchange) { EnigmailLog.DEBUG("decryptPermanently.jsm: mimeToString: MS-Exchange fix\n"); for (let j in this.allTasks) { if (this.allTasks[j].partName == mime.parts[0].partName) { boundary = EnigmailMime.createBoundary(); msg += getRfc822Headers(mime.headers, ct, "content-type"); msg += 'Content-Type: multipart/mixed; boundary="' + boundary + '"\r\n\r\n'; msg += "This is a multi-part message in MIME format."; msg += "\r\n--" + boundary + "\r\n"; msg += this.allTasks[j].data; msg += "\r\n--" + boundary + "--\r\n"; return msg; } } } else if (mime instanceof MimeMessageAttachment) { for (let j in this.allTasks) { if (this.allTasks[j].partName == mime.partName && this.allTasks[j].origName == mime.name) { let a = this.allTasks[j]; EnigmailLog.DEBUG("decryptPermanently.jsm: mimeToString: attaching " + j + " as '" + a.name + "'\n"); for (let header in mime.headers) { if (!(a.status == STATUS_OK && header == "content-type")) { msg += prettyPrintHeader(header, mime.headers[header]) + "\r\n"; } } if (a.type == "attachment") { if (a.status == STATUS_OK) { msg += "Content-Type: application/octet-stream; name=\"" + a.name + "\"\r\n"; msg += "Content-Disposition: attachment; filename\"" + a.name + "\"\r\n"; } msg += "Content-Transfer-Encoding: base64\r\n\r\n"; msg += EnigmailData.encodeBase64(a.data) + "\r\n"; } } } } else if (mime instanceof MimeContainer || mime instanceof MimeUnknown) { for (let j in this.allTasks) { if (this.allTasks[j].partName == mime.partName && this.allTasks[j].type == "mime") { let a = this.allTasks[j]; msg += a.data; mime.noBottomBoundary = true; } } } else if (mime instanceof MimeMessage && ct.substr(0, 5) == "text/") { let subct = mime.parts[0].headers['content-type']; if (subct) { mime.headers['content-type'] = subct; } subct = mime.parts[0].headers['content-transfer-encoding']; if (subct) { mime.headers['content-transfer-encoding'] = subct; } msg += getRfc822Headers(mime.headers, ct); msg += "\r\n" + mime.parts[0].body + "\r\n"; return msg; } else { if (!topLevel && (mime instanceof MimeMessage)) { let mimeName = mime.name; if (!mimeName || mimeName === "") { mimeName = getHeaderValue(mime, 'subject') + ".eml"; } msg += 'Content-Type: message/rfc822; name="' + EnigmailMime.encodeHeaderValue(mimeName) + '\r\n'; msg += 'Content-Transfer-Encoding: 7bit\r\n'; msg += 'Content-Disposition: attachment; filename="' + EnigmailMime.encodeHeaderValue(mimeName) + '"\r\n\r\n'; } msg += getRfc822Headers(mime.headers, ct); msg += "\r\n"; if (mime.body) { msg += mime.body + "\r\n"; } else if ((mime instanceof MimeMessage) && ct.substr(0, 5) != "text/") { msg += "This is a multi-part message in MIME format.\r\n"; } } for (let i in mime.parts) { let subPart = this.mimeToString(mime.parts[i], false); if (subPart.length > 0) { if (boundary && !(mime instanceof MimeMessage)) { msg += "--" + boundary + "\r\n"; } msg += subPart + "\r\n"; } } if (ct.indexOf("multipart/") === 0 && !(mime instanceof MimeContainer)) { if (!mime.noBottomBoundary) { msg += "--" + boundary + "--\r\n"; } } return msg; } }; /** * Format a mime header * * e.g. content-type -> Content-Type */ function formatHeader(headerLabel) { return headerLabel.replace(/^.|(-.)/g, function(match) { return match.toUpperCase(); }); } function formatMimeHeader(headerLabel, headerValue) { if (headerLabel.search(/^(sender|from|reply-to|to|cc|bcc)$/i) === 0) { return formatHeader(headerLabel) + ": " + EnigmailMime.formatHeaderData(EnigmailMime.formatEmailAddress(headerValue)); } else { return formatHeader(headerLabel) + ": " + EnigmailMime.formatHeaderData(EnigmailMime.encodeHeaderValue(headerValue)); } } function prettyPrintHeader(headerLabel, headerData) { let hdrData = ""; if (Array.isArray(headerData)) { let h = []; for (let i in headerData) { h.push(formatMimeHeader(headerLabel, GlodaUtils.deMime(headerData[i]))); } return h.join("\r\n"); } else { return formatMimeHeader(headerLabel, GlodaUtils.deMime(String(headerData))); } } function getHeaderValue(mimeStruct, header) { EnigmailLog.DEBUG("decryptPermanently.jsm: getHeaderValue: '" + header + "'\n"); try { if (header in mimeStruct.headers) { if (typeof mimeStruct.headers[header] == "string") { return mimeStruct.headers[header]; } else { return mimeStruct.headers[header].join(" "); } } else { return ""; } } catch (ex) { EnigmailLog.DEBUG("decryptPermanently.jsm: getHeaderValue: header not present\n"); return ""; } } /*** * get the formatted headers for MimeMessage objects * * @headerArr: Array of headers (key/value pairs), such as mime.headers * @ignoreHeadersArr: Array of headers to exclude from returning * * @return: String containing formatted header list */ function getRfc822Headers(headerArr, contentType, ignoreHeadersArr) { let hdrs = ""; let ignore = []; if (contentType.indexOf("multipart/") >= 0) { ignore = ['content-transfer-encoding', 'content-disposition', 'content-description' ]; } if (ignoreHeadersArr) { ignore = ignore.concat(ignoreHeadersArr); } for (let i in headerArr) { if (ignore.indexOf(i) < 0) { hdrs += prettyPrintHeader(i, headerArr[i]) + "\r\n"; } } return hdrs; } function getContentType(shdr) { try { shdr = String(shdr); return shdr.match(/([A-z-]+\/[A-z-]+)/)[1].toLowerCase(); } catch (e) { EnigmailLog.DEBUG("decryptPermanently.jsm: getContentType: " + e + "\n"); return null; } } // return the content of the boundary parameter function getBoundary(shdr) { try { shdr = String(shdr); return EnigmailMime.getBoundary(shdr); } catch (e) { EnigmailLog.DEBUG("decryptPermanently.jsm: getBoundary: " + e + "\n"); return null; } } function getCharset(shdr) { try { shdr = String(shdr); return EnigmailMime.getParameter(shdr, 'charset').toLowerCase(); } catch (e) { EnigmailLog.DEBUG("decryptPermanently.jsm: getCharset: " + e + "\n"); return null; } } function getProtocol(shdr) { try { shdr = String(shdr); return EnigmailMime.getProtocol(shdr).toLowerCase(); } catch (e) { EnigmailLog.DEBUG("decryptPermanently.jsm: getProtocol: " + e + "\n"); return ""; } } function getSMimeProtocol(shdr) { try { shdr = String(shdr); return shdr.match(/smime-type="?([A-z0-9'()+_,-./:=?]+)"?/)[1].toLowerCase(); } catch (e) { EnigmailLog.DEBUG("decryptPermanently.jsm: getSMimeProtocol: " + e + "\n"); return ""; } } function getPepSubject(mimeString) { EnigmailLog.DEBUG("decryptPermanently.jsm: getPepSubject()\n"); let subject = null; let emitter = { ct: "", firstPlainText: false, startPart: function(partNum, headers) { EnigmailLog.DEBUG("decryptPermanently.jsm: getPepSubject.startPart: partNum=" + partNum + "\n"); try { this.ct = String(headers.getRawHeader("content-type")).toLowerCase(); if (!subject && !this.firstPlainText) { let s = headers.getRawHeader("subject"); if (s) { subject = String(s); this.firstPlainText = true; } } } catch (ex) { this.ct = ""; } }, endPart: function(partNum) {}, deliverPartData: function(partNum, data) { EnigmailLog.DEBUG("decryptPermanently.jsm: getPepSubject.deliverPartData: partNum=" + partNum + " ct=" + this.ct + "\n"); if (!this.firstPlainText && this.ct.search(/^text\/plain/) === 0) { // check data this.firstPlainText = true; let o = EnigmailMime.extractSubjectFromBody(data); if (o) { subject = o.subject; } } } }; let opt = { strformat: "unicode", bodyformat: "decode" }; try { let p = new jsmime.MimeParser(emitter, opt); p.deliverData(mimeString); } catch (ex) {} return subject; } /** * Lazy deletion of original messages */ function deleteOriginalMail(msgHdr) { EnigmailLog.DEBUG("decryptPermanently.jsm: deleteOriginalMail(" + msgHdr.messageKey + ")\n"); let delMsg = function() { try { EnigmailLog.DEBUG("decryptPermanently.jsm: deleting original message " + msgHdr.messageKey + "\n"); let folderInfoObj = {}; msgHdr.folder.getDBFolderInfoAndDB(folderInfoObj).DeleteMessage(msgHdr.messageKey, null, true); } catch (e) { EnigmailLog.DEBUG("decryptPermanently.jsm: deletion failed. Error: " + e.toString() + "\n"); } }; EnigmailTimer.setTimeout(delMsg, 500); } diff --git a/package/decryptPermanently.jsm b/package/encryptPermanently.jsm similarity index 81% copy from package/decryptPermanently.jsm copy to package/encryptPermanently.jsm index 296b5866..fb1cd773 100644 --- a/package/decryptPermanently.jsm +++ b/package/encryptPermanently.jsm @@ -1,1254 +1,1119 @@ /*global Components: false*/ /*jshint -W097 */ /* * 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/. */ "use strict"; -var EXPORTED_SYMBOLS = ["EnigmailDecryptPermanently"]; +var EXPORTED_SYMBOLS = ["EnigmailEncryptPermanently"]; const Cu = Components.utils; Cu.import("resource://enigmail/lazy.jsm"); /*global EnigmailLazy: false */ Cu.import("resource://gre/modules/AddonManager.jsm"); /*global AddonManager: false */ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); /*global XPCOMUtils: false */ Cu.import("resource://enigmail/log.jsm"); /*global EnigmailLog: false */ Cu.import("resource://enigmail/armor.jsm"); /*global EnigmailArmor: false */ Cu.import("resource://enigmail/locale.jsm"); /*global EnigmailLocale: false */ Cu.import("resource://enigmail/execution.jsm"); /*global EnigmailExecution: false */ Cu.import("resource://enigmail/dialog.jsm"); /*global EnigmailDialog: false */ Cu.import("resource://enigmail/glodaUtils.jsm"); /*global GlodaUtils: false */ Cu.import("resource:///modules/MailUtils.js"); /*global MailUtils: false */ Cu.import("resource://enigmail/core.jsm"); /*global EnigmailCore: false */ Cu.import("resource://enigmail/gpg.jsm"); /*global EnigmailGpg: false */ Cu.import("resource://enigmail/streams.jsm"); /*global EnigmailStreams: false */ Cu.import("resource://enigmail/passwords.jsm"); /*global EnigmailPassword: false */ Cu.import("resource://enigmail/mime.jsm"); /*global EnigmailMime: false */ Cu.import("resource://enigmail/data.jsm"); /*global EnigmailData: false */ Cu.import("resource://enigmail/attachment.jsm"); /*global EnigmailAttachment: false */ Cu.import("resource://enigmail/timer.jsm"); /*global EnigmailTimer: false */ +Cu.import("resource://enigmail/encryption.jsm"); /*global EnigmailEncryption: false */ Cu.import("resource:///modules/jsmime.jsm"); /*global jsmime: false*/ /*global MimeBody: false, MimeUnknown: false, MimeMessageAttachment: false */ /*global msgHdrToMimeMessage: false, MimeMessage: false, MimeContainer: false */ Cu.import("resource://enigmail/glodaMime.jsm"); const getGpgAgent = EnigmailLazy.loader("enigmail/gpgAgent.jsm", "EnigmailGpgAgent"); var EC = EnigmailCore; const Cc = Components.classes; const Ci = Components.interfaces; const nsIEnigmail = Components.interfaces.nsIEnigmail; const STATUS_OK = 0; const STATUS_FAILURE = 1; const STATUS_NOT_REQUIRED = 2; const IOSERVICE_CONTRACTID = "@mozilla.org/network/io-service;1"; /* * Decrypt a message and copy it to a folder * * @param nsIMsgDBHdr hdr Header of the message * @param String destFolder Folder URI * @param Boolean move If true the original message will be deleted * * @return a Promise that we do that */ -const EnigmailDecryptPermanently = { +const EnigmailEncryptPermanently = { /*** * dispatchMessages * * Because Thunderbird throws all messages at once at us thus we have to rate limit the dispatching * of the message processing. Because there is only a negligible performance gain when dispatching * several message at once we serialize to not overwhelm low power devices. * * The function is implemented asynchronously. * * Parameters * aMsgHdrs: Array of nsIMsgDBHdr * targetFolder: String; target folder URI * copyListener: listener for async request (nsIMsgCopyServiceListener) * move: Boolean: type of action; true = "move" / false = "copy" * **/ dispatchMessages: function(aMsgHdrs, targetFolder, copyListener, move) { - EnigmailLog.DEBUG("decryptPermanently.jsm: dispatchMessages()\n"); + EnigmailLog.DEBUG("encryptPermanently.jsm: dispatchMessages()\n"); if (copyListener) { copyListener.OnStartCopy(); } - let promise = EnigmailDecryptPermanently.decryptMessage(aMsgHdrs[0], targetFolder, move); + let promise = EnigmailEncryptPermanently.encryptMessage(aMsgHdrs[0], targetFolder, move); var processNext = function(data) { aMsgHdrs.splice(0, 1); if (aMsgHdrs.length > 0) { - EnigmailDecryptPermanently.dispatchMessages(aMsgHdrs, targetFolder, move); + EnigmailEncryptPermanently.dispatchMessages(aMsgHdrs, targetFolder, move); } else { // last message was finished processing if (copyListener) { copyListener.OnStopCopy(0); } - EnigmailLog.DEBUG("decryptPermanently.jsm: dispatchMessages - DONE\n"); + EnigmailLog.DEBUG("encryptPermanently.jsm: dispatchMessages - DONE\n"); } }; promise.then(processNext); promise.catch(function(err) { processNext(null); }); }, - decryptMessage: function(hdr, destFolder, move) { + encryptMessage: function(msgHdr, encryptTo) { return new Promise( function(resolve, reject) { - let msgUriSpec = hdr.folder.getUriForMsg(hdr); + let msgUriSpec = msgHdr.folder.getUriForMsg(msgHdr); const msgSvc = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger).messageServiceFromURI(msgUriSpec); - const decrypt = new DecryptMessageIntoFolder(destFolder, move, resolve); + const encrypt = new EncryptMessageIntoFolder(resolve,encryptTo); try { - msgHdrToMimeMessage(hdr, decrypt, decrypt.messageParseCallback, true, { + msgHdrToMimeMessage(msgHdr, encrypt, encrypt.messageParseCallback, true, { examineEncryptedParts: false, partsOnDemand: false }); } catch (ex) { reject("msgHdrToMimeMessage failed"); } return; } ); } }; -function DecryptMessageIntoFolder(destFolder, move, resolve) { - this.destFolder = destFolder; - this.move = move; +function EncryptMessageIntoFolder(resolve,encryptTo) { this.resolve = resolve; this.foundPGP = 0; this.mime = null; this.hdr = null; - this.decryptionTasks = []; + this.encryptionTasks = []; this.subject = ""; + this.fromEmail = ""; + this.encryptTo = encryptTo; } -DecryptMessageIntoFolder.prototype = { +EncryptMessageIntoFolder.prototype = { messageParseCallback: function(hdr, mime) { this.hdr = hdr; this.mime = mime; var self = this; - try { - if (!mime) { - this.resolve(true); - return; - } - - if (!("content-type" in mime.headers)) { - mime.headers["content-type"] = ["text/plain"]; - } - - var ct = getContentType(getHeaderValue(mime, 'content-type')); - var pt = getProtocol(getHeaderValue(mime, 'content-type')); - - this.subject = GlodaUtils.deMime(getHeaderValue(mime, 'subject')); - - if (!ct) { - this.resolve(true); - return; - } - - - this.walkMimeTree(this.mime, this.mime); - - this.decryptINLINE(this.mime); - if (this.foundPGP < 0) { - // decryption failed - this.resolve(true); - return; - } - - - for (let i in this.mime.allAttachments) { - let a = this.mime.allAttachments[i]; - let suffixIndexEnd = a.name.toLowerCase().lastIndexOf('.pgp'); - if (suffixIndexEnd < 0) { - suffixIndexEnd = a.name.toLowerCase().lastIndexOf('.asc'); - } - - if (suffixIndexEnd > 0 && - a.contentType.search(/application\/pgp-signature/i) < 0) { - - // possible OpenPGP attachment - let p = self.decryptAttachment(a, a.name.substring(0, suffixIndexEnd)); - this.decryptionTasks.push(p); - } - else { - let p = this.readAttachment(a); - this.decryptionTasks.push(p); - } - } - - Promise.all(this.decryptionTasks).then( - function(tasks) { - self.allTasks = tasks; - for (let a in tasks) { - switch (tasks[a].status) { - case STATUS_NOT_REQUIRED: - tasks[a].name = tasks[a].origName; - break; - case STATUS_OK: - ++self.foundPGP; - break; - case STATUS_FAILURE: - // attachment did not decrypt successfully - self.resolve(true); - return; - default: - // no valid result?! - tasks[a].name = tasks[a].origName; - } - } - - if (self.foundPGP === 0) { - self.resolve(true); - return; - } - - var msg = self.mimeToString(self.mime, true); - - if (!msg || msg === "") { - // no message data found - self.resolve(true); - return; - } - - //XXX Do we wanna use the tmp for this? - var tempFile = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile); - tempFile.append("message.eml"); - tempFile.createUnique(0, 384); // == 0600, octal is deprecated - - // ensure that file gets deleted on exit, if something goes wrong ... - var extAppLauncher = Cc["@mozilla.org/mime;1"].getService(Ci.nsPIExternalAppLauncher); - - var foStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream); - foStream.init(tempFile, 2, 0x200, false); // open as "write only" - foStream.write(msg, msg.length); - foStream.close(); - - extAppLauncher.deleteTemporaryFileOnExit(tempFile); - - // - // This was taken from the HeaderToolsLite Example Addon "original by Frank DiLecce" - // - // this is interesting: nsIMsgFolder.copyFileMessage seems to have a bug on Windows, when - // the nsIFile has been already used by foStream (because of Windows lock system?), so we - // must initialize another nsIFile object, pointing to the temporary file - var fileSpec = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); - fileSpec.initWithPath(tempFile.path); - - const copySvc = Cc["@mozilla.org/messenger/messagecopyservice;1"].getService(Ci.nsIMsgCopyService); - - var copyListener = { - QueryInterface: function(iid) { - if (iid.equals(Ci.nsIMsgCopyServiceListener) || iid.equals(Ci.nsISupports)) { - return this; - } - EnigmailLog.DEBUG("decryptPermanently.jsm: copyListener error\n"); - throw Components.results.NS_NOINTERFACE; - }, - GetMessageId: function(messageId) {}, - OnProgress: function(progress, progressMax) {}, - OnStartCopy: function() { - EnigmailLog.DEBUG("decryptPermanently.jsm: copyListener: OnStartCopy()\n"); - }, - SetMessageKey: function(key) { - EnigmailLog.DEBUG("decryptPermanently.jsm: copyListener: SetMessageKey(" + key + ")\n"); - }, - OnStopCopy: function(statusCode) { - EnigmailLog.DEBUG("decryptPermanently.jsm: copyListener: OnStopCopy()\n"); - if (statusCode !== 0) { - EnigmailLog.DEBUG("decryptPermanently.jsm: Error copying message: " + statusCode + "\n"); - try { - tempFile.remove(false); - } - catch (ex) { - try { - fileSpec.remove(false); - } - catch (e2) { - EnigmailLog.DEBUG("decryptPermanently.jsm: Could not delete temp file\n"); - } - } - self.resolve(true); - return; - } - EnigmailLog.DEBUG("decryptPermanently.jsm: Copy complete\n"); - - if (self.move) { - deleteOriginalMail(self.hdr); - } - - try { - tempFile.remove(false); - } - catch (ex) { - try { - fileSpec.remove(false); - } - catch (e2) { - EnigmailLog.DEBUG("decryptPermanently.jsm: Could not delete temp file\n"); - } - } - - EnigmailLog.DEBUG("decryptPermanently.jsm: Cave Johnson. We're done\n"); - self.resolve(true); - } - }; - - EnigmailLog.DEBUG("decryptPermanently.jsm: copySvc ready for copy\n"); - try { - if (self.mime.headers.subject) { - self.hdr.subject = self.mime.headers.subject.join(); - } - } - catch (ex) {} - - copySvc.CopyFileMessage(fileSpec, MailUtils.getFolderForURI(self.destFolder, false), self.hdr, - false, 0, "", copyListener, null); - } - ).catch( - function catchErr(errorMsg) { - EnigmailLog.DEBUG("decryptPermanently.jsm: Promise.catchErr: " + errorMsg + "\n"); - self.resolve(false); - } - ); - - } - catch (ex) { - EnigmailLog.DEBUG("decryptPermanently.jsm: messageParseCallback: caught error " + ex.toString() + "\n"); - self.resolve(false); - } - }, + try { + if (!mime) { + this.resolve(true); + return; + } + + if (!("content-type" in mime.headers)) { + mime.headers["content-type"] = ["text/plain"]; + } + + var ct = getContentType(getHeaderValue(mime, 'content-type')); + var pt = getProtocol(getHeaderValue(mime, 'content-type')); + + this.subject = GlodaUtils.deMime(getHeaderValue(mime, 'subject')); + this.fromEmail = GlodaUtils.deMime(getHeaderValue(mime, 'from')); + + if (!ct) { + this.resolve(true); + return; + } + + this.walkMimeTree(this.mime, this.mime); + + EnigmailLog.DEBUG("encrypt to: " + this.encryptTo + ", from: " + this.fromEmail + "\n"); + let exitCode = {}; + let errorMsg = {}; + + let msg = EnigmailEncryption.encryptMessage( + null, + nsIEnigmail.UI_UNVERIFIED_ENC_OK, + this.mimeToString(mime), + this.encryptTo, + this.fromEmail, + "", + nsIEnigmail.SAVE_MESSAGE | nsIEnigmail.SEND_ENCRYPTED | + nsIEnigmail.SEND_ALWAYS_TRUST | nsIEnigmail.SEND_PGP_MIME, + exitCode, {}, errorMsg); + let outHandler = { + 'deliverData': function(s) { EnigmailLog.DEBUG("got: " + s + "\n"); }, + 'deliverEOF': function() { EnigmailLog.DEBUG("got EOF\n"); } + }; + let enc = Cc["@enigmail.net/enigmail/composesecure;1"].createInstance(Ci.nsIMsgComposeSecure); + enc.beginCryptoEncapsulation({},"",null,null,null,false); + EnigmailLog.DEBUG("emit: " + jsmime.headeremitter.emitStructuredHeader("content-type", "lala", {})); + + EnigmailLog.DEBUG("########## msg ###########\n"); + EnigmailLog.DEBUG(msg); + EnigmailLog.DEBUG("########## end ###########\n"); + EnigmailLog.DEBUG("exit: " + JSON.stringify(exitCode) + "\n"); + EnigmailLog.DEBUG("error: " + JSON.stringify(errorMsg) + "\n"); + } + catch(ex) { + EnigmailLog.DEBUG("caught: " + ex + "\n"); + } + }, readAttachment: function(attachment, strippedName) { return new Promise( function(resolve, reject) { EnigmailLog.DEBUG("decryptPermanently.jsm: readAttachment\n"); let o; var f = function _cb(data) { EnigmailLog.DEBUG("decryptPermanently.jsm: readAttachment - got data (" + data.length + ")\n"); o = { type: "attachment", data: data, name: strippedName ? strippedName : attachment.name, partName: attachment.partName, origName: attachment.name, status: STATUS_NOT_REQUIRED }; resolve(o); }; try { var bufferListener = EnigmailStreams.newStringStreamListener(f); var ioServ = Cc[IOSERVICE_CONTRACTID].getService(Components.interfaces.nsIIOService); var msgUri = ioServ.newURI(attachment.url, null, null); var channel = EnigmailStreams.createChannelFromURI(msgUri); channel.asyncOpen(bufferListener, msgUri); } catch (ex) { reject(o); } } ); }, decryptAttachment: function(attachment, strippedName) { var self = this; return new Promise( function(resolve, reject) { EnigmailLog.DEBUG("decryptPermanently.jsm: decryptAttachment\n"); self.readAttachment(attachment, strippedName).then( function(o) { var attachmentHead = o.data.substr(0, 30); if (attachmentHead.match(/-----BEGIN PGP \w+ KEY BLOCK-----/)) { // attachment appears to be a PGP key file, we just go-a-head resolve(o); return; } var enigmailSvc = EnigmailCore.getService(); var args = EnigmailGpg.getStandardArgs(true); args = args.concat(EnigmailPassword.command()); args.push("-d"); var statusMsgObj = {}; var cmdLineObj = {}; var exitCode = -1; var statusFlagsObj = {}; var errorMsgObj = {}; statusFlagsObj.value = 0; var listener = EnigmailExecution.newSimpleListener( function _stdin(pipe) { // try to get original file name if file does not contain suffix if (strippedName.indexOf(".") < 0) { let s = EnigmailAttachment.getFileName(null, o.data); if (s) o.name = s; } pipe.write(o.data); pipe.close(); } ); do { var proc = EnigmailExecution.execStart(getGpgAgent().agentPath, args, false, null, listener, statusFlagsObj); if (!proc) { resolve(o); return; } // Wait for child STDOUT to close proc.wait(); EnigmailExecution.execEnd(listener, statusFlagsObj, statusMsgObj, cmdLineObj, errorMsgObj); if ((listener.stdoutData && listener.stdoutData.length > 0) || (statusFlagsObj.value & nsIEnigmail.DECRYPTION_OKAY)) { EnigmailLog.DEBUG("decryptPermanently.jsm: decryptAttachment: decryption OK\n"); exitCode = 0; } else if (statusFlagsObj.value & nsIEnigmail.DECRYPTION_FAILED) { EnigmailLog.DEBUG("decryptPermanently.jsm: decryptAttachment: decryption failed\n"); // since we cannot find out if the user wants to cancel // we should ask let msg = EnigmailLocale.getString("converter.decryptAtt.failed", [attachment.name, self.subject]); if (!EnigmailDialog.confirmDlg(null, msg, EnigmailLocale.getString("dlg.button.retry"), EnigmailLocale.getString("dlg.button.skip"))) { o.status = STATUS_FAILURE; resolve(o); return; } } else if (statusFlagsObj.value & nsIEnigmail.DECRYPTION_INCOMPLETE) { // failure; message not complete EnigmailLog.DEBUG("decryptPermanently.jsm: decryptAttachment: decryption incomplete\n"); o.status = STATUS_FAILURE; resolve(o); return; } else { // there is nothing to be decrypted EnigmailLog.DEBUG("decryptPermanently.jsm: decryptAttachment: no decryption required\n"); o.status = STATUS_NOT_REQUIRED; resolve(o); return; } } while (exitCode !== 0); EnigmailLog.DEBUG("decryptPermanently.jsm: decryptAttachment: decrypted to " + listener.stdoutData.length + " bytes\n"); o.data = listener.stdoutData; o.status = STATUS_OK; resolve(o); } ); } ); }, /* * The following functions walk the MIME message structure and decrypt if they find something to decrypt */ // the sunny world of PGP/MIME walkMimeTree: function(mime, parent) { EnigmailLog.DEBUG("decryptPermanently.jsm: walkMimeTree:\n"); let ct = getContentType(getHeaderValue(mime, 'content-type')); EnigmailLog.DEBUG("decryptPermanently.jsm: walkMimeTree: part=" + mime.partName + " - " + ct + "\n"); // assign part name on lowest possible level -> that's where the attachment // really belongs to for (let i in mime.allAttachments) { mime.allAttachments[i].partName = mime.partName; } if (this.isPgpMime(mime) || this.isSMime(mime)) { + EnigmailLog.DEBUG("pgp mime part " + mime.partName + "\n"); let p = this.decryptPGPMIME(parent, mime.partName); this.decryptionTasks.push(p); } else if (this.isBrokenByExchange(mime)) { let p = this.decryptAttachment(mime.parts[0].parts[2], "decrypted.txt"); mime.isBrokenByExchange = true; mime.parts[0].parts[2].name = "ignore.txt"; this.decryptionTasks.push(p); } else if (typeof(mime.body) == "string") { EnigmailLog.DEBUG(" body size: " + mime.body.length + "\n"); } for (var i in mime.parts) { this.walkMimeTree(mime.parts[i], mime); } }, /*** * * Detect if mime part is PGP/MIME message that got modified by MS-Exchange: * * - multipart/mixed Container with * - application/pgp-encrypted Attachment with name "PGPMIME Version Identification" * - application/octet-stream Attachment with name "encrypted.asc" having the encrypted content in base64 * - see: * - http://www.mozilla-enigmail.org/forum/viewtopic.php?f=4&t=425 * - http://sourceforge.net/p/enigmail/forum/support/thread/4add2b69/ */ isBrokenByExchange: function(mime) { EnigmailLog.DEBUG("decryptPermanently.jsm: isBrokenByExchange:\n"); try { if (mime.parts && mime.parts.length && mime.parts.length == 1 && mime.parts[0].parts && mime.parts[0].parts.length && mime.parts[0].parts.length == 3 && mime.parts[0].headers["content-type"][0].indexOf("multipart/mixed") >= 0 && mime.parts[0].parts[0].size === 0 && mime.parts[0].parts[0].headers["content-type"][0].search(/multipart\/encrypted/i) < 0 && mime.parts[0].parts[0].headers["content-type"][0].indexOf("text/plain") >= 0 && mime.parts[0].parts[1].headers["content-type"][0].indexOf("application/pgp-encrypted") >= 0 && mime.parts[0].parts[1].headers["content-type"][0].search(/multipart\/encrypted/i) < 0 && mime.parts[0].parts[1].headers["content-type"][0].search(/PGPMIME Versions? Identification/i) >= 0 && mime.parts[0].parts[2].headers["content-type"][0].indexOf("application/octet-stream") >= 0 && mime.parts[0].parts[2].headers["content-type"][0].indexOf("encrypted.asc") >= 0) { EnigmailLog.DEBUG("decryptPermanently.jsm: isBrokenByExchange: found message broken by MS-Exchange\n"); return true; } } catch (ex) {} return false; }, isPgpMime: function(mime) { EnigmailLog.DEBUG("decryptPermanently.jsm: isPgpMime:\n"); try { var ct = mime.contentType; if (!ct) return false; if (!('content-type' in mime.headers)) return false; var pt = getProtocol(getHeaderValue(mime, 'content-type')); if (!pt) return false; if (ct.toLowerCase() == "multipart/encrypted" && pt == "application/pgp-encrypted") { return true; } } catch (ex) { //EnigmailLog.DEBUG("decryptPermanently.jsm: isPgpMime:"+ex+"\n"); } return false; }, // smime-type=enveloped-data isSMime: function(mime) { EnigmailLog.DEBUG("decryptPermanently.jsm: isSMime:\n"); try { var ct = mime.contentType; if (!ct) return false; if (!('content-type' in mime.headers)) return false; var pt = getSMimeProtocol(getHeaderValue(mime, 'content-type')); if (!pt) return false; if (ct.toLowerCase() == "application/pkcs7-mime" && pt == "enveloped-data") { return true; } } catch (ex) { EnigmailLog.DEBUG("decryptPermanently.jsm: isSMime:" + ex + "\n"); } return false; }, decryptPGPMIME: function(mime, part) { EnigmailLog.DEBUG("decryptPermanently.jsm: decryptPGPMIME: part=" + part + "\n"); var self = this; return new Promise( function(resolve, reject) { var m = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(Ci.nsIMimeHeaders); var messenger = Cc["@mozilla.org/messenger;1"].getService(Ci.nsIMessenger); let msgSvc = messenger.messageServiceFromURI(self.hdr.folder.getUriForMsg(self.hdr)); let u = {}; msgSvc.GetUrlForUri(self.hdr.folder.getUriForMsg(self.hdr), u, null); let op = (u.value.spec.indexOf("?") > 0 ? "&" : "?"); let url = u.value.spec + op + 'part=' + part + "&header=enigmailConvert"; EnigmailLog.DEBUG("decryptPermanently.jsm: getting data from URL " + url + "\n"); let s = EnigmailStreams.newStringStreamListener( function analyzeDecryptedData(data) { EnigmailLog.DEBUG("decryptPermanently.jsm: analyzeDecryptedData: got " + data.length + " bytes\n"); if (EnigmailLog.getLogLevel() > 5) { EnigmailLog.DEBUG("*** start data ***\n'" + data + "'\n***end data***\n"); } let subpart = mime.parts[0]; let o = { type: "mime", name: "", origName: "", data: "", partName: part, status: STATUS_OK }; if (data.length === 0) { // fail if no data found o.status = STATUS_FAILURE; resolve(o); return; } let bodyIndex = data.search(/\n\s*\r?\n/); if (bodyIndex < 0) { bodyIndex = 0; } else { ++bodyIndex; } if (data.substr(bodyIndex).search(/\r?\n$/) === 0) { o.status = STATUS_FAILURE; resolve(o); return; } m.initialize(data.substr(0, bodyIndex)); let ct = m.extractHeader("content-type", false) || ""; if (part.length > 0 && part.search(/[^01.]/) < 0) { if (ct.search(/protected-headers/i) >= 0) { if (m.hasHeader("subject")) { let subject = m.extractHeader("subject", false) || ""; self.mime.headers.subject = [subject]; } } else if (self.mime.headers.subject.join("") === "pEp") { let subject = getPepSubject(data); if (subject) { self.mime.headers.subject = [subject]; } } } let boundary = getBoundary(getHeaderValue(mime, 'content-type')); if (!boundary) boundary = EnigmailMime.createBoundary(); // append relevant headers mime.headers['content-type'] = "multipart/mixed; boundary=\"" + boundary + "\""; o.data = "--" + boundary + "\n"; o.data += "Content-Type: " + ct + "\n"; let h = m.headerNames; while (h.hasMore()) { let hdr = h.getNext(); if (hdr.search(/^content-type$/i) < 0) { try { EnigmailLog.DEBUG("decryptPermanently.jsm: getUnstructuredHeader: hdr=" + hdr + "\n"); let hdrVal = m.getUnstructuredHeader(hdr.toLowerCase()); o.data += hdr + ": " + hdrVal + "\n"; } catch (ex) { EnigmailLog.DEBUG("decryptPermanently.jsm: getUnstructuredHeader: exception " + ex.toString() + "\n"); } } } EnigmailLog.DEBUG("decryptPermanently.jsm: getUnstructuredHeader: done\n"); o.data += data.substr(bodyIndex); if (subpart) { subpart.body = undefined; subpart.headers['content-type'] = ct; } resolve(o); } ); try { var channel = EnigmailStreams.createChannel(url); channel.asyncOpen(s, null); } catch (e) { EnigmailLog.DEBUG("decryptPermanently.jsm: decryptPGPMIME: exception " + e.toString() + "\n"); } } ); }, //inline wonderland decryptINLINE: function(mime) { EnigmailLog.DEBUG("decryptPermanently.jsm: decryptINLINE:\n"); if (typeof mime.body !== 'undefined') { let ct = getContentType(getHeaderValue(mime, 'content-type')); if (ct == "text/html") { mime.body = this.stripHTMLFromArmoredBlocks(mime.body); } var enigmailSvc = EnigmailCore.getService(); var exitCodeObj = {}; var statusFlagsObj = {}; var userIdObj = {}; var sigDetailsObj = {}; var errorMsgObj = {}; var keyIdObj = {}; var blockSeparationObj = { value: "" }; var encToDetailsObj = {}; var signatureObj = {}; signatureObj.value = ""; var uiFlags = nsIEnigmail.UI_INTERACTIVE | nsIEnigmail.UI_UNVERIFIED_ENC_OK; var plaintexts = []; var blocks = EnigmailArmor.locateArmoredBlocks(mime.body); var tmp = []; for (let i = 0; i < blocks.length; i++) { if (blocks[i].blocktype == "MESSAGE") { tmp.push(blocks[i]); } } blocks = tmp; if (blocks.length < 1) { return 0; } let charset = "utf-8"; for (let i = 0; i < blocks.length; i++) { let plaintext = null; do { let ciphertext = mime.body.substring(blocks[i].begin, blocks[i].end + 1); if (ciphertext.length === 0) { break; } let hdr = ciphertext.search(/(\r\r|\n\n|\r\n\r\n)/); if (hdr > 0) { let chset = ciphertext.substr(0, hdr).match(/^(charset:)(.*)$/mi); if (chset && chset.length == 3) { charset = chset[2].trim(); } } plaintext = enigmailSvc.decryptMessage(null, uiFlags, ciphertext, signatureObj, exitCodeObj, statusFlagsObj, keyIdObj, userIdObj, sigDetailsObj, errorMsgObj, blockSeparationObj, encToDetailsObj); if (!plaintext || plaintext.length === 0) { if (statusFlagsObj.value & nsIEnigmail.DISPLAY_MESSAGE) { EnigmailDialog.alert(null, errorMsgObj.value); this.foundPGP = -1; return -1; } if (statusFlagsObj.value & nsIEnigmail.DECRYPTION_FAILED) { // since we cannot find out if the user wants to cancel // we should ask let msg = EnigmailLocale.getString("converter.decryptBody.failed", this.subject); if (!EnigmailDialog.confirmDlg(null, msg, EnigmailLocale.getString("dlg.button.retry"), EnigmailLocale.getString("dlg.button.skip"))) { this.foundPGP = -1; return -1; } } else if (statusFlagsObj.value & nsIEnigmail.DECRYPTION_INCOMPLETE) { this.foundPGP = -1; return -1; } } if (ct == "text/html") { plaintext = plaintext.replace(/\n/ig, "
\n"); } if (i == 0 && this.mime.headers.subject && this.mime.headers.subject[0] === "pEp" && mime.partName.length > 0 && mime.partName.search(/[^01.]/) < 0) { let m = EnigmailMime.extractSubjectFromBody(plaintext); if (m) { plaintext = m.messageBody; this.mime.headers.subject = [m.subject]; } } if (plaintext) { plaintexts.push(plaintext); } } while (!plaintext || plaintext === ""); } var decryptedMessage = mime.body.substring(0, blocks[0].begin) + plaintexts[0]; for (let i = 1; i < blocks.length; i++) { decryptedMessage += mime.body.substring(blocks[i - 1].end + 1, blocks[i].begin + 1) + plaintexts[i]; } decryptedMessage += mime.body.substring(blocks[(blocks.length - 1)].end + 1); // enable base64 encoding if non-ASCII character(s) found let j = decryptedMessage.search(/[^\x01-\x7F]/); // eslint-disable-line no-control-regex if (j >= 0) { mime.headers['content-transfer-encoding'] = ['base64']; mime.body = EnigmailData.encodeBase64(decryptedMessage); } else { mime.body = decryptedMessage; mime.headers['content-transfer-encoding'] = ['8bit']; } let origCharset = getCharset(getHeaderValue(mime, 'content-type')); if (origCharset) { mime.headers['content-type'] = getHeaderValue(mime, 'content-type').replace(origCharset, charset); } else { mime.headers['content-type'] = getHeaderValue(mime, 'content-type') + "; charset=" + charset; } this.foundPGP = 1; return 1; } if (typeof mime.parts !== 'undefined' && mime.parts.length > 0) { var ret = 0; for (let part in mime.parts) { ret += this.decryptINLINE(mime.parts[part]); } return ret; } let ct = getContentType(getHeaderValue(mime, 'content-type')); EnigmailLog.DEBUG("decryptPermanently.jsm: Decryption skipped: " + ct + "\n"); return 0; }, stripHTMLFromArmoredBlocks: function(text) { var index = 0; var begin = text.indexOf("-----BEGIN PGP"); var end = text.indexOf("-----END PGP"); while (begin > -1 && end > -1) { let sub = text.substring(begin, end); sub = sub.replace(/(<([^>]+)>)/ig, ""); sub = sub.replace(/&[A-z]+;/ig, ""); text = text.substring(0, begin) + sub + text.substring(end); index = end + 10; begin = text.indexOf("-----BEGIN PGP", index); end = text.indexOf("-----END PGP", index); } return text; }, /****** * * We have the technology we can rebuild. * * Function to reassemble the message from the MIME Tree * into a String. * ******/ mimeToString: function(mime, topLevel) { EnigmailLog.DEBUG("decryptPermanently.jsm: mimeToString: part: '" + mime.partName + "', is of type '" + typeof(mime) + "'\n"); let ct = getContentType(getHeaderValue(mime, 'content-type')); if (!ct) { return ""; } let boundary = getBoundary(getHeaderValue(mime, 'content-type')); let msg = ""; if (mime.isBrokenByExchange) { EnigmailLog.DEBUG("decryptPermanently.jsm: mimeToString: MS-Exchange fix\n"); for (let j in this.allTasks) { if (this.allTasks[j].partName == mime.parts[0].partName) { boundary = EnigmailMime.createBoundary(); msg += getRfc822Headers(mime.headers, ct, "content-type"); msg += 'Content-Type: multipart/mixed; boundary="' + boundary + '"\r\n\r\n'; msg += "This is a multi-part message in MIME format."; msg += "\r\n--" + boundary + "\r\n"; msg += this.allTasks[j].data; msg += "\r\n--" + boundary + "--\r\n"; return msg; } } } else if (mime instanceof MimeMessageAttachment) { for (let j in this.allTasks) { if (this.allTasks[j].partName == mime.partName && this.allTasks[j].origName == mime.name) { let a = this.allTasks[j]; EnigmailLog.DEBUG("decryptPermanently.jsm: mimeToString: attaching " + j + " as '" + a.name + "'\n"); for (let header in mime.headers) { if (!(a.status == STATUS_OK && header == "content-type")) { msg += prettyPrintHeader(header, mime.headers[header]) + "\r\n"; } } if (a.type == "attachment") { if (a.status == STATUS_OK) { msg += "Content-Type: application/octet-stream; name=\"" + a.name + "\"\r\n"; msg += "Content-Disposition: attachment; filename\"" + a.name + "\"\r\n"; } msg += "Content-Transfer-Encoding: base64\r\n\r\n"; msg += EnigmailData.encodeBase64(a.data) + "\r\n"; } } } } else if (mime instanceof MimeContainer || mime instanceof MimeUnknown) { for (let j in this.allTasks) { if (this.allTasks[j].partName == mime.partName && this.allTasks[j].type == "mime") { let a = this.allTasks[j]; msg += a.data; mime.noBottomBoundary = true; } } } else if (mime instanceof MimeMessage && ct.substr(0, 5) == "text/") { let subct = mime.parts[0].headers['content-type']; if (subct) { mime.headers['content-type'] = subct; } subct = mime.parts[0].headers['content-transfer-encoding']; if (subct) { mime.headers['content-transfer-encoding'] = subct; } msg += getRfc822Headers(mime.headers, ct); msg += "\r\n" + mime.parts[0].body + "\r\n"; return msg; } else { if (!topLevel && (mime instanceof MimeMessage)) { let mimeName = mime.name; if (!mimeName || mimeName === "") { mimeName = getHeaderValue(mime, 'subject') + ".eml"; } msg += 'Content-Type: message/rfc822; name="' + EnigmailMime.encodeHeaderValue(mimeName) + '\r\n'; msg += 'Content-Transfer-Encoding: 7bit\r\n'; msg += 'Content-Disposition: attachment; filename="' + EnigmailMime.encodeHeaderValue(mimeName) + '"\r\n\r\n'; } msg += getRfc822Headers(mime.headers, ct); msg += "\r\n"; if (mime.body) { msg += mime.body + "\r\n"; } else if ((mime instanceof MimeMessage) && ct.substr(0, 5) != "text/") { msg += "This is a multi-part message in MIME format.\r\n"; } } for (let i in mime.parts) { let subPart = this.mimeToString(mime.parts[i], false); if (subPart.length > 0) { if (boundary && !(mime instanceof MimeMessage)) { msg += "--" + boundary + "\r\n"; } msg += subPart + "\r\n"; } } if (ct.indexOf("multipart/") === 0 && !(mime instanceof MimeContainer)) { if (!mime.noBottomBoundary) { msg += "--" + boundary + "--\r\n"; } } return msg; } }; /** * Format a mime header * * e.g. content-type -> Content-Type */ function formatHeader(headerLabel) { return headerLabel.replace(/^.|(-.)/g, function(match) { return match.toUpperCase(); }); } function formatMimeHeader(headerLabel, headerValue) { if (headerLabel.search(/^(sender|from|reply-to|to|cc|bcc)$/i) === 0) { return formatHeader(headerLabel) + ": " + EnigmailMime.formatHeaderData(EnigmailMime.formatEmailAddress(headerValue)); } else { return formatHeader(headerLabel) + ": " + EnigmailMime.formatHeaderData(EnigmailMime.encodeHeaderValue(headerValue)); } } function prettyPrintHeader(headerLabel, headerData) { let hdrData = ""; if (Array.isArray(headerData)) { let h = []; for (let i in headerData) { h.push(formatMimeHeader(headerLabel, GlodaUtils.deMime(headerData[i]))); } return h.join("\r\n"); } else { return formatMimeHeader(headerLabel, GlodaUtils.deMime(String(headerData))); } } function getHeaderValue(mimeStruct, header) { EnigmailLog.DEBUG("decryptPermanently.jsm: getHeaderValue: '" + header + "'\n"); try { if (header in mimeStruct.headers) { if (typeof mimeStruct.headers[header] == "string") { return mimeStruct.headers[header]; } else { return mimeStruct.headers[header].join(" "); } } else { return ""; } } catch (ex) { EnigmailLog.DEBUG("decryptPermanently.jsm: getHeaderValue: header not present\n"); return ""; } } /*** * get the formatted headers for MimeMessage objects * * @headerArr: Array of headers (key/value pairs), such as mime.headers * @ignoreHeadersArr: Array of headers to exclude from returning * * @return: String containing formatted header list */ function getRfc822Headers(headerArr, contentType, ignoreHeadersArr) { let hdrs = ""; let ignore = []; if (contentType.indexOf("multipart/") >= 0) { ignore = ['content-transfer-encoding', 'content-disposition', 'content-description' ]; } if (ignoreHeadersArr) { ignore = ignore.concat(ignoreHeadersArr); } for (let i in headerArr) { if (ignore.indexOf(i) < 0) { hdrs += prettyPrintHeader(i, headerArr[i]) + "\r\n"; } } return hdrs; } function getContentType(shdr) { try { shdr = String(shdr); return shdr.match(/([A-z-]+\/[A-z-]+)/)[1].toLowerCase(); } catch (e) { EnigmailLog.DEBUG("decryptPermanently.jsm: getContentType: " + e + "\n"); return null; } } // return the content of the boundary parameter function getBoundary(shdr) { try { shdr = String(shdr); return EnigmailMime.getBoundary(shdr); } catch (e) { EnigmailLog.DEBUG("decryptPermanently.jsm: getBoundary: " + e + "\n"); return null; } } function getCharset(shdr) { try { shdr = String(shdr); return EnigmailMime.getParameter(shdr, 'charset').toLowerCase(); } catch (e) { EnigmailLog.DEBUG("decryptPermanently.jsm: getCharset: " + e + "\n"); return null; } } function getProtocol(shdr) { try { shdr = String(shdr); return EnigmailMime.getProtocol(shdr).toLowerCase(); } catch (e) { EnigmailLog.DEBUG("decryptPermanently.jsm: getProtocol: " + e + "\n"); return ""; } } function getSMimeProtocol(shdr) { try { shdr = String(shdr); return shdr.match(/smime-type="?([A-z0-9'()+_,-./:=?]+)"?/)[1].toLowerCase(); } catch (e) { EnigmailLog.DEBUG("decryptPermanently.jsm: getSMimeProtocol: " + e + "\n"); return ""; } } function getPepSubject(mimeString) { EnigmailLog.DEBUG("decryptPermanently.jsm: getPepSubject()\n"); let subject = null; let emitter = { ct: "", firstPlainText: false, startPart: function(partNum, headers) { EnigmailLog.DEBUG("decryptPermanently.jsm: getPepSubject.startPart: partNum=" + partNum + "\n"); try { this.ct = String(headers.getRawHeader("content-type")).toLowerCase(); if (!subject && !this.firstPlainText) { let s = headers.getRawHeader("subject"); if (s) { subject = String(s); this.firstPlainText = true; } } } catch (ex) { this.ct = ""; } }, endPart: function(partNum) {}, deliverPartData: function(partNum, data) { EnigmailLog.DEBUG("decryptPermanently.jsm: getPepSubject.deliverPartData: partNum=" + partNum + " ct=" + this.ct + "\n"); if (!this.firstPlainText && this.ct.search(/^text\/plain/) === 0) { // check data this.firstPlainText = true; let o = EnigmailMime.extractSubjectFromBody(data); if (o) { subject = o.subject; } } } }; let opt = { strformat: "unicode", bodyformat: "decode" }; try { let p = new jsmime.MimeParser(emitter, opt); p.deliverData(mimeString); } catch (ex) {} return subject; } /** * Lazy deletion of original messages */ function deleteOriginalMail(msgHdr) { EnigmailLog.DEBUG("decryptPermanently.jsm: deleteOriginalMail(" + msgHdr.messageKey + ")\n"); let delMsg = function() { try { EnigmailLog.DEBUG("decryptPermanently.jsm: deleting original message " + msgHdr.messageKey + "\n"); let folderInfoObj = {}; msgHdr.folder.getDBFolderInfoAndDB(folderInfoObj).DeleteMessage(msgHdr.messageKey, null, true); } catch (e) { EnigmailLog.DEBUG("decryptPermanently.jsm: deletion failed. Error: " + e.toString() + "\n"); } }; EnigmailTimer.setTimeout(delMsg, 500); } diff --git a/package/encryption.jsm b/package/encryption.jsm index 647ca6fd..6e995d98 100644 --- a/package/encryption.jsm +++ b/package/encryption.jsm @@ -1,491 +1,493 @@ /*global Components: false, EnigmailCore: false, EnigmailLog: false, EnigmailPrefs: false, EnigmailApp: false, EnigmailLocale: false, EnigmailDialog: false */ /*jshint -W097 */ /* * 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/. */ "use strict"; var EXPORTED_SYMBOLS = ["EnigmailEncryption"]; const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; +Cu.import("resource://enigmail/lazy.jsm"); /*global EnigmailLazy: false */ Cu.import("resource://enigmail/core.jsm"); Cu.import("resource://enigmail/data.jsm"); /*global EnigmailData: false */ Cu.import("resource://enigmail/log.jsm"); Cu.import("resource://enigmail/prefs.jsm"); Cu.import("resource://enigmail/app.jsm"); Cu.import("resource://enigmail/locale.jsm"); Cu.import("resource://enigmail/dialog.jsm"); Cu.import("resource://enigmail/gpgAgent.jsm"); /*global EnigmailGpgAgent: false */ Cu.import("resource://enigmail/gpg.jsm"); /*global EnigmailGpg: false */ Cu.import("resource://enigmail/errorHandling.jsm"); /*global EnigmailErrorHandling: false */ Cu.import("resource://enigmail/execution.jsm"); /*global EnigmailExecution: false */ Cu.import("resource://enigmail/files.jsm"); /*global EnigmailFiles: false */ Cu.import("resource://enigmail/passwords.jsm"); /*global EnigmailPassword: false */ Cu.import("resource://enigmail/funcs.jsm"); /*global EnigmailFuncs: false */ Cu.import("resource://enigmail/keyRing.jsm"); /*global EnigmailKeyRing: false */ +const getGpgAgent = EnigmailLazy.loader("enigmail/gpgAgent.jsm", "EnigmailGpgAgent"); const nsIEnigmail = Ci.nsIEnigmail; var EC = EnigmailCore; const gMimeHashAlgorithms = [null, "sha1", "ripemd160", "sha256", "sha384", "sha512", "sha224", "md5"]; const ENC_TYPE_MSG = 0; const ENC_TYPE_ATTACH_BINARY = 1; const ENC_TYPE_ATTACH_ASCII = 2; const GPG_COMMENT_OPT = "Using GnuPG with %s - http://www.enigmail.net/"; const EnigmailEncryption = { getEncryptCommand: function(fromMailAddr, toMailAddr, bccMailAddr, hashAlgorithm, sendFlags, isAscii, errorMsgObj) { EnigmailLog.DEBUG("encryption.jsm: getEncryptCommand: hashAlgorithm=" + hashAlgorithm + "\n"); try { fromMailAddr = EnigmailFuncs.stripEmail(fromMailAddr); toMailAddr = EnigmailFuncs.stripEmail(toMailAddr); bccMailAddr = EnigmailFuncs.stripEmail(bccMailAddr); } catch (ex) { errorMsgObj.value = EnigmailLocale.getString("invalidEmail"); return null; } var defaultSend = sendFlags & nsIEnigmail.SEND_DEFAULT; var signMsg = sendFlags & nsIEnigmail.SEND_SIGNED; var encryptMsg = sendFlags & nsIEnigmail.SEND_ENCRYPTED; var usePgpMime = sendFlags & nsIEnigmail.SEND_PGP_MIME; var useDefaultComment = false; try { useDefaultComment = EnigmailPrefs.getPref("useDefaultComment"); } catch (ex) {} var hushMailSupport = false; try { hushMailSupport = EnigmailPrefs.getPref("hushMailSupport"); } catch (ex) {} var detachedSig = (usePgpMime || (sendFlags & nsIEnigmail.SEND_ATTACHMENT)) && signMsg && !encryptMsg; var toAddrList = toMailAddr.split(/\s*,\s*/); var bccAddrList = bccMailAddr.split(/\s*,\s*/); var k; var encryptArgs = EnigmailGpg.getStandardArgs(true); if (!useDefaultComment) encryptArgs = encryptArgs.concat(["--comment", GPG_COMMENT_OPT.replace(/%s/, EnigmailApp.getName())]); var angledFromMailAddr = ((fromMailAddr.search(/^0x/) === 0) || hushMailSupport) ? fromMailAddr : "<" + fromMailAddr + ">"; angledFromMailAddr = angledFromMailAddr.replace(/(["'`])/g, "\\$1"); if (signMsg && hashAlgorithm) { encryptArgs = encryptArgs.concat(["--digest-algo", hashAlgorithm]); } if (encryptMsg) { switch (isAscii) { case ENC_TYPE_MSG: encryptArgs.push("-a"); encryptArgs.push("-t"); break; case ENC_TYPE_ATTACH_ASCII: encryptArgs.push("-a"); } encryptArgs.push("--encrypt"); if (signMsg) encryptArgs.push("--sign"); if (sendFlags & nsIEnigmail.SEND_ALWAYS_TRUST) { encryptArgs.push("--trust-model"); encryptArgs.push("always"); } if ((sendFlags & nsIEnigmail.SEND_ENCRYPT_TO_SELF) && fromMailAddr) encryptArgs = encryptArgs.concat(["--encrypt-to", angledFromMailAddr]); for (k = 0; k < toAddrList.length; k++) { toAddrList[k] = toAddrList[k].replace(/'/g, "\\'"); if (toAddrList[k].length > 0) { encryptArgs.push("-r"); if (toAddrList[k].search(/^GROUP:/) === 0) { // groups from gpg.conf file encryptArgs.push(toAddrList[k].substr(6)); } else { encryptArgs.push((hushMailSupport || (toAddrList[k].search(/^0x/) === 0)) ? toAddrList[k] : "<" + toAddrList[k] + ">"); } } } for (k = 0; k < bccAddrList.length; k++) { bccAddrList[k] = bccAddrList[k].replace(/'/g, "\\'"); if (bccAddrList[k].length > 0) { encryptArgs.push("--hidden-recipient"); encryptArgs.push((hushMailSupport || (bccAddrList[k].search(/^0x/) === 0)) ? bccAddrList[k] : "<" + bccAddrList[k] + ">"); } } } else if (detachedSig) { encryptArgs = encryptArgs.concat(["-s", "-b"]); switch (isAscii) { case ENC_TYPE_MSG: encryptArgs = encryptArgs.concat(["-a", "-t"]); break; case ENC_TYPE_ATTACH_ASCII: encryptArgs.push("-a"); } } else if (signMsg) { encryptArgs = encryptArgs.concat(["-t", "--clearsign"]); } if (fromMailAddr) { encryptArgs = encryptArgs.concat(["-u", angledFromMailAddr]); } return encryptArgs; }, /** * Determine if the sender key ID or user ID can be used for signing and/or encryption * * @param sendFlags: Number - the send Flags; need to contain SEND_SIGNED and/or SEND_ENCRYPTED * @param fromMailAddr: String - the sender email address or key ID * * @return Object: * - keyId: String - the found key ID, or null if fromMailAddr is not valid * - errorMsg: String - the erorr message if key not valid, or null if key is valid */ determineOwnKeyUsability: function(sendFlags, fromMailAddr) { EnigmailLog.DEBUG("encryption.jsm: determineOwnKeyUsability: sendFlags=" + sendFlags + ", sender=" + fromMailAddr + "\n"); let keyList = []; let ret = { keyId: null, errorMsg: null }; let sign = (sendFlags & nsIEnigmail.SEND_SIGNED ? true : false); let encrypt = (sendFlags & nsIEnigmail.SEND_ENCRYPTED ? true : false); if (fromMailAddr.search(/^(0x)?[A-Z0-9]+$/) === 0) { // key ID specified let key = EnigmailKeyRing.getKeyById(fromMailAddr); keyList.push(key); } else { // email address specified keyList = EnigmailKeyRing.getKeysByUserId(fromMailAddr); } if (keyList.length === 0) { ret.errorMsg = EnigmailLocale.getString("errorOwnKeyUnusable", fromMailAddr); return ret; } if (sign) { keyList = keyList.reduce(function _f(p, keyObj) { if (keyObj.getSigningValidity().keyValid) p.push(keyObj); return p; }, []); } if (encrypt) { keyList = keyList.reduce(function _f(p, keyObj) { if (keyObj && keyObj.getEncryptionValidity().keyValid) p.push(keyObj); return p; }, []); } if (keyList.length === 0) { if (sign) { ret.errorMsg = EnigmailErrorHandling.determineInvSignReason(fromMailAddr); } else { ret.errorMsg = EnigmailErrorHandling.determineInvRcptReason(fromMailAddr); } } else { ret.keyId = keyList[0].fpr; } return ret; }, encryptMessageStart: function(win, uiFlags, fromMailAddr, toMailAddr, bccMailAddr, hashAlgorithm, sendFlags, listener, statusFlagsObj, errorMsgObj) { EnigmailLog.DEBUG("encryption.jsm: encryptMessageStart: uiFlags=" + uiFlags + ", from " + fromMailAddr + " to " + toMailAddr + ", hashAlgorithm=" + hashAlgorithm + " (" + EnigmailData.bytesToHex( EnigmailData.pack(sendFlags, 4)) + ")\n"); let keyUseability = this.determineOwnKeyUsability(sendFlags, fromMailAddr); if (!keyUseability.keyId) { EnigmailLog.DEBUG("encryption.jsm: encryptMessageStart: own key invalid\n"); errorMsgObj.value = keyUseability.errorMsg; statusFlagsObj.value = nsIEnigmail.INVALID_RECIPIENT | nsIEnigmail.NO_SECKEY | nsIEnigmail.DISPLAY_MESSAGE; return null; } var pgpMime = uiFlags & nsIEnigmail.UI_PGP_MIME; var hashAlgo = gMimeHashAlgorithms[EnigmailPrefs.getPref("mimeHashAlgorithm")]; if (hashAlgorithm) { hashAlgo = hashAlgorithm; } errorMsgObj.value = ""; if (!sendFlags) { EnigmailLog.DEBUG("encryption.jsm: encryptMessageStart: NO ENCRYPTION!\n"); errorMsgObj.value = EnigmailLocale.getString("notRequired"); return null; } if (!EnigmailCore.getService(win)) { EnigmailLog.ERROR("encryption.jsm: encryptMessageStart: not yet initialized\n"); errorMsgObj.value = EnigmailLocale.getString("notInit"); return null; } var encryptArgs = EnigmailEncryption.getEncryptCommand(fromMailAddr, toMailAddr, bccMailAddr, hashAlgo, sendFlags, ENC_TYPE_MSG, errorMsgObj); if (!encryptArgs) return null; var signMsg = sendFlags & nsIEnigmail.SEND_SIGNED; - var proc = EnigmailExecution.execStart(EnigmailGpgAgent.agentPath, encryptArgs, signMsg, win, listener, statusFlagsObj); + var proc = EnigmailExecution.execStart(getGpgAgent().agentPath, encryptArgs, signMsg, win, listener, statusFlagsObj); if (statusFlagsObj.value & nsIEnigmail.MISSING_PASSPHRASE) { EnigmailLog.ERROR("encryption.jsm: encryptMessageStart: Error - no passphrase supplied\n"); errorMsgObj.value = ""; } if (pgpMime && errorMsgObj.value) { EnigmailDialog.alert(win, errorMsgObj.value); } return proc; }, encryptMessageEnd: function(fromMailAddr, stderrStr, exitCode, uiFlags, sendFlags, outputLen, retStatusObj) { EnigmailLog.DEBUG("encryption.jsm: encryptMessageEnd: uiFlags=" + uiFlags + ", sendFlags=" + EnigmailData.bytesToHex(EnigmailData.pack(sendFlags, 4)) + ", outputLen=" + outputLen + "\n"); var pgpMime = uiFlags & nsIEnigmail.UI_PGP_MIME; var defaultSend = sendFlags & nsIEnigmail.SEND_DEFAULT; var signMsg = sendFlags & nsIEnigmail.SEND_SIGNED; var encryptMsg = sendFlags & nsIEnigmail.SEND_ENCRYPTED; retStatusObj.statusFlags = 0; retStatusObj.errorMsg = ""; retStatusObj.blockSeparation = ""; if (!EnigmailCore.getService().initialized) { EnigmailLog.ERROR("encryption.jsm: encryptMessageEnd: not yet initialized\n"); retStatusObj.errorMsg = EnigmailLocale.getString("notInit"); return -1; } EnigmailErrorHandling.parseErrorOutput(stderrStr, retStatusObj); exitCode = EnigmailExecution.fixExitCode(exitCode, retStatusObj); if ((exitCode === 0) && !outputLen) { exitCode = -1; } if (exitCode !== 0 && (signMsg || encryptMsg)) { // GnuPG might return a non-zero exit code, even though the message was correctly // signed or encryped -> try to fix the exit code var correctedExitCode = 0; if (signMsg) { if (!(retStatusObj.statusFlags & nsIEnigmail.SIG_CREATED)) correctedExitCode = exitCode; } if (encryptMsg) { if (!(retStatusObj.statusFlags & nsIEnigmail.END_ENCRYPTION)) correctedExitCode = exitCode; } exitCode = correctedExitCode; } EnigmailLog.DEBUG("encryption.jsm: encryptMessageEnd: command execution exit code: " + exitCode + "\n"); if (retStatusObj.statusFlags & nsIEnigmail.DISPLAY_MESSAGE) { if (retStatusObj.extendedStatus.search(/\bdisp:/) >= 0) { retStatusObj.errorMsg = retStatusObj.statusMsg; } else { if (fromMailAddr.search(/^0x/) === 0) { fromMailAddr = fromMailAddr.substr(2); } if (fromMailAddr.search(/^[A-F0-9]{8,40}$/i) === 0) { fromMailAddr = "[A-F0-9]+" + fromMailAddr; } let s = new RegExp("^(\\[GNUPG:\\] )?INV_(RECP|SGNR) [0-9]+ (\\<|0x)?" + fromMailAddr + "\\>?", "m"); if (retStatusObj.statusMsg.search(s) >= 0) { retStatusObj.errorMsg += "\n\n" + EnigmailLocale.getString("keyError.resolutionAction"); } else if (retStatusObj.statusMsg.length > 0) { retStatusObj.errorMsg = retStatusObj.statusMsg; } } } else if (retStatusObj.statusFlags & nsIEnigmail.INVALID_RECIPIENT) { retStatusObj.errorMsg = retStatusObj.statusMsg; } else if (exitCode !== 0) { retStatusObj.errorMsg = EnigmailLocale.getString("badCommand"); } return exitCode; }, encryptMessage: function(parent, uiFlags, plainText, fromMailAddr, toMailAddr, bccMailAddr, sendFlags, exitCodeObj, statusFlagsObj, errorMsgObj) { EnigmailLog.DEBUG("enigmail.js: Enigmail.encryptMessage: " + plainText.length + " bytes from " + fromMailAddr + " to " + toMailAddr + " (" + sendFlags + ")\n"); exitCodeObj.value = -1; statusFlagsObj.value = 0; errorMsgObj.value = ""; if (!plainText) { EnigmailLog.DEBUG("enigmail.js: Enigmail.encryptMessage: NO ENCRYPTION!\n"); exitCodeObj.value = 0; EnigmailLog.DEBUG(" <=== encryptMessage()\n"); return plainText; } var defaultSend = sendFlags & nsIEnigmail.SEND_DEFAULT; var signMsg = sendFlags & nsIEnigmail.SEND_SIGNED; var encryptMsg = sendFlags & nsIEnigmail.SEND_ENCRYPTED; if (encryptMsg) { // First convert all linebreaks to newlines plainText = plainText.replace(/\r\n/g, "\n"); plainText = plainText.replace(/\r/g, "\n"); // we need all data in CRLF according to RFC 4880 plainText = plainText.replace(/\n/g, "\r\n"); } var listener = EnigmailExecution.newSimpleListener( function _stdin(pipe) { pipe.write(plainText); pipe.close(); }, function _done(exitCode) {}); var proc = EnigmailEncryption.encryptMessageStart(parent, uiFlags, fromMailAddr, toMailAddr, bccMailAddr, null, sendFlags, listener, statusFlagsObj, errorMsgObj); if (!proc) { exitCodeObj.value = -1; EnigmailLog.DEBUG(" <=== encryptMessage()\n"); return ""; } // Wait for child pipes to close proc.wait(); var retStatusObj = {}; exitCodeObj.value = EnigmailEncryption.encryptMessageEnd(fromMailAddr, EnigmailData.getUnicodeData(listener.stderrData), listener.exitCode, uiFlags, sendFlags, listener.stdoutData.length, retStatusObj); statusFlagsObj.value = retStatusObj.statusFlags; statusFlagsObj.statusMsg = retStatusObj.statusMsg; errorMsgObj.value = retStatusObj.errorMsg; if ((exitCodeObj.value === 0) && listener.stdoutData.length === 0) exitCodeObj.value = -1; if (exitCodeObj.value === 0) { // Normal return EnigmailLog.DEBUG(" <=== encryptMessage()\n"); return EnigmailData.getUnicodeData(listener.stdoutData); } // Error processing EnigmailLog.DEBUG("enigmail.js: Enigmail.encryptMessage: command execution exit code: " + exitCodeObj.value + "\n"); return ""; }, encryptAttachment: function(parent, fromMailAddr, toMailAddr, bccMailAddr, sendFlags, inFile, outFile, exitCodeObj, statusFlagsObj, errorMsgObj) { EnigmailLog.DEBUG("encryption.jsm: EnigmailEncryption.encryptAttachment infileName=" + inFile.path + "\n"); statusFlagsObj.value = 0; sendFlags |= nsIEnigmail.SEND_ATTACHMENT; let asciiArmor = false; try { asciiArmor = EnigmailPrefs.getPrefBranch().getBoolPref("inlineAttachAsciiArmor"); } catch (ex) {} const asciiFlags = (asciiArmor ? ENC_TYPE_ATTACH_ASCII : ENC_TYPE_ATTACH_BINARY); let args = EnigmailEncryption.getEncryptCommand(fromMailAddr, toMailAddr, bccMailAddr, "", sendFlags, asciiFlags, errorMsgObj); if (!args) { return null; } const signMessage = (sendFlags & nsIEnigmail.SEND_SIGNED); if (signMessage) { args = args.concat(EnigmailPassword.command()); } //const inFilePath = EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePathReadonly(inFile.QueryInterface(Ci.nsIFile))); const fileContents = EnigmailFiles.readBinaryFile(inFile.QueryInterface(Ci.nsIFile)); const inFileName = inFile.QueryInterface(Ci.nsIFile).leafName; const outFilePath = EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePathReadonly(outFile.QueryInterface(Ci.nsIFile))); args = args.concat(["--yes", "-o", outFilePath, "--set-filename", inFileName]); let cmdErrorMsgObj = {}; const msg = EnigmailExecution.execCmd(EnigmailGpgAgent.agentPath, args, fileContents, exitCodeObj, statusFlagsObj, {}, cmdErrorMsgObj); if (exitCodeObj.value !== 0) { if (cmdErrorMsgObj.value) { errorMsgObj.value = EnigmailFiles.formatCmdLine(EnigmailGpgAgent.agentPath, args); errorMsgObj.value += "\n" + cmdErrorMsgObj.value; } else { errorMsgObj.value = "An unknown error has occurred"; } return ""; } return msg; }, registerOn: function(target) { target.encryptMessage = EnigmailEncryption.encryptMessage; target.encryptAttachment = EnigmailEncryption.encryptAttachment; } }; diff --git a/package/filters.jsm b/package/filters.jsm index 1f23a3d1..5faf0871 100644 --- a/package/filters.jsm +++ b/package/filters.jsm @@ -1,347 +1,403 @@ /*global Components: false, EnigmailDecryptPermanently: false, EnigmailCore: false, EnigmailLog: false, EnigmailLocale: false, EnigmailLazy: false */ /*jshint -W097 */ /* * 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/. */ "use strict"; var EXPORTED_SYMBOLS = ["EnigmailFilters"]; const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; Cu.import("resource://enigmail/lazy.jsm"); Cu.import("resource://enigmail/locale.jsm"); Cu.import("resource://enigmail/core.jsm"); -Cu.import("resource://enigmail/decryptPermanently.jsm"); +Cu.import("resource://enigmail/decryptPermanently.jsm"); /* global EnigmailDecryptPermanently: false */ +Cu.import("resource://enigmail/encryptPermanently.jsm"); /* global EnigmailEncryptPermanently: false */ Cu.import("resource://enigmail/log.jsm"); Cu.import("resource://enigmail/funcs.jsm"); /* global EnigmailFuncs: false */ Cu.import("resource://enigmail/streams.jsm"); /* global EnigmailStreams: false */ Cu.import("resource://enigmail/constants.jsm"); /* global EnigmailConstants: false */ Cu.import("resource://enigmail/data.jsm"); /* global EnigmailData: false */ +Cu.import("resource://enigmail/keyRing.jsm"); /* global EnigmailKeyRing: false */ Cu.import("resource:///modules/jsmime.jsm"); /*global jsmime: false*/ const getDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog"); var gNewMailListenerInitiated = false; /** * filter action for creating a decrypted version of the mail and * deleting the original mail at the same time */ const filterActionMoveDecrypt = { id: EnigmailConstants.FILTER_MOVE_DECRYPT, name: EnigmailLocale.getString("filter.decryptMove.label"), value: "movemessage", apply: function(aMsgHdrs, aActionValue, aListener, aType, aMsgWindow) { EnigmailLog.DEBUG("filters.jsm: filterActionMoveDecrypt: Move to: " + aActionValue + "\n"); var msgHdrs = []; for (var i = 0; i < aMsgHdrs.length; i++) { msgHdrs.push(aMsgHdrs.queryElementAt(i, Ci.nsIMsgDBHdr)); } EnigmailDecryptPermanently.dispatchMessages(msgHdrs, aActionValue, aListener, true); }, isValidForType: function(type, scope) { return true; }, validateActionValue: function(value, folder, type) { getDialog().alert(null, EnigmailLocale.getString("filter.decryptMove.warnExperimental")); if (value === "") { return EnigmailLocale.getString("filter.folderRequired"); } return null; }, allowDuplicates: false, isAsync: true, needsBody: true }; /** * filter action for creating a decrypted copy of the mail, leaving the original * message untouched */ const filterActionCopyDecrypt = { id: EnigmailConstants.FILTER_COPY_DECRYPT, name: EnigmailLocale.getString("filter.decryptCopy.label"), value: "copymessage", apply: function(aMsgHdrs, aActionValue, aListener, aType, aMsgWindow) { EnigmailLog.DEBUG("filters.jsm: filterActionCopyDecrypt: Copy to: " + aActionValue + "\n"); var msgHdrs = []; for (var i = 0; i < aMsgHdrs.length; i++) { msgHdrs.push(aMsgHdrs.queryElementAt(i, Ci.nsIMsgDBHdr)); } EnigmailDecryptPermanently.dispatchMessages(msgHdrs, aActionValue, aListener, false); }, isValidForType: function(type, scope) { return true; }, validateActionValue: function(value, folder, type) { if (value === "") { return EnigmailLocale.getString("filter.folderRequired"); } return null; }, allowDuplicates: false, isAsync: true, needsBody: true }; +/** + * filter action for to encrypt a mail to a specific key + */ +const filterActionEncrypt = { + id: EnigmailConstants.FILTER_ENCRYPT, + name: EnigmailLocale.getString("filter.encrypt.label"), + apply: function(aMsgHdrs, aActionValue, aListener, aType, aMsgWindow) { + EnigmailLog.DEBUG("filters.jsm: filterActionEncrypt: Encrypt to: " + aActionValue + "\n"); + + var msgHdrs = []; + + for (var i = 0; i < aMsgHdrs.length; i++) { + let msg = aMsgHdrs.queryElementAt(i, Ci.nsIMsgDBHdr); + + EnigmailDecryptPermanently.decryptMessage(msg,msg.folder.folderURL,true) + .then(function(value) { + EnigmailLog.DEBUG("decrypt: " + JSON.stringify(value) + "\n"); + let newMsg = msg.folder.GetMessageHeader(value); + EnigmailEncryptPermanently.encryptMessage(newMsg,aActionValue); + }); + } + }, + + isValidForType: function(type, scope) { + return true; + }, + + validateActionValue: function(value, folder, type) { + + EnigmailLog.DEBUG("filters.jsm: validateActionValue: Encrypt to: " + value + "\n"); + if (value === "") { + return EnigmailLocale.getString("filter.keyRequired"); + } + + let keyFound = EnigmailKeyRing.getKeyById(value); + + if (keyFound === null) { + EnigmailLog.DEBUG("filters.jsm: failed to find key by id: " + value + "\n"); + keyFound = EnigmailKeyRing.getValidKeyForRecipient(value); + } + + if (keyFound === null) { + return EnigmailLocale.getString("filter.keyNotFound", [value]); + } + + return null; + }, + + allowDuplicates: false, + isAsync: true, + needsBody: true +}; + function initNewMailListener() { EnigmailLog.DEBUG("filters.jsm: initNewMailListener()\n"); if (!gNewMailListenerInitiated) { let notificationService = Cc["@mozilla.org/messenger/msgnotificationservice;1"] .getService(Ci.nsIMsgFolderNotificationService); notificationService.addListener(newMailListener, notificationService.msgAdded); } gNewMailListenerInitiated = true; } function getIdentityForSender(senderEmail, msgServer) { let accountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager); let identities = accountManager.getIdentitiesForServer(msgServer); for (let i = 0; i < identities.length; i++) { let id = identities.queryElementAt(i, Ci.nsIMsgIdentity); if (id.email.toLowerCase() === senderEmail.toLowerCase()) { return id; } } return null; } var consumerList = []; function JsmimeEmitter(requireBody) { this.requireBody = requireBody; this.mimeTree = { partNum: "", headers: null, body: "", parent: null, subParts: [] }; this.stack = []; this.currPartNum = ""; } JsmimeEmitter.prototype = { createPartObj: function(partNum, headers, parent) { return { partNum: partNum, headers: headers, body: "", parent: parent, subParts: [] }; }, getMimeTree: function() { return this.mimeTree.subParts[0]; }, /** JSMime API **/ startMessage: function() { this.currentPart = this.mimeTree; }, endMessage: function() {}, startPart: function(partNum, headers) { EnigmailLog.DEBUG("filters.jsm: JsmimeEmitter.startPart: partNum=" + partNum + "\n"); //this.stack.push(partNum); let newPart = this.createPartObj(partNum, headers, this.currentPart); if (partNum.indexOf(this.currPartNum) === 0) { // found sub-part this.currentPart.subParts.push(newPart); } else { // found same or higher level this.currentPart.subParts.push(newPart); } this.currPartNum = partNum; this.currentPart = newPart; }, endPart: function(partNum) { EnigmailLog.DEBUG("filters.jsm: JsmimeEmitter.startPart: partNum=" + partNum + "\n"); this.currentPart = this.currentPart.parent; }, deliverPartData: function(partNum, data) { EnigmailLog.DEBUG("filters.jsm: JsmimeEmitter.deliverPartData: partNum=" + partNum + "\n"); if (this.requireBody) { if (typeof(data) === "string") { this.currentPart.body += data; } else { this.currentPart.body += EnigmailData.arrayBufferToString(data); } } } }; function processIncomingMail(url, requireBody, aMsgHdr) { EnigmailLog.DEBUG("filters.jsm: processIncomingMail()\n"); let inputStream = EnigmailStreams.newStringStreamListener(msgData => { let opt = { strformat: "unicode", bodyformat: "decode" }; try { let e = new JsmimeEmitter(requireBody); let p = new jsmime.MimeParser(e, opt); p.deliverData(msgData); for (let c of consumerList) { try { c.consumeMessage(e.getMimeTree(), msgData, aMsgHdr); } catch (ex) { EnigmailLog.DEBUG("filters.jsm: processIncomingMail: exception: " + ex.toString() + "\n"); } } } catch (ex) {} }); try { let channel = EnigmailStreams.createChannel(url); channel.asyncOpen(inputStream, null); } catch (e) { EnigmailLog.DEBUG("filters.jsm: processIncomingMail: open stream exception " + e.toString() + "\n"); } } function getRequireMessageProcessing(aMsgHdr) { let isInbox = aMsgHdr.folder.getFlag(Ci.nsMsgFolderFlags.CheckNew) || aMsgHdr.folder.getFlag(Ci.nsMsgFolderFlags.Inbox); let requireBody = false; let inboxOnly = true; let selfSentOnly = false; let processReadMail = false; for (let c of consumerList) { if (!c.incomingMailOnly) { inboxOnly = false; } if (!c.unreadOnly) { processReadMail = true; } if (!c.headersOnly) { requireBody = true; } if (c.selfSentOnly) { selfSentOnly = true; } } if (!processReadMail && aMsgHdr.isRead) return null; if (inboxOnly && !isInbox) return null; if (selfSentOnly) { let sender = EnigmailFuncs.parseEmails(aMsgHdr.author, true); let id = null; if (sender && sender[0]) { id = getIdentityForSender(sender[0].email, aMsgHdr.folder.server); } if (!id) return null; } EnigmailLog.DEBUG("filters.jsm: getRequireMessageProcessing: author: " + aMsgHdr.author + "\n"); let messenger = Cc["@mozilla.org/messenger;1"].getService(Ci.nsIMessenger); let msgSvc = messenger.messageServiceFromURI(aMsgHdr.folder.getUriForMsg(aMsgHdr)); let u = {}; msgSvc.GetUrlForUri(aMsgHdr.folder.getUriForMsg(aMsgHdr), u, null); let op = (u.value.spec.indexOf("?") > 0 ? "&" : "?"); let url = u.value.spec + op + "header=enigmailFilter"; return { url: url, requireBody: requireBody }; } const newMailListener = { msgAdded: function(aMsgHdr) { EnigmailLog.DEBUG("filters.jsm: newMailListener.msgAdded() - got new mail in " + aMsgHdr.folder.prettiestName + "\n"); if (consumerList.length === 0) return; let ret = getRequireMessageProcessing(aMsgHdr); if (ret) { processIncomingMail(ret.url, ret.requireBody, aMsgHdr); } } }; /** messageStructure - Object: - partNum: String - MIME part number - headers: Object(nsIStructuredHeaders) - MIME part headers - body: String or typedarray - the body part - parent: Object(messageStructure) - link to the parent part - subParts: Array of Object(messageStructure) - array of the sub-parts */ const EnigmailFilters = { registerAll: function() { var filterService = Cc["@mozilla.org/messenger/services/filters;1"].getService(Ci.nsIMsgFilterService); filterService.addCustomAction(filterActionMoveDecrypt); filterService.addCustomAction(filterActionCopyDecrypt); + filterService.addCustomAction(filterActionEncrypt); initNewMailListener(); }, /** * add a new consumer to listen to new mails * * @param consumer - Object * - headersOnly: Boolean - needs full message body? [FUTURE] * - incomingMailOnly: Boolean - only work on folder(s) that obtain new mail * (Inbox and folders that listen to new mail) * - unreadOnly: Boolean - only process unread mails * - selfSentOnly: Boolean - only process mails with sender Email == Account Email * - consumeMessage: function(messageStructure, rawMessageData, nsIMsgHdr) */ addNewMailConsumer: function(consumer) { EnigmailLog.DEBUG("filters.jsm: addNewMailConsumer()\n"); consumerList.push(consumer); }, removeNewMailConsumer: function(consumer) { } }; diff --git a/ui/locale/en-US/enigmail.properties b/ui/locale/en-US/enigmail.properties index 77485451..367fac51 100644 --- a/ui/locale/en-US/enigmail.properties +++ b/ui/locale/en-US/enigmail.properties @@ -1,772 +1,775 @@ Enigmail=Enigmail ##################################################################### # Strings used within enigmailCommon.js and enigmailCommon.jsm ##################################################################### enigAlert=Enigmail Alert enigConfirm=Enigmail Confirm enigInfo=Enigmail Information enigError=Enigmail Error enigPrompt=Enigmail Prompt dlgYes=&Yes dlgNo=&No dlgKeepSetting=Remember my answer and do not ask me again dlgNoPrompt=Do not show me this dialog again dlg.button.delete=&Delete dlg.button.cancel=&Cancel dlg.button.close=&Close dlg.button.continue=Con&tinue dlg.button.skip=&Skip dlg.button.overwrite=&Overwrite dlg.button.view=&View dlg.button.retry=&Retry dlg.button.ignore=&Ignore dlg.button.ok=&OK repeatPrefix=\n\nThis alert will repeat %S repeatSuffixSingular=more time. repeatSuffixPlural=more times. noRepeat=\n\nThis alert will not repeat until you upgrade Enigmail. pgpNotSupported=You seem to be using Enigmail together with PGP 6.x\n\nUnfortunately, PGP 6.x has a number of issues that prevent Enigmail from working correctly. Therefore, Enigmail does not support PGP 6.x anymore; please switch to GnuPG (GPG) instead.\n\nIf you need help on switching to GnuPG, check the Help section of the Enigmail homepage. initErr.howToFixIt=In order to use Enigmail, GnuPG is required. If you did not install GnuPG yet, the easiest way to do this is using the "Setup Wizard" button below. initErr.setupWizard.button=&Setup Wizard passphraseCleared=The passphrase has been cleared. cannotClearPassphrase=You are using a non-standard tool (such as gnome-keyring) for passphrase handling. Clearing the passphrase is therefore not possible from within Enigmail. noPhotoAvailable=No Photo available debugLog.title=Enigmail Debug Log error.photoPathNotReadable=Photo path '%S' is not readable generalError=Error: %S # Strings in configure.jsm enigmailCommon.versionSignificantlyChanged=This new version of Enigmail has significant changes in the handling of preferences and options. We tried to transfer the old settings to this new version. However, we cannot cover all cases automatically. Please double check the resulting new preferences and options. enigmailCommon.checkPreferences=Check Preferences ... preferences.defaultToPgpMime=We have changed the default message encoding in Enigmail from Inline-PGP to PGP/MIME. We recommend you keep this as default.\n\nIf you still wish to use Inline-PGP by default, you can do so in the Account Settings under OpenPGP Security. ##################################################################### # Strings in enigmailAbout.js ##################################################################### usingVersion=Running Enigmail version %S enigmailPepVersion=Enigmail/p≡p version %S usingAgent=Using %1$S executable %2$S to encrypt and decrypt agentError=ERROR: Failed to access Enigmail core service! ##################################################################### # Strings in enigmailKeygen.js ##################################################################### accessError=Error in accessing Enigmail service onlyGPG=Key generation only works with GnuPG (not with PGP)! keygenComplete=Key generation completed! Identity <%S> will be used for signing. revokeCertRecommended=We highly recommend to create a revocation certificate for your key. This certificate can be used to invalidate your key, e.g. in case your secret key gets lost or compromised. Do you want to create such a revocation certificate now? keyMan.button.generateCert=&Generate Certificate genCompleteNoSign=Key generation completed! genGoing=Key generation already in progress! passNoMatch=Passphrase entries do not match; please re-enter passCheckBox=Please check box if specifying no passphrase for key passUserName=Please specify user name for this identity keygen.missingUserName=There is no name specified for the selected account/identity. Please enter a value in the field "Your name" in the account settings. keygen.passCharProblem=You are using special characters in your passphrase. Unfortunately, this can cause troubles for other applications. We recommend you choose a passphrase consisting only of any of these characters:\na-z A-Z 0-9 /.;:-,!?(){}[]%* passSpaceProblem=Due to technical reasons, your passphrase may not start or end with a space character. changePassFailed=Changing the passphrase failed. keyConfirm=Generate public and private keys for '%S'? keyMan.button.generateKey=&Generate Key keyAbort=Abort key generation? keyMan.button.generateKeyAbort=&Abort Key Generation keyMan.button.generateKeyContinue=&Continue Key Generation expiryTooLong=You cannot create a key that expires in more than 100 years. expiryTooLongShorter=You cannot create a key that expires in more than 90 years. expiryTooShort=Your key must be valid for at least one day. dsaSizeLimit=DSA signing keys are limited to 3072 bits. The key size will be reduced accordingly. keyGenFailed=The key generation failed. Please check the Enigmail console (Menu Enigmail > Debugging Enigmail) for details. setKeyExpirationDateFailed=The expiration date could not be changed # Strings in enigmailMessengerOverlay.js securityInfo=Enigmail Security Info\n\n enigHeader=Enigmail: enigContentNote=Enigmail: *Attachments to this message have not been signed or encrypted*\r\n\r\n possiblyPgpMime=Possibly PGP/MIME encrypted or signed message; use 'Decrypt/Verify' function to verify noDecrypted=No decrypted message to save!\nUse Save command from File menu noMessage=No message to save! useButton=Please use 'Decrypt/Verify' function to decrypt message saveHeader=Enigmail: Save decrypted message saveAttachmentHeader=Enigmail: Save decrypted attachment noTempDir=Could not find a temporary directory to write to\nPlease set the TEMP environment variable attachmentPgpKey=The attachment '%S' you are opening appears to be an OpenPGP key file.\n\nClick 'Import' to import the keys contained or 'View' to view the file contents in a browser window beginPgpPart=********* *BEGIN ENCRYPTED or SIGNED PART* ********* endPgpPart=********** *END ENCRYPTED or SIGNED PART* ********** notePartEncrypted=Enigmail: *Parts of the message have NOT been signed or encrypted* noteCutMessage=Enigmail: *Multiple message blocks found -- decryption/verification aborted* decryptOkNoSig=Warning\n\nDecryption was successful, but the signature could not be verified correctly msgOvl.button.contAnyway=&Continue Anyway signature.verifiedOK=The signature for attachment %S was successfully verified signature.verifyFailed=The signature for attachment %S could not be verified attachment.noMatchToSignature=Could not match attachment '%S' to a signature file attachment.noMatchFromSignature=Could not match signature file '%S' to an attachment fixBrokenExchangeMsg.failed=Did not succeed to repair message. enigmail.msgViewColumn.label=Enigmail enigmailPep.msgViewColumn.label=Enigmail/p≡p wksNoIdentity=This key is not linked to any of your email accounts. Please add an account for at least one of the following email addresse(s):\n\n%S wksConfirmSuccess=Confirmation email sent. wksConfirmFailure=Sending the confirmation email failed. fileSaveError=Error in saving to file %S autocrypt.importSetupKey.invalidMessage=Error - could not read setup message. The message seems to be corrupted. Please try to create a new setup message on your "other" device. autocrypt.importSetupKey.invalidKey=Error - the key could not be imported. The key is either not supported by your version of GnuPG, or it got corrupted. autocrypt.importSetupKey.wrongPasswd=The password you entered is wrong. Do you want to retry? autocrypt.importSetupKey.success=The Autocrypt setup message was processed successfully. Autocrypt is now available for your account '%S'. ##################################################################### # Strings in enigmailMsgComposeOverlay.js ##################################################################### keysToExport=Select OpenPGP Keys to Insert keysToUse=Select OpenPGP Key(s) to use for %S pubKey=Public key for %S\n windowLocked=Compose window is locked; send cancelled sendUnencrypted=Failed to initialize Enigmail.\nSend unencrypted message? composeSpecifyEmail=Please specify your primary email address, which will be used to choose the signing key for outgoing messages.\n If you leave it blank, the FROM address of the message will be used to choose the signing key. sendingHiddenRcpt=This message has BCC (blind copy) recipients. If this message is encrypted, it is possible to hide the BCC recipients but users of some products (e.g. PGP Corp.) will not be able to decrypt the message. Given this, we recommend to avoid BCC-emails with encrypted messages. sendWithHiddenBcc=Hide BCC recipients sendWithShownBcc=Encrypt normally sendingNews=Encrypted send operation aborted.\n\nThis message cannot be encrypted because there are newsgroup recipients. Please re-send the message without encryption. sendToNewsWarning=Warning: you are about to send an encrypted email to a newsgroup.\n\nThis is discouraged because it only makes sense if all members of the group can decrypt the message, i.e. the message needs to be encrypted with the keys of all group participants. Please send this message only if you know exactly what you are doing.\n\nContinue? hasHTML=HTML mail warning:\nThis message may contain HTML, which could cause signing/encryption to fail. To avoid this in the future, you should press the SHIFT key when clicking on the Compose/Reply button to send signed mail.\nIf you sign mail by default, you should uncheck the 'Compose Messages in HTML' preference box to permanently disable HTML mail for this mail account. strippingHTML=Message contains HTML formatting information that will be lost when converting to plain text for signing/encryption. Do you wish to proceed? msgCompose.button.sendAnyway=&Send Message Anyway attachWarning=Attachments to this message are not local, they cannot be encrypted. In order to encrypt the attachments, store them as local files first and attach these files. Do you wish to send the message anyway? quotedPrintableWarn=You have enabled 'quoted-printable' encoding for sending messages. This may result in incorrect decryption and/or verification of your message.\nDo you wish to turn off sending 'quoted-printable' messages now? minimalLineWrapping=You have set line wrapping to %S characters. For correct encryption and/or signing, this value needs to be at least 68.\nDo you wish to change line wrapping to 68 characters now? warning=Warning signIconClicked=You have manually modified signing. Therefore, while you are composing this message, (de)activating signing does not depend anymore on (de)activating encryption. errorOwnKeyUnusable=The key ID '%S' configured for the current identity does not yield a usable OpenPGP key.\n\nPlease ensure that you have a valid, not expired OpenPGP key and that your account settings point to that key.\nIf your key is not expired, then check if you did set Owner trust to full or ultimate. msgCompose.cannotSaveDraft=Error while saving draft msgCompose.internalEncryptionError=Internal Error: promised encryption disabled msgCompose.internalError=An internal error has occurred. msgCompose.toolbarTxt.signAndEncrypt=This message will be signed and encrypted msgCompose.toolbarTxt.signOnly=This message will be signed msgCompose.toolbarTxt.encryptOnly=This message will be encrypted msgCompose.toolbarTxt.noEncryption=This message will be unsigned and unencrypted msgCompose.toolbarTxt.disabled=Enigmail is disabled for the selected identity msgCompose.encryptedSubjectStub=Encrypted Message msgCompose.detailsButton.label=Details ... msgCompose.detailsButton.accessKey=D msgCompose.pepSendUnknown=Unknown msgCompose.pepSendUnsecure=Unsecure msgCompose.pepSendSecure=Secure msgCompose.pepSendTrusted=Secure & Trusted pep.alert.disabledForIdentity=p≡p is disabled for the current identity. Please enable p≡p via the Enigmail/p≡p preferences. pep.alert.weakReply=You’re about to forward or reply to a secure message as insecure. If you choose to proceed, confidential information might be leaked putting you and your communication partner at risk. Are you sure you want to continue? # note: should end with double newline: sendAborted=Send operation aborted.\n\n # details: keyNotTrusted=Not enough trust for key '%S' keyNotFound=Key '%S' not found keyRevoked=Key '%S' revoked keyExpired=Key '%S' expired statPGPMIME=PGP/MIME statSMIME=S/MIME statSigned=SIGNED statEncrypted=ENCRYPTED statPlain=UNSIGNED and UNENCRYPTED offlineSave=Save %1$S message to %2$S in Unsent Messages folder? onlineSend=Send %1$S message to %2$S? encryptKeysNote=Note: The message is encrypted for the following User ID's / Keys: %S hiddenKey= signFailed=Error in Enigmail; Encryption/signing failed; send unencrypted message? msgCompose.button.sendUnencrypted=&Send Unencrypted Message recipientsSelectionHdr=Select Recipients for Encryption configureNow=You did not yet configure Enigmail security for the selected identity. Do you want to do this now? # encryption/signing status and associated reasons: encryptMessageAuto=Encrypt Message (auto) encryptMessageNorm=Encrypt Message signMessageAuto=Sign Message (auto) signMessageNorm=Sign Message encryptOff=Encryption: OFF encryptOnWithReason=Encryption: ON (%S) encryptOffWithReason=Encryption: OFF (%S) encryptOn=Encryption: ON signOn=Signing: ON signOff=Signing: OFF signOnWithReason=Signing: ON (%S) signOffWithReason=Signing: OFF (%S) reasonEnabledByDefault=enabled by default reasonManuallyForced=manually forced reasonByRecipientRules=forced by recipient rules reasonByAutoEncryption=forced by auto encryption reasonByConflict=due to conflict in recipient rules reasonByEncryptionMode=due to encryption mode # should not be used anymore: encryptYes=Message will be encrypted encryptNo=Message will not be encrypted # should not be used anymore: signYes=Message will be signed signNo=Message will not be signed # PGP/MIME status: pgpmimeNormal=Protocol: PGP/MIME inlinePGPNormal=Protocol: Inline PGP smimeNormal=Protocol: S/MIME pgpmimeAuto=Protocol: PGP/MIME (auto) inlinePGPAuto=Protocol: Inline PGP (auto) smimeAuto=Protocol: S/MIME (auto) # should not be used anymore pgpmimeYes=PGP/MIME will be used pgpmimeNo=Inline PGP will be used # Attach own key status (tooltip strings): attachOwnKeyNo=Your own key will not be attached attachOwnKeyYes=Your own key will be attached attachOwnKeyDisabled=Your own key cannot be attached. You have to select a specific key\nin the OpenPGP section of the Account Settings to enable this feature. rulesConflict=Conflicting per-recipient rules detected\n%S\n\nSend message with these settings? msgCompose.button.configure=&Configure msgCompose.button.send=&Send Message msgCompose.button.save=&Save Message # Strings in enigmailMsgHdrViewOverlay.js keyNeeded=Public key %S needed to verify signature keyUsed=Public key %S used to verify signature clickDecrypt=; use 'Decrypt/Verify' function clickDecryptRetry=; use 'Decrypt/Verify' function to retry clickDetailsButton=; click on 'Details' button for more information clickImportButton=; click on the 'Import Key' button to import the key keyTypeUnsupported=; the key type is not supported by your version of GnuPG msgPart=Part of the message %S msgSigned=signed msgSignedUnkownKey=signed with unknown key msgEncrypted=encrypted msgSignedAndEnc=signed and encrypted unverifiedSig=Unverified signature incompleteDecrypt=Decryption incomplete needKey=Error - no matching private/secret key found to decrypt message failedDecrypt=Error - decryption failed badPhrase=Error - bad passphrase failedDecryptVerify=Error - decryption/verification failed viewInfo=; View > Message security info for details decryptedMsg=Decrypted message decryptedMsgWithFormatError=Decrypted message (restored broken PGP email format probably caused by an old Exchange server, so that the result might not be perfect to read) usedAlgorithms=Used Algorithms: %1$S and %2$S pepStatusInfo.text=p≡p Message Status. pepStatusInfo.title.m3=Under Attack pepStatusInfo.info.m3=This message is not secure and has been tampered with. pepStatusInfo.title.m1=Mistrusted pepStatusInfo.info.m1=This message has a communication partner that has previously been marked as mistrusted pepStatusInfo.title.r0=Unknown pepStatusInfo.info.r0=This message does not contain enough information to determine if it is secure. pepStatusInfo.title.r1=Cannot Decrypt pepStatusInfo.info.r1=This message cannot be decrypted because the key is not available. pepStatusInfo.title.r2=Cannot Decrypt pepStatusInfo.info.r2=This message cannot be decrypted because the key is not available. pepStatusInfo.title.r3=Unsecure pepStatusInfo.info.r3=This message is unsecure. pepStatusInfo.title.r4=Unsecure for Some pepStatusInfo.info.r4=This message is unsecure for some communication partners. pepStatusInfo.title.r5=Unreliable Security pepStatusInfo.info.r5=This message has unreliable protection. pepStatusInfo.title.r6=Secure... pepStatusInfo.info.r6=This message is secure but you still need to verify the identity of your communication partner. pepStatusInfo.title.r7=Secure & Trusted pepStatusInfo.info.r7=This message is secure and trusted. pepStatusInfo.color.green=Green pepStatusInfo.color.yellow=Yellow pepStatusInfo.color.red=Red pepRevokeTrust.question=Do you really want to cancel the trust for %S? pepRevokeMistrust.question=Do you really want to re-trust the key for %S? pepRevokeTrust.doRevoke=Cancel &trust wksConfirmationReq=Webkey Directory Confirmation Request wksConfirmationReq.message=This message has been sent by your email provider to confirm deployment of your OpenPGP public key\nin their Web Key Directory.\nProviding your public key helps others to discover your key and thus being able to encrypt messages to you.\n\nIf you want to deploy your key in the Web Key Directory now, please click on the button "Confirm Request" in the status bar.\nOtherwise, simply ignore this message. wksConfirmationReq.button.label=Confirm Request autocryptSetupReq=Perform Autocrypt Setup autocryptSetupReq.button.label=Start Setup autocryptSetupReq.setupMsg.desc=This message contains all information to transfer your Autocrypt settings along with your secret key securely from your original device. autocryptSetupReq.setupMsg.backup=You can keep this message and use it as a backup for your secret key. If you want to do this, you should write down the backup code and store it securely. autocryptSetupReq.message.import=To import the settings and key(s) in Enigmail, please click on the "Start Setup" button in the status bar. autocryptSetupReq.message.sent=Please click on the message on your new device and follow the instuctions to import the settings. # strings in pref-enigmail.js oldGpgVersion20=Enigmail initialization failed.\n\nYou are using GnuPG version %1$S, which is not supported anymore. Enigmail requires GnuPG version %2$S or newer. Please upgrade your GnuPG installation, or Enigmail will not work. locateGpg=Locate GnuPG program invalidGpgPath=GnuPG cannot be executed with the path provided. Enigmail is therefore deactivated until you change the path to GnuPG again or until you restart the application. warningsAreReset=All warnings have been reset. prefs.gpgFound=GnuPG was found in %S prefs.gpgNotFound=Could not find GnuPG prefs.warnAskNever=Warning: activating this option will result in unencrypted emails without any further information if there is no key for one of the recipients -- Enigmail will not inform you if this happens! prefs.warnIdleTimeForUnknownAgent=Cannot connect to gpg-agent. Maybe your system uses a specialized tool for passphrase handling (e.g. gnome-keyring, seahorse-agent, KDE wallet manager, ...). Unfortunately Enigmail cannot control the passphrase timeout for the tool you are using. Therefore the respective timeout settings in Enigmail are disregarded. prefEnigmail.oneKeyserverOnly=Error - you can only specify one keyserver for automatic downloading of missing OpenPGP keys. # Strings used in components/enigmail.js # (said file also re-uses some strings from above) enterAdminPin=Please type in the ADMIN PIN of your SmartCard enterCardPin=Please type your SmartCard PIN notInit=Error - Enigmail service not yet initialized badCommand=Error - encryption command failed cmdLine=command line and output: notRequired=Error - no encryption required notComplete=Error - key generation not yet completed invalidEmail=Error - invalid email address(es) noPassphrase=Error - no passphrase supplied noPGPblock=Error - No valid armored OpenPGP data block found unverifiedReply=Indented message part (reply) was probably modified keyInMessageBody=Key in message body found. Click 'Import Key' to import the key sigMismatch=Error - Signature mismatch cantImport=Error in importing public key\n\n doImportOne=Import %1$S (%2$S)? doImportMultiple=Import the following keys?\n\n%S previewFailed=Can't read public key file. # Strings used in errorHandling.jsm sc.wrongCardAvailable=The SmartCard %1$S found in your reader cannot be used to process the message.\nPlease insert your SmartCard %2$S and repeat the operation. sc.insertCard=The operation requires your SmartCard %S.\nPlease insert the required SmartCard and repeat the operation. sc.removeCard=The operation requires no SmartCard to be in the reader.\nPlease remove your SmartCard and repeat the operation. sc.noCardAvailable=No SmartCard could be found in your reader\nPlease insert your SmartCard and repeat the operation. sc.noReaderAvailable=Your SmartCard reader could not be accessed\nPlease attach your SmartCard reader, insert your card, and repeat the operation. keyError.keySpecNotFound=The email address "%S" cannot be matched to a key on your keyring. keyError.keyIdNotFound=The configured key ID "%S" cannot be found on your keyring. keyError.resolutionAction=Please select a valid key in the OpenPGP section of your Account Settings. missingPassphrase=Missing passphrase errorHandling.gpgAgentInvalid=Your system is running a version of gpg-agent that is not suitable for your GnuPG version. errorHandling.gpgAgentError=GnuPG reported an error in the communication with gpg-agent (a component of GnuPG). errorHandling.dirmngrError=GnuPG reported an error in the communication with dirmngr (a component of GnuPG). errorHandling.pinentryError=GnuPG cannot query your passphrase via pinentry. errorHandling.readFaq=This is a system setup or configuration error that prevents Enigmail from working properly and cannot be fixed automatically.\n\nWe strongly recommend that you consult our support web site at https://enigmail.net/faq. gpgNotFound=Unable to locate GnuPG program '%S'.\nMake sure you have set the GnuPG executable path correctly in the Enigmail Preferences. gpgNotInPath=Unable to locate GnuPG executable in the PATH.\nMake sure you have set the GnuPG executable path correctly in the Enigmail Preferences. enigmailNotAvailable=Enigmail core Service not available gpgAgentNotStarted=Could not start the gpg-agent program which is needed for your GnuPG version %S. prefUntrusted=UNTRUSTED prefRevoked=REVOKED KEY prefExpiredKey=EXPIRED KEY prefExpired=EXPIRED prefGood=Good signature from %S prefBad=BAD signature from %S failCancel=Error - Key receive cancelled by user failNoServer=Error - No keyserver specified to receive key from failNoID=Error - No key ID specified to receive key for failKeyExtract=Error - key extraction command failed notFirstBlock=Error - First OpenPGP block not public key block importKeyConfirm=Import public key(s) embedded in message? failKeyImport=Error - key importing failed fileWriteFailed=Failed to write to file %S importKey=Import public key %S from keyserver: uploadKey=Send public key %S to keyserver: keyId=Key ID keyAndSigDate=Key ID: 0x%1$S / Signed on: %2$S keyFpr=Key fingerprint: %S noEmailProvided=You did not provide an email address! keyAlreadySigned=The key is already signed, you cannot sign it twice. ##################################################################### # Strings used in enigmailKeySelection.js ##################################################################### selKeyExpired=expired %S createdHeader=Created atLeastOneKey=No key selected! You have to select at least one key to accept this dialog fewerKeysThanRecipients=You have selected a smaller number of keys than recipients. Are you sure that the list of keys to encrypt is complete? userSel.button.goBack=Select more Keys userSel.secretKeySel.title=Select a Secret OpenPGP Key to Sign Your Messages userSel.problemNoKey=No valid key userSel.problemMultipleKeys=Multiple keys # should be same as thunderbird ENTITY sendLaterCmd.label: sendLaterCmd.label=Send Later # Strings used in enigmailAttachmentDialog.js pgpMimeNote=NOTE: PGP/MIME is only supported by a limited number of mail clients! On Windows only Mozilla/Thunderbird, Sylpheed, Pegasus and Mulberry are known to support this standard; on Linux/UNIX and Mac OS X most popular mail clients support it. If you are unsure, select the %S option. first=first second=second # Strings used in am-enigprefs.js / enigmailEditIdentity.js encryptKeyHeader=Select OpenPGP Key for Encryption identityName=Identity: %S # Strings used in enigmailSingleRcptSettings.js noEncryption=You have activated encryption, but you did not select a key. In order to encrypt mails sent to %1$S, you need to specify one or several valid key(s) from your key list. Do you want to disable encryption for %2$S? noKeyToUse=(none - no encryption) noEmptyRule=The Rule may not be empty! Please set an email address in the Rule field. invalidAddress=The email address(es) you have entered are not valid. You should not set the names of the recipients, just the email addresses. E.g.:\nInvalid: Some Name \nValid: some.name@address.net noCurlyBrackets=The curly brackets {} have a special meaning and should not be used in the email addresses. If you want to modify the matching behavior for this rule, use the 'Apply rule if recipient ...' option.\nMore information is available from the Help button. # Strings used in enigmailRulesEditor.js never=Never always=Always possible=Possible deleteRule=Really delete the selected rule? nextRcpt=(Next recipient) negateRule=Not addKeyToRule=Add key %1$S (%2$S) to per-recipient rule # Strings used in enigmailSearchKey.js needOnline=The function you have selected is not available in offline mode. Please go online and try again. protocolNotSupported=The protocol '%S://' that you have selected is not supported for downloading OpenPGP keys. gpgkeysDisabled=It might help to enable the option 'extensions.enigmail.useGpgKeysTool'. noKeyserverConn=Could not connect to keyserver at %S. keyDownloadFailed=Failed to download key from keyserver. Status message is:\n%S internalError=An internal error occurred. The keys could not be downloaded or imported. noKeyFound=We are sorry, could not find any key that would match the specified search criteria. # gpgkeys_%S is one of the gpg command line tools gpgkeys_hkp, gpgkeys_ldap, etc. gpgKeysFailed=Failed to search or download key from keyserver: gpgkeys_%S could not be executed. # Strings in enigmailEditKeyTrustDlg.xul setKeyTrustFailed=Setting owner trust failed # Strings in enigmailSignKeyDlg.js signKeyFailed=Key signing failed alreadySigned.label=Note: the key %S is already signed with the selected private key. alreadySignedexportable.label=Note: the key %S is already signed exportable with the selected private key. A local signature does not make sense. partlySigned.label=Note: some user IDs of key %S are already signed with the selected private key. noTrustedOwnKeys=No eligible key found for signing! You need at least one fully trusted private key in order to sign keys. # Strings in enigmailKeyManager.js keyMan.loadingKeys=Loading keys, please wait ... keyValid.unknown=unknown keyValid.invalid=invalid keyValid.disabled=disabled keyValid.revoked=revoked keyValid.expired=expired keyValid.noSubkey=no valid subkey keyTrust.untrusted=untrusted keyTrust.marginal=marginal keyTrust.full=trusted keyTrust.ultimate=ultimate keyTrust.group=(group) keyType.public=pub keyType.publicAndSec=pub/sec keyMan.enableKey=Enable Key keyMan.disableKey=Disable Key userAtt.photo=User attribute (JPEG image) asciiArmorFile=ASCII Armored Files (*.asc) importKeyFile=Import OpenPGP Key File gnupgFile=GnuPG Files saveRevokeCertAs=Create & Save Revocation Certificate revokeCertOK=The revocation certificate has been successfully created. You can use it to invalidate your public key, e.g. in case you would lose your secret key.\n\nPlease transfer it to a medium which can be stored away safely such as a CD or USB stick. If somebody gains access to this certificate they can use it to render your key unusable. revokeCertFailed=The revocation certificate could not be created. addUidOK=User ID added successfully addUidFailed=Adding the User ID failed noKeySelected=You should select at least one key in order to perform the selected operation exportToFile=Export Public Key To File exportKeypairToFile=Export Secret and Public Key To File exportSecretKey=Do you want to include the secret key in the saved OpenPGP key file? saveKeysOK=The keys were successfully saved saveKeysFailed=Saving the keys failed importKeysFailed=Importing the keys failed enableKeyFailed=Enabling/disabling the keys failed specificPubKeyFilename=%1$S (0x%2$S) pub specificPubSecKeyFilename=%1$S (0x%2$S) pub-sec defaultPubKeyFilename=Exported-public-keys defaultPubSecKeyFilename=Exported-public-and-secret-keys noSecretKeys=No secret keys found.\n\nDo you want to generate your own key now? sendKeysOk=Key(s) sent successfully sendKeysFailed=Sending of keys failed receiveKeysOk=Key(s) updated successfully receiveKeysFailed=Downloading of keys failed importFromClip=Do you want to import some key(s) from clipboard? importFromUrl=Download public key from this URL: copyToClipbrdFailed=Could not copy the selected key(s) to the clipboard. copyToClipbrdOK=Key(s) copied to clipboard deleteSecretKey=WARNING: You are about to delete a secret key!\nIf you delete your secret key, you will no longer be able to decrypt any messages encrypted for that key, and you cannot revoke your key anymore.\n\nDo you really want to delete BOTH, the secret key and the public key\n'%S'? deleteMix=WARNING: You are about to delete secret keys!\nIf you delete your secret key, you will no longer be able to decrypt any messages encrypted for that key.\n\nDo you really want to delete BOTH, the selected secret and public keys? deletePubKey=Do you want to delete the public key\n'%S'? deleteSelectedPubKey=Do you want to delete the public keys? deleteKeyFailed=The key could not be deleted. revokeKeyQuestion=You are about to revoke the key '%S'.\n\nYou will no longer be able to sign with this key, and once distributed, others will no longer be able to encrypt with that key. You can still use the key to decrypt old messages.\n\nDo you want to proceed? revokeKeyOk=The key has been revoked. If your key is available on a key server, it is recommended to re-upload it, so that others can see the revocation. revokeKeyFailed=The key could not be revoked. revokeKeyNotPresent=You have no key (0x%S) which fits to this revocation certificate!\n\nIf you have lost your key, you must import it (e.g. from a keyserver) before the revocation certificate! revokeKeyAlreadyRevoked=The key 0x%S has already been revoked. refreshAllQuestion=You did not select any key. Would you like to refresh ALL keys? refreshKeyServiceOn.warn=Warning: Your keys are currently being refreshed in the background as safely as possible.\nRefreshing all your keys at once will unnecessarily reveal information about you.\nDo you really want to do this? refreshKey.warn=Warning: depending on the number of keys and the connection speed, refreshing all keys could be quite a lengthy process! downloadContactsKeys.warn=Warning: depending on the number of contacts and the connection speed, downloading all keys could be quite a lengthy process! downloadContactsKeys.importFrom=Import contacts from address book '%S'? keyMan.button.exportSecKey=Export &Secret Keys keyMan.button.exportPubKey=Export &Public Keys Only keyMan.button.import=&Import keyMan.button.refreshAll=&Refresh All Keys keyMan.button.revokeKey=&Revoke Key keyMan.button.skip=&Skip Key keylist.noOtherUids=Has no other identities keylist.hasOtherUids=Also known as keylist.noPhotos=No photograph available keylist.hasPhotos=Photographs keyMan.addphoto.filepicker.title=Select photo to add keyMan.addphoto.warnLargeFile=The file you have chosen is larger than 25 kB.\nIt is not recommended to add very large files because the keys get very large by this. keyMan.addphoto.noJpegFile=The selected file does not appear to be a JPEG file. Please choose a different file. keyMan.addphoto.failed=The photo could not be added. noWksIdentity=The key %S does not have a WKS identity. keyman.addBlacklistKey.msg=Do you really want to stop pEp using the key "%1$S (%2$S)" for encrypting messages? keyman.removeBlacklistKey.msg=Do you want allow pEp to use key "%1$S (%2$S)" for future messages? keyman.addBlacklistKey.button=&Blacklist the key keyman.removeBlacklistKey.button=&Remove key from Blacklist # Strings in enigmailManageUidDlg.xul changePrimUidFailed=Changing the primary User ID failed changePrimUidOK=The primary user ID was changed successfully deleteUidFailed=Deleting the user ID %S failed deleteUidOK=User ID %S was deleted successfully revokeUidFailed=Revoking the user ID %S failed revokeUidOK=User ID %S was revoked successfully. If your key is available on a key server, it is recommended to re-upload it, so that others can see the revocation. revokeUidQuestion=Do you really want to revoke the user ID %S? deleteUidQuestion=Do you really want to delete the user ID %S?\n\nPlease note: if you have submitted your public key to a key server, deleting a user ID will not change anything. In this case you should use 'Revoke user ID'. # Strings in enigmailKeyImportInfo.xul importInfoTitle=SUCCESS! Keys imported importInfoSuccess=\u2705 importInfoBits=Bits importInfoCreated=Created importInfoFpr=Fingerprint importInfoDetails=(Details) importInfoNoKeys=No keys imported. # Strings in enigmailKeyDetailsDlg.xul keyTypePublic=public key keyTypePrimary=primary key keyTypeSubkey=subkey keyTypePair=key pair keyExpiryNever=never keyAlgorithm_1=RSA keyAlgorithm_2=RSA keyAlgorithm_3=RSA keyAlgorithm_16=ELG keyAlgorithm_17=DSA keyAlgorithm_18=ECDH keyAlgorithm_19=ECDSA keyAlgorithm_20=ELG keyAlgorithm_22=EDDSA keyUsageEncrypt=Encrypt keyUsageSign=Sign keyUsageCertify=Certify keyUsageAuthentication=Authentication keyDoesNotExpire=Key does not expire # Strings in enigmailGenCardKey.xul keygen.started=Please wait while the key is being generated .... keygen.completed=Key Generated. The new Key ID is: 0x%S keygen.keyBackup=The key is backed up as %S keygen.passRequired=Please specify a passphrase if you want to create a backup copy of your key outside your SmartCard. # Strings in enigmailSetCardPin.xul cardPin.processFailed=Failed to change PIN # Strings in enigRetrieveProgres.xul keyserverProgress.refreshing=Refreshing keys, please wait ... keyserverProgress.uploading=Uploading keys, please wait ... keyserverProgress.wksCheck=Checking provider for Webkey Service support ... keyserverProgress.noWks=Your provider does not support the Webkey Service keyserverProgress.wksUploadFailed=Could not upload your key to the Webkey Service keyserverProgress.wksUploadCompleted=Your public key was successfully submitted to your provider. You will receive an email to confirm that you initiated the upload. keyserverTitle.refreshing=Refresh Keys keyserverTitle.uploading=Key Upload # Strings in enigmailSetupWizard passphrase.min8keys=Your passphrase should contain at least 8 characters! setupWizard.reallyCancel=Do you really want to close the Enigmail Setup Wizard? setupWizard.invalidGpg=The file you specified is not a GnuPG executable. Please specify a different file. setupWizard.specifyFile=You need to at least specify a public key file in order to proceed. setupWizard.installFailed=It seems that the installation was not successful. Please either retry the installation, or install GnuPG manually and locate it using the Browse button. setupWizard.downloadForbidden=For your own security, we will not download GnuPG. Please visit https://gnupg.org/ in order to download GnuPG. setupWizard.downloadImpossible=We cannot download GnuPG currently. Please try later or visit https://gnupg.org/ in order to download GnuPG. setupWizard.hashSumError=The wizard could not verify the integrity of the downloaded file. The file may be broken or manipulated. Do you want to continue the installation anyway? setupWizard.importSettingsFile=Specify backup file to load from setupWizard.invalidSettingsFile=The specified file is not a correct Enigmail Settings backup file. setupWizard.gpgConfExists=The GnuPG config file already exists. Do you want to overwrite it with the one from your old installation? setupWizard.noGpgHomeDir=It appears that you configured a specificy %S to use for GnuPG. However, this is not a directory - you cannot use it. setupWizard.unmachtedIds=The following identities of your old setup could not be matched:\n%S\nThe settings for these identities were skipped. # Strings in installGnuPG.jsm installGnuPG.downloadFailed=An error occurred while trying to download GnuPG. Please check the console log for further details. installGnuPG.installFailed=An error occurred while installing GnuPG. Please check the console log for further details. # Strings in enigmailAddUidDlg.xul addUidDlg.nameOrEmailError=You have to fill in a name and an email address addUidDlg.nameMinLengthError=The name must at least have 5 characters addUidDlg.invalidEmailError=You must specify a valid email address # Strings in enigmailCardDetails.js Carddetails.NoASCII=OpenPGP Smartcards only support ASCII characters in Firstname/Name. # network error types errorType.SecurityCertificate=The security certificate presented by the web service is not valid. errorType.SecurityProtocol=The security protocol used by the web service is unknown. errorType.Network=A network error has occurred. # filter stuff filter.folderRequired=You must select a target folder. filter.decryptMove.label=Decrypt permanently (Enigmail) filter.decryptCopy.label=Create decrypted Copy (Enigmail) filter.decryptMove.warnExperimental=Warning - the filter action "Decrypt permanently" may lead to destroyed messages.\n\nWe strongly recommend that you first try the "Create decrypted Copy" filter, test the result carefully, and only start using this filter once you are satisified with the result. +filter.encrypt.label=Encrypt to (Enigmail) +filter.keyRequired=You must select a recipient key. +filter.keyNotFound=Could not find an encryption key for "%S". filter.tempPepFilterDesc=Temporary filter to store sent message unencrypted # strings in enigmailConvert.jsm converter.decryptBody.failed=Could not decrypt message with subject\n"%S".\nDo you want to retry with a different passphrase or do you want to skip the message? converter.decryptAtt.failed=Could not decrypt attachment "%1$S"\nof message with subject\n"%2$S".\nDo you want to retry with a different passphrase or do you want to skip the message? saveLogFile.title=Save Log File # strings in gpg.jsm unknownSigningAlg=Unknown signing algorithm (ID: %S) unknownHashAlg=Unknown cryptographic hash (ID: %S) # strings in keyRing.jsm keyring.photo=Photo keyRing.pubKeyRevoked=The key %1$S (key ID %2$S) is revoked. keyRing.pubKeyExpired=The key %1$S (key ID %2$S) has expired. keyRing.pubKeyNotForSigning=The key %1$S (key ID %2$S) cannot be used for signing. keyRing.pubKeyNotForEncryption=The key %1$S (key ID %2$S) cannot be used for encryption. keyRing.keyDisabled=The key %1$S (key ID %2$S) is disabled; it cannot be used. keyRing.keyNotTrusted=The key %1$S (key ID %2$S) is not trusted enough. Please set the trust level of your key to "ultimate" to use it for signing. keyRing.keyInvalid=The key %1$S (key ID %2$S) is not valid. Please consider verifying it correctly. Alternatively use "Convenient encryption settings". keyRing.signSubKeysRevoked=All signing-subkeys of key %1$S (key ID %2$S) are revoked. keyRing.signSubKeysExpired=All signing-subkeys of key %1$S (key ID %2$S) have expired. keyRing.signSubKeysUnusable=All signing-subkeys of key %1$S (key ID %2$S) are revoked, expired or otherwise unusable. keyRing.encSubKeysRevoked=All encryption subkeys of key %1$S (key ID %2$S) are revoked. keyRing.encSubKeysExpired=All encryption subkeys of key %1$S (key ID %2$S) have expired. keyRing.noSecretKey=You do not seem to have the secret key for %1$S (key ID %2$S) on your keyring; you cannot use the key for signing. keyRing.encSubKeysUnusable=All encryption subkeys of key %1$S (key ID %2$S) are revoked, expired or otherwise unusable. #strings in exportSettingsWizard.js cannotWriteToFile=Cannot save to file '%S'. Please select a different file. dataExportError=An error occurred during exporting your data. enigmailSettings=EnigmailSettings defaultBackupFileName=Enigmail-export specifyExportFile=Specify file name for exporting homedirParamNotSUpported=Additional params that configure paths such as --homedir and --keyring are not supported for exporting/restoring your settings. Please use alternative methods such as setting the environment variable GNUPGHOME. #strings in expiry.jsm expiry.keyExpiresSoon=Your key %1$S will expire in less than %2$S days.\n\nWe recommend that you create a new key pair and configure the corresponding accounts to use the new key. expiry.keysExpireSoon=The following of your keys will expire in less than %1$S days:\n%2$S. We recommend that you create new keys and configure your accounts to use the new keys. expiry.keyMissingOwnerTrust=Your secret key %S has missing trust.\n\nWe recommend that you set "You rely on certifications" to ultimate in key properties. expiry.keysMissingOwnerTrust=The following of your secret keys have missing trust.\n%S.\nWe recommend that you set "You rely on certifications" to ultimate in key properties. expiry.OpenKeyManager=Open Enigmail Key Management expiry.OpenKeyProperties=Open Key Properties #strings in pEpDecrypt.jsm pEpDecrypt.cannotDecrypt=This is an encrypted message. Unfortunately you don't have the private key to decrypt the message. #strings in gpgAgent.jsm gpghomedir.notexists=The directory '%S' containing your OpenPGP keys does not exist and cannot be created. gpghomedir.notwritable=The directory '%S' containing your OpenPGP keys is not writable. gpghomedir.notdirectory=The directory '%S' containing your OpenPGP keys is a file instead of a directory. gpghomedir.notusable=Please fix the directory permissions or change the location of your GnuPG "home" directory. GnuPG cannot work corretly otherwise. #strings in pepTrustWords.js pepTrustWords.cannotVerifyOwnId=Cannot verify p≡p Trustwords for own account. pepTrustWords.cannotFindKey=Cannot find key for %S. pepTrustWords.cannotStoreChange=Could not change trust for %S. pepTrustWords.generalFailure=Cannot obtain trustwords for %S. pepTrustWords.partnerFingerprint=Fingerprint for %S: #strings in mimeWkdHandler.jsm wkdMessage.body.req=Your email provider processed your request to upload your public key to the OpenPGP Web Key Directory.\n\nPlease click the confirmation button in the Enigmail header to complete the publishing of your public key. wkdMessage.body.process=This is an email related to the automatic processing to upload your public key to the OpenPGP Web Key Directory.\n\nYou do not need to take any manual action at this point. #strings in pepHandshake.js pepPrivacyStatus.RatingBrokenSuggestion=Either you or the sender should resend the message. pepPrivacyStatus.RatingHaveNoKeySuggestionOutgoing=If you composed this message, your key is not available. pepPrivacyStatus.RatingMistrustSuggestion=Re-establish the connection with your communication partner and try to complete another handshake. pepPrivacyStatus.RatingReliableSuggestion=Complete a handshake with your communication partner by exchanging trustwords in person or over the phone. A handshake is needed only once per partner and will ensure secure and trusted communication. pepPrivacyStatus.RatingTrustedSuggestion=No action needed! pepPrivacyStatus.RatingUndefinedSuggestionIncoming=Be aware this message may not be secure. pepPrivacyStatus.RatingUndefinedSuggestionOutgoing=Please add the necessary information. pepPrivacyStatus.RatingUnderAttackSuggestion=Separately verify the content of this message with your communication partner. pepPrivacyStatus.RatingUnencryptedForSomeSuggestion=Make sure the privacy status for each communication partner listed is at least secure. pepPrivacyStatus.RatingUnencryptedSuggestion=Please ask your communication partner to use an encryption solution or install p≡p. pepPrivacyStatus.RatingUnreliableSuggestion=This message has no reliable encryption or no signature. Ask your communication partner to upgrade their encryption solution or install p≡p. pepPrivacyStatus.RatingBrokenExplanation=This message has broken encryption or formatting. pepPrivacyStatus.RatingHaveNoKeyExplanation=This message cannot be decrypted because the key is not available. pepPrivacyStatus.RatingMistrustExplanation=This message has a communication partner that has previously been marked as mistrusted. pepPrivacyStatus.RatingReliableExplanation=This message is secure but you still need to verify the identity of your communication partner. pepPrivacyStatus.RatingTrustedExplanation=This message is secure and trusted. pepPrivacyStatus.RatingUndefinedExplanation=This message does not contain enough information to determine if it is secure. pepPrivacyStatus.RatingUnderAttackExplanation=This message is not secure and has been tampered with. pepPrivacyStatus.RatingUnencryptedExplanation=This message is unsecure. pepPrivacyStatus.RatingUnencryptedForSomeExplanation=This message is unsecure for some communication partners. pepPrivacyStatus.RatingUnreliableExplanation=This message has unreliable protection. pepPrivacyStatus.RatingBrokenText=Broken pepPrivacyStatus.RatingHaveNoKeyText=Cannot Decrypt pepPrivacyStatus.RatingMistrustText=Mistrusted pepPrivacyStatus.RatingReliableText=Secure pepPrivacyStatus.RatingTrustedText=Secure & Trusted pepPrivacyStatus.RatingUndefinedText=Unknown pepPrivacyStatus.RatingUnderAttackText=Under Attack pepPrivacyStatus.RatingUnencryptedForSomeText=Unsecure for Some pepPrivacyStatus.RatingUnencryptedText=Unsecure pepPrivacyStatus.RatingUnreliableText=Unreliable Security handshakeDlg.button.initHandshake=Handshake... handshakeDlg.button.stopTrust=Stop Trusting handshakeDlg.button.reTrust=Stop Mistrusting handshakeDlg.label.outgoingMessage=Outgoing message handshakeDlg.label.incomingMessage=Incoming message handshakeDlg.error.noPeers=Cannot handshake without any correspondents. handshakeDlg.error.noProtection=Please enable protection in order to use the Handshake function. enigmail.acSetupPasswd.descEnterPasswd=Please enter the setup code that is displayed on the other device enigmail.acSetupPasswd.descCopyPasswd=Please enter the setup code below on your other device to proceed with the setup #strings in autocrypt.jsm autocrypt.setupMsg.subject=Autocrypt Setup Message autocrypt.setupMsg.msgBody=To set up your new device for Autocrypt, please follow the instuctions that should be presented by your new device. autocrypt.setupMsg.fileTxt=This is the Autocrypt setup file used to transfer settings and keys between clients. You can decrypt it using the password presented on your old device, and then import the contained key into your keyring. diff --git a/ui/skin/common/enigmail-common.css b/ui/skin/common/enigmail-common.css index d41c7eaf..57413cab 100644 --- a/ui/skin/common/enigmail-common.css +++ b/ui/skin/common/enigmail-common.css @@ -1,326 +1,330 @@ /* * 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/. */ /** * Common Enigmail styles for all platforms */ .action-box { width: 100px } #expandedEnigmailBox { background-color:#d8e0e8; color: black; } .enigmailHeaderName { color: #888a85; /* lower contrast, TB3 */ text-align: right; background-color: transparent; -moz-margin-end: 2px; } .enigmailHeaderNameBox { width: 7.7em; background-color:#d8e0e8; color: black; } .enigmailHeaderValue { min-width: 50px; white-space: normal; color: black; line-height: 1.4em; -moz-appearance: none !important; -moz-appearance: none; padding: 0px !important; margin: 0px !important; -moz-margin-start: 3px !important; border: none !important; background-color: transparent; } .enigmailHeaderSpacer { width: 2px; } .enigmailHeaderBoxLabelNoSignature { background-color:#d8e0e8; color: black; } .enigmailHeaderBoxLabelSignatureOk { background-color:#ccffcc; color: black; } .enigmailHeaderBoxLabelSignatureVerified { background-color:#ccffff; color: black; } .enigmailHeaderBoxLabelSignatureNotOk { background-color:#FFD8FE; color: black; } .enigmailHeaderBoxLabelSignatureUnknown { background-color:#FFFFC9; color: black; } .enigmailHeaderBoxLabelBuggyMailFormat { background-color:#FFD8FE; color: black; } .enigmailLink { text-decoration: underline; color: blue; cursor: pointer; } .enigmailStrong { font-weight: bold; color: red; } .enigmailUidInactive { color: gray; font-style: italic; } .enigMsgHdrView-flat-button { min-width: 1px !important; -moz-appearance: none; color: black; background-color: #DDDDDD; border: 2px solid transparent; margin-top: 2px; margin-bottom: 2px; padding: 0 2px; border-radius: 2px; -moz-border-top-colors: transparent threedhighlight threedlightshadow; -moz-border-right-colors: transparent threeddarkshadow threedshadow; -moz-border-bottom-colors: transparent threeddarkshadow threedshadow; -moz-border-left-colors: transparent threedhighlight threedlightshadow; min-height: 1ex; } .enigMsgHdrView-flat-button:hover { background-color: #CCCCCC; } .enigmailToolbar { -moz-appearance: none; } .enigmailTitle { font-size: larger; font-weight: bold; } #messagepanebox[enigSigned="ok"] #messagepane { color: red; } .enigmailMessagePane { margin: 6px; -moz-user-focus: normal; -moz-user-select: text; cursor: text !important; white-space: pre-wrap; unicode-bidi: -moz-plaintext; } /*************************************************** * Various other styles ***************************************************/ .enigmailDialogTitle { font-size: 120%; font-weight: bold; padding-bottom: 6px; } .enigmailDialogBody { -moz-user-focus: normal; -moz-user-select: text; cursor: text !important; white-space: pre-wrap; unicode-bidi: -moz-plaintext; } .enigmailPrefsTitle { font-weight: bold; height: 25px; } .enigmailKeyImportHeader { font-weight: bold; color: #888; } .enigmailKeyImportUserId { font-weight: bold; } .enigmailKeyImportCaption { list-style-image: url("chrome://enigmail/skin/importSuccess.png"); max-height: 2em; max-width: 2em; } .enigmailKeyImportDetails { color: blue; } .enigmailKeyImportDetails:hover { text-decoration: underline; } treechildren::-moz-tree-cell-text(enigmailSubkeyTitle) { font-weight: bold; } treechildren::-moz-tree-cell-text(enigmailOwnKey) { font-weight: bold; } treechildren::-moz-tree-cell-text(enigKeyInactive) { color: gray; font-style: italic; } treechildren::-moz-tree-column(enigDontEncrypt) { background-color: rgb(90%, 90%, 90%); } treechildren::-moz-tree-cell-text(fixedWidthFont) { font-family: "Courier New", Courier, monospace; } .enigmailExpandViewButton { width: 9px; /* The image's width is 9 pixels */ list-style-image: url("chrome://enigmail/skin/twisty-clsd.png"); } .enigmailCollapseViewButton { width: 9px; /* The image's width is 9 pixels */ list-style-image: url("chrome://enigmail/skin/twisty-open.png"); } .enigmailWarningIcon { list-style-image: url("chrome://enigmail/skin/warning-16.png"); } treechildren::-moz-tree-image(enigSignedEncrypted) { list-style-image: url("chrome://enigmail/skin/col-encrypted-signed.png"); } treechildren::-moz-tree-image(enigSigned) { list-style-image: url("chrome://enigmail/skin/enigSignOk.png"); } treechildren::-moz-tree-image(enigEncrypted) { list-style-image: url("chrome://enigmail/skin/enigEncOk.png"); } /* the following styles are available for the key trust columnm in the key manager: enigmail_keyValid_unknown enigmail_keyValid_invalid enigmail_keyValid_disabled enigmail_keyValid_revoked enigmail_keyValid_expired enigmail_keyTrust_untrusted enigmail_keyTrust_marginal enigmail_keyTrust_full enigmail_keyTrust_ultimate enigmail_keyTrust_unknown They can be applied using: treechildren::-moz-tree-cell(STYLE) {} treechildren::-moz-tree-cell-text(STYLE) {} */ /****************************** * Rules for filter actions ******************************/ .ruleactiontarget[type="enigmail@enigmail.net#filterActionMoveDecrypt"] { -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-folder"); } .ruleactiontarget[type="enigmail@enigmail.net#filterActionCopyDecrypt"] { -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-folder"); } +.ruleactiontarget[type="enigmail@enigmail.net#filterActionEncrypt"] { + -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-forwardto"); +} + .enigmailPassphraseQuality { margin: 2px 4px; min-width: 128px; height: 12px; background-color: #C21540; } .enigmailPassphraseQuality[value="medium"] { background-color: #5885C4; } .enigmailPassphraseQuality[value="high"] { background-color: #64C4A1; } .enigmailPassphraseQuality[value="excellent"] { background-color: #1A9C2A; } /* a spinning wheel circle */ .enigmailWheel { list-style-image: url("chrome://enigmail/skin/spinning-wheel.png"); max-width: 100%; max-height: 100%; } .enigmailSpinning { animation: enigmailDoRotation 1.4s infinite linear; transform: translateZ(0); } @keyframes enigmailDoRotation { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /*************************************************** * Styles for Setup Wizard and Key Generation ***************************************************/ #passphraseBox #passphraseError { margin-top: 3px; margin-bottom: 3px; color: red; } #passphraseBox #passphraseErrorRepeat { margin-top: 3px; margin-bottom: 3px; color: red; } diff --git a/util/genxpi b/util/genxpi index 5afabac1..f52edec6 100755 --- a/util/genxpi +++ b/util/genxpi @@ -1,166 +1,167 @@ #!/bin/sh # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # This script generates the Enigmail XPI # echo "genxpi: Generating $1 in $3" if [ $# -lt 5 ]; then echo "Wrong number of parameters" exit 1 fi xpiFile=$1 xpiVersion="$2" distDir="$3" srcDir=$4 xpiModule=$5 enableLang=$6 cd ${srcDir} cwd=`pwd` cd "$distDir" targetDir=`pwd` cd "$cwd" cp ${srcDir}/package/install.rdf ${targetDir}/install.rdf # Prepare chrome.manifest cat ${srcDir}/package/chrome.manifest \ > ${targetDir}/chrome.manifest # Prepare languages other than en-US if [ "$enableLang" = "yes" ]; then if [ -s ${srcDir}/lang/current-languages.txt ]; then echo '' >> ${targetDir}/chrome.manifest echo '# Additional languages' >> ${targetDir}/chrome.manifest for lang in `cat ${srcDir}/lang/current-languages.txt`; do echo 'locale enigmail '$lang' jar:chrome/enigmail.jar!/locale/'$lang'/' >> ${targetDir}/chrome.manifest done fi fi # cd ${srcDir}/package cd "$targetDir" mkdir -p "${targetDir}/wrappers" cp ${cwd}/util/gpg-agent-wrapper wrappers echo "Creating ${xpiFile} file" zip --must-match\ ../${xpiFile} \ components/${xpiModule}.xpt \ components/${xpiModule}.js \ components/prefs-service.js \ components/msgCompFields.js \ components/pgpmimeHandler.js \ components/mimeEncrypt.js \ defaults/preferences/enigmail.js \ modules/addrbook.jsm \ modules/app.jsm \ modules/armor.jsm \ modules/attachment.jsm \ modules/autocrypt.jsm \ modules/card.jsm \ modules/clipboard.jsm \ modules/commandLine.jsm \ modules/configure.jsm \ modules/constants.jsm \ modules/data.jsm \ modules/decryption.jsm \ modules/decryptPermanently.jsm \ + modules/encryptPermanently.jsm \ modules/dialog.jsm \ modules/encryption.jsm \ modules/core.jsm \ modules/configBackup.jsm \ modules/errorHandling.jsm \ modules/funcs.jsm \ modules/gpgAgent.jsm \ modules/protocolHandler.jsm \ modules/events.jsm \ modules/execution.jsm \ modules/files.jsm \ modules/filters.jsm \ modules/fixExchangeMsg.jsm \ modules/glodaMime.jsm \ modules/glodaUtils.jsm \ modules/gpg.jsm \ modules/hash.jsm \ modules/httpProxy.jsm \ modules/installGnuPG.jsm \ modules/installPep.jsm \ modules/key.jsm \ modules/keyEditor.jsm \ modules/keyRing.jsm \ modules/keyUsability.jsm \ modules/keyRefreshService.jsm \ modules/keyserver.jsm \ modules/keyserverUris.jsm \ modules/lazy.jsm \ modules/locale.jsm \ modules/log.jsm \ modules/mime.jsm \ modules/mimeDecrypt.jsm \ modules/mimeVerify.jsm \ modules/os.jsm \ modules/openpgp.jsm \ modules/passwordCheck.jsm \ modules/passwords.jsm \ modules/pEp.jsm \ modules/pEpAdapter.jsm \ modules/pEpDecrypt.jsm \ modules/pEpFilter.jsm \ modules/pEpListener.jsm \ modules/pEpKeySync.jsm \ modules/pipeConsole.jsm \ modules/prefs.jsm \ modules/rng.jsm \ modules/rules.jsm \ modules/send.jsm \ modules/socks5Proxy.jsm \ modules/stdlib/compose.jsm \ modules/stdlib/misc.jsm \ modules/stdlib/msgHdrUtils.jsm \ modules/stdlib/openpgp-lib.js \ modules/stdlib/openpgp.worker.min.js \ modules/stdlib.jsm \ modules/streams.jsm \ modules/subprocess.jsm \ modules/enigmailprocess_shared_unix.js \ modules/enigmailprocess_worker_common.js \ modules/enigmailprocess_common.jsm \ modules/enigmailprocess_shared_win.js \ modules/enigmailprocess_worker_unix.js \ modules/enigmailprocess_main.jsm \ modules/enigmailprocess_unix.jsm \ modules/enigmailprocess_worker_win.js \ modules/enigmailprocess_shared.js \ modules/enigmailprocess_win.jsm \ modules/system.jsm \ modules/time.jsm \ modules/timer.jsm \ modules/tor.jsm \ modules/trust.jsm \ modules/uris.jsm \ modules/verify.jsm \ modules/versioning.jsm \ modules/webKey.jsm \ modules/wkdLookup.jsm \ modules/windows.jsm \ modules/wksMimeHandler.jsm \ modules/zbase32.jsm \ wrappers/gpg-agent-wrapper \ chrome/${xpiModule}.jar \ chrome.manifest \ install.rdf