Bug 1616558, remove code for gnupg subprocess execution and gpg-agent interaction. r=PatrickBrunschwig
Differential Revision: https://phabricator.services.mozilla.com/D63325 --HG-- extra : rebase_source : 9edebe121f8e78abf35e71efff86b89d9906b950
This commit is contained in:
Родитель
433e0d8fcd
Коммит
75eb1b1a91
|
@ -53,15 +53,9 @@ var BondOpenPGP = {
|
|||
if (!MailConstants.MOZ_OPENPGP) {
|
||||
return;
|
||||
}
|
||||
const engine = Services.prefs.getIntPref("temp.openpgp.engine");
|
||||
if (!engine) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.initDone) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.initDone = true;
|
||||
console.log("loading OpenPGP");
|
||||
try {
|
||||
|
|
|
@ -33,7 +33,6 @@ const EnigmailStreams = ChromeUtils.import("chrome://openpgp/content/modules/str
|
|||
const EnigmailArmor = ChromeUtils.import("chrome://openpgp/content/modules/armor.jsm").EnigmailArmor;
|
||||
const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
|
||||
const EnigmailRules = ChromeUtils.import("chrome://openpgp/content/modules/rules.jsm").EnigmailRules;
|
||||
const EnigmailKeyEditor = ChromeUtils.import("chrome://openpgp/content/modules/keyEditor.jsm").EnigmailKeyEditor;
|
||||
const EnigmailStdlib = ChromeUtils.import("chrome://openpgp/content/modules/stdlib.jsm").EnigmailStdlib;
|
||||
const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
|
||||
const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["EnigmailCard"];
|
||||
|
||||
const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
|
||||
const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
|
||||
const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
|
||||
|
||||
var EnigmailCard = {
|
||||
getCardStatus: function(exitCodeObj, errorMsgObj) {
|
||||
EnigmailLog.DEBUG("card.jsm: EnigmailCard.getCardStatus\n");
|
||||
const GPG_ADDITIONAL_OPTIONS=["--no-verbose", "--status-fd", "2", "--fixed-list-mode", "--with-colons", "--card-status"];
|
||||
const args = EnigmailGpg.getStandardArgs(false).concat(GPG_ADDITIONAL_OPTIONS);
|
||||
const statusMsgObj = {};
|
||||
const statusFlagsObj = {};
|
||||
|
||||
const outputTxt = EnigmailExecution.execCmd(EnigmailGpg.agentPath, args, "", exitCodeObj, statusFlagsObj, statusMsgObj, errorMsgObj);
|
||||
|
||||
if ((exitCodeObj.value === 0) && !outputTxt) {
|
||||
exitCodeObj.value = -1;
|
||||
return "";
|
||||
}
|
||||
|
||||
return outputTxt;
|
||||
}
|
||||
};
|
|
@ -13,13 +13,11 @@ const {
|
|||
} = Components;
|
||||
Cm.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
|
||||
const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
|
||||
const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
|
||||
const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
|
||||
|
||||
// load all modules lazily to avoid possible cross-reference errors
|
||||
const getEnigmailConsole = EnigmailLazy.loader("enigmail/pipeConsole.jsm", "EnigmailConsole");
|
||||
//const getEnigmailGpgAgent = EnigmailLazy.loader("enigmail/gpgAgent.jsm", "EnigmailGpgAgent");
|
||||
const getEnigmailMimeEncrypt = EnigmailLazy.loader("enigmail/mimeEncrypt.jsm", "EnigmailMimeEncrypt");
|
||||
const getEnigmailProtocolHandler = EnigmailLazy.loader("enigmail/protocolHandler.jsm", "EnigmailProtocolHandler");
|
||||
const getEnigmailFiltersWrapper = EnigmailLazy.loader("enigmail/filtersWrapper.jsm", "EnigmailFiltersWrapper");
|
||||
|
@ -122,7 +120,6 @@ var EnigmailCore = {
|
|||
getEnigmailFiltersWrapper().onShutdown();
|
||||
getEnigmailVerify().unregisterContentTypeHandler();
|
||||
|
||||
//getEnigmailGpgAgent().finalize();
|
||||
getEnigmailLocale().shutdown();
|
||||
getEnigmailLog().onShutdown();
|
||||
|
||||
|
@ -223,29 +220,6 @@ function initializeLogging(env) {
|
|||
}
|
||||
}
|
||||
|
||||
function initializeSubprocessLogging(env) {
|
||||
const nspr_log_modules = env.get("NSPR_LOG_MODULES");
|
||||
const matches = nspr_log_modules.match(/subprocess:(\d+)/);
|
||||
|
||||
subprocess.registerLogHandler(function(txt) {
|
||||
getEnigmailLog().ERROR("subprocess.jsm: " + txt);
|
||||
});
|
||||
|
||||
if (matches && matches.length > 1 && matches[1] > 2) {
|
||||
subprocess.registerDebugHandler(function(txt) {
|
||||
getEnigmailLog().DEBUG("subprocess.jsm: " + txt);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initializeAgentInfo() {
|
||||
return;
|
||||
|
||||
if (!getEnigmailOS().isDosLike && !getEnigmailGpgAgent().isDummy()) {
|
||||
EnigmailCore.addToEnvList("GPG_AGENT_INFO=" + getEnigmailGpgAgent().gpgAgentInfo.envStr);
|
||||
}
|
||||
}
|
||||
|
||||
function failureOn(ex, status) {
|
||||
status.initializationError = getEnigmailLocale().getString("enigmailNotAvailable");
|
||||
getEnigmailLog().ERROR("core.jsm: Enigmail.initialize: Error - " + status.initializationError + "\n");
|
||||
|
@ -340,7 +314,6 @@ Enigmail.prototype = {
|
|||
|
||||
this.environment = getEnvironment(this);
|
||||
|
||||
initializeSubprocessLogging(this.environment);
|
||||
initializeEnvironment(this.environment);
|
||||
|
||||
try {
|
||||
|
@ -349,11 +322,6 @@ Enigmail.prototype = {
|
|||
failureOn(ex, this);
|
||||
}
|
||||
|
||||
//getEnigmailGpgAgent().setAgentPath(domWindow, this, gPreferredGpgPath);
|
||||
//getEnigmailGpgAgent().detectGpgAgent(domWindow, this);
|
||||
|
||||
//initializeAgentInfo();
|
||||
|
||||
//getEnigmailKeyRefreshService().start(getEnigmailKeyServer());
|
||||
|
||||
this.initialized = true;
|
||||
|
@ -368,7 +336,6 @@ Enigmail.prototype = {
|
|||
|
||||
getEnigmailConsole().write("Reinitializing Enigmail service ...\n");
|
||||
initializeEnvironment(this.environment);
|
||||
//getEnigmailGpgAgent().setAgentPath(null, this, gPreferredGpgPath);
|
||||
this.initialized = true;
|
||||
},
|
||||
|
||||
|
@ -444,13 +411,6 @@ Enigmail.prototype = {
|
|||
|
||||
getEnigmailLog().DEBUG("core.jsm: getService: last used version: " + configuredVersion + "\n");
|
||||
|
||||
/*
|
||||
if (firstInitialization && this.initialized &&
|
||||
getEnigmailGpgAgent().agentType === "pgp") {
|
||||
getEnigmailDialog().alert(win, getEnigmailLocale().getString("pgpNotSupported"));
|
||||
}
|
||||
*/
|
||||
|
||||
if (this.initialized && (getEnigmailApp().getVersion() != configuredVersion)) {
|
||||
getEnigmailConfigure().configureEnigmail(win, startingPreferences);
|
||||
}
|
||||
|
|
|
@ -4,30 +4,30 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["EnigmailCryptoAPI"];
|
||||
|
||||
var gCurrentApi = null;
|
||||
var gGnuPGApi = null;
|
||||
var Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
|
||||
|
||||
function EnigmailCryptoAPI() {
|
||||
if (!gCurrentApi) {
|
||||
const gOpenPGPEngine = Services.prefs.getIntPref("temp.openpgp.engine");
|
||||
|
||||
if (gOpenPGPEngine == 1) {
|
||||
const {
|
||||
getRNPAPI
|
||||
} = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI/RNPCryptoAPI.jsm");
|
||||
gCurrentApi = getRNPAPI();
|
||||
} else if (gOpenPGPEngine == 2) {
|
||||
const {
|
||||
getGnuPGAPI
|
||||
} = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI/GnuPGCryptoAPI.jsm");
|
||||
gCurrentApi = getGnuPGAPI();
|
||||
}
|
||||
const {
|
||||
getRNPAPI
|
||||
} = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI/RNPCryptoAPI.jsm");
|
||||
gCurrentApi = getRNPAPI();
|
||||
}
|
||||
|
||||
return gCurrentApi;
|
||||
}
|
||||
|
||||
function EnigmailGnuPGAPI() {
|
||||
if (!gGnuPGApi) {
|
||||
const {
|
||||
getGnuPGAPI
|
||||
} = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI/GnuPGCryptoAPI.jsm");
|
||||
gGnuPGApi = getGnuPGAPI();
|
||||
}
|
||||
return gGnuPGApi;
|
||||
}
|
||||
|
|
|
@ -19,19 +19,15 @@ Services.scriptloader.loadSubScript("chrome://openpgp/content/modules/cryptoAPI/
|
|||
const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
|
||||
const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
|
||||
const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
|
||||
const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
|
||||
const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
|
||||
const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
|
||||
const EnigmailTime = ChromeUtils.import("chrome://openpgp/content/modules/time.jsm").EnigmailTime;
|
||||
const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
|
||||
const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
|
||||
const EnigmailPassword = ChromeUtils.import("chrome://openpgp/content/modules/passwords.jsm").EnigmailPassword;
|
||||
const EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules/errorHandling.jsm").EnigmailErrorHandling;
|
||||
const GnuPGDecryption = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI/gnupg-decryption.jsm").GnuPGDecryption;
|
||||
|
||||
const {
|
||||
obtainKeyList,
|
||||
createKeyObj,
|
||||
getPhotoFileFromGnuPG,
|
||||
extractSignatures,
|
||||
getGpgKeyData
|
||||
|
@ -63,35 +59,6 @@ class GnuPGCryptoAPI extends CryptoAPI {
|
|||
return keyList.keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get groups defined in gpg.conf in the same structure as KeyObject
|
||||
*
|
||||
* @return {Array of KeyObject} with type = "grp"
|
||||
*/
|
||||
getGroups() {
|
||||
let groups = EnigmailGpg.getGpgGroups();
|
||||
|
||||
let r = [];
|
||||
for (var i = 0; i < groups.length; i++) {
|
||||
|
||||
let keyObj = createKeyObj(["grp"]);
|
||||
keyObj.keyTrust = "g";
|
||||
keyObj.userId = EnigmailData.convertGpgToUnicode(groups[i].alias).replace(/\\e3A/g, ":");
|
||||
keyObj.keyId = keyObj.userId;
|
||||
var grpMembers = EnigmailData.convertGpgToUnicode(groups[i].keylist).replace(/\\e3A/g, ":").split(/[,;]/);
|
||||
for (var grpIdx = 0; grpIdx < grpMembers.length; grpIdx++) {
|
||||
keyObj.userIds.push({
|
||||
userId: grpMembers[grpIdx],
|
||||
keyTrust: "q"
|
||||
});
|
||||
}
|
||||
r.push(keyObj);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtain signatures for a given set of key IDs.
|
||||
*
|
||||
|
@ -102,28 +69,7 @@ class GnuPGCryptoAPI extends CryptoAPI {
|
|||
*/
|
||||
async getKeySignatures(keyId, ignoreUnknownUid = false) {
|
||||
EnigmailLog.DEBUG(`gnupg.js: getKeySignatures: ${keyId}\n`);
|
||||
|
||||
const args = EnigmailGpg.getStandardArgs(true).concat(["--with-fingerprint", "--fixed-list-mode", "--with-colons", "--list-sig"]).concat(keyId.split(" "));
|
||||
|
||||
let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, "");
|
||||
|
||||
if (!(res.statusFlags & EnigmailConstants.BAD_SIGNATURE)) {
|
||||
// ignore exit code as recommended by GnuPG authors
|
||||
res.exitCode = 0;
|
||||
}
|
||||
|
||||
if (res.exitCode !== 0) {
|
||||
if (res.errorMsg) {
|
||||
res.errorMsg += "\n" + EnigmailFiles.formatCmdLine(EnigmailGpg.agentPath, args);
|
||||
res.errorMsg += "\n" + res.errorMsg;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
if (res.stdoutData.length > 0) {
|
||||
return extractSignatures(res.stdoutData, ignoreUnknownUid);
|
||||
}
|
||||
return null;
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
|
||||
|
@ -143,51 +89,7 @@ class GnuPGCryptoAPI extends CryptoAPI {
|
|||
*/
|
||||
async getMinimalPubKey(fpr, email, subkeyDates) {
|
||||
EnigmailLog.DEBUG(`gnupg.js: getMinimalPubKey: ${fpr}\n`);
|
||||
|
||||
let retObj = {
|
||||
exitCode: 0,
|
||||
errorMsg: "",
|
||||
keyData: ""
|
||||
};
|
||||
let minimalKeyBlock = null;
|
||||
|
||||
let args = EnigmailGpg.getStandardArgs(true);
|
||||
|
||||
if (EnigmailGpg.getGpgFeature("export-specific-uid")) {
|
||||
// Use GnuPG filters if possible
|
||||
let dropSubkeyFilter = "usage!~e && usage!~s";
|
||||
|
||||
if (subkeyDates && subkeyDates.length > 0) {
|
||||
dropSubkeyFilter = subkeyDates.map(x => `key_created!=${x}`).join(" && ");
|
||||
}
|
||||
args = args.concat(["--export-options", "export-minimal,no-export-attributes",
|
||||
"--export-filter", "keep-uid=" + (email ? "mbox=" + email : "primary=1"),
|
||||
"--export-filter", "drop-subkey=" + dropSubkeyFilter,
|
||||
"--export", fpr
|
||||
]);
|
||||
} else {
|
||||
args = args.concat(["--export-options", "export-minimal,no-export-attributes", "-a", "--export", fpr]);
|
||||
}
|
||||
|
||||
const statusObj = {};
|
||||
const exitCodeObj = {};
|
||||
let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args);
|
||||
let keyBlock = res.stdoutData;
|
||||
|
||||
// GnuPG 2.1.10+
|
||||
if (!EnigmailGpg.getGpgFeature("export-result")) {
|
||||
retObj.exitCode = 2;
|
||||
retObj.errorMsg = EnigmailLocale.getString("failKeyExtract");
|
||||
}
|
||||
|
||||
let r = new RegExp("^\\[GNUPG:\\] EXPORTED " + fpr, "m");
|
||||
if (res.stderrData.search(r) < 0) {
|
||||
retObj.exitCode = 2;
|
||||
retObj.errorMsg = EnigmailLocale.getString("failKeyExtract");
|
||||
}
|
||||
|
||||
retObj.keyData = btoa(keyBlock);
|
||||
return retObj;
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -254,20 +156,7 @@ class GnuPGCryptoAPI extends CryptoAPI {
|
|||
|
||||
async getFileName(byteData) {
|
||||
EnigmailLog.DEBUG(`gnupg.js: getFileName()\n`);
|
||||
const args = EnigmailGpg.getStandardArgs(true).concat(EnigmailPassword.command()).concat(["--decrypt"]);
|
||||
|
||||
let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, byteData + "\n");
|
||||
|
||||
const matches = res.stderrData.match(/^(\[GNUPG:\] PLAINTEXT [0-9]+ [0-9]+ )(.*)$/m);
|
||||
if (matches && (matches.length > 2)) {
|
||||
var filename = matches[2];
|
||||
if (filename.indexOf(" ") > 0) {
|
||||
filename = filename.replace(/ .*$/, "");
|
||||
}
|
||||
return EnigmailData.convertToUnicode(unescape(filename), "utf-8");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -283,20 +172,7 @@ class GnuPGCryptoAPI extends CryptoAPI {
|
|||
|
||||
async verifyAttachment(filePath, sigPath) {
|
||||
EnigmailLog.DEBUG(`gnupg.js: verifyAttachment()\n`);
|
||||
const args = EnigmailGpg.getStandardArgs(true).concat(["--verify", sigPath, filePath]);
|
||||
let result = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args);
|
||||
const decrypted = {};
|
||||
GnuPGDecryption.decryptMessageEnd(result.stderrData, result.exitCode, 1, true, true, EnigmailConstants.UI_INTERACTIVE, decrypted);
|
||||
if (result.exitCode === 0) {
|
||||
const detailArr = decrypted.sigDetails.split(/ /);
|
||||
const dateTime = EnigmailTime.getDateTime(detailArr[2], true, true);
|
||||
const msg1 = decrypted.errorMsg.split(/\n/)[0];
|
||||
const msg2 = EnigmailLocale.getString("keyAndSigDate", ["0x" + decrypted.keyId, dateTime]);
|
||||
const message = msg1 + "\n" + msg2;
|
||||
return (message);
|
||||
} else {
|
||||
throw (decrypted.errorMsg);
|
||||
}
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
|
||||
|
@ -313,14 +189,7 @@ class GnuPGCryptoAPI extends CryptoAPI {
|
|||
|
||||
async decryptAttachment(encrypted) {
|
||||
EnigmailLog.DEBUG(`gnupg.js: decryptAttachment()\n`);
|
||||
|
||||
let args = EnigmailGpg.getStandardArgs(true);
|
||||
args.push("--yes");
|
||||
args = args.concat(EnigmailPassword.command());
|
||||
args.push("-d");
|
||||
|
||||
let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, encrypted);
|
||||
return res;
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
|
||||
|
@ -338,26 +207,7 @@ class GnuPGCryptoAPI extends CryptoAPI {
|
|||
|
||||
async decrypt(encrypted, options) {
|
||||
EnigmailLog.DEBUG(`gnupg.js: decrypt()\n`);
|
||||
|
||||
options.logFile = EnigmailErrorHandling.getTempLogFile();
|
||||
const args = GnuPGDecryption.getDecryptionArgs(options);
|
||||
let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, encrypted);
|
||||
EnigmailErrorHandling.appendLogFileToDebug(options.logFile);
|
||||
|
||||
if (res.statusFlags & EnigmailConstants.MISSING_PASSPHRASE) {
|
||||
EnigmailLog.ERROR("decryption.jsm: decryptMessageStart: Error - no passphrase supplied\n");
|
||||
throw {
|
||||
errorMsg: EnigmailLocale.getString("noPassphrase")
|
||||
};
|
||||
}
|
||||
|
||||
const result = {
|
||||
exitCode: res.exitCode,
|
||||
decryptedData: res.stdoutData
|
||||
};
|
||||
GnuPGDecryption.decryptMessageEnd(res.stderrData, res.exitCode, res.stdoutData.length, options.verifyOnly, options.noOutput, options.uiFlags, result);
|
||||
|
||||
return result;
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -40,21 +40,6 @@ const EnigmailLocale = ChromeUtils.import(
|
|||
"chrome://openpgp/content/modules/locale.jsm"
|
||||
).EnigmailLocale;
|
||||
|
||||
/*
|
||||
const {
|
||||
obtainKeyList,
|
||||
createKeyObj,
|
||||
getPhotoFileFromGnuPG,
|
||||
extractSignatures,
|
||||
getGpgKeyData
|
||||
} = ChromeUtils.import("chrome://openpgp/content/cryptoAPI/gnupg-keylist.jsm");
|
||||
|
||||
const {
|
||||
GnuPG_importKeyFromFile,
|
||||
GnuPG_extractSecretKey
|
||||
} = ChromeUtils.import("chrome://openpgp/content/cryptoAPI/gnupg-key.jsm");
|
||||
*/
|
||||
|
||||
/**
|
||||
* RNP implementation of CryptoAPI
|
||||
*/
|
||||
|
@ -75,13 +60,6 @@ class RNPCryptoAPI extends CryptoAPI {
|
|||
return RNP.getKeys(onlyKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get groups defined in gpg.conf in the same structure as KeyObject
|
||||
*
|
||||
* @return {Array of KeyObject} with type = "grp"
|
||||
*/
|
||||
getGroups() {}
|
||||
|
||||
/**
|
||||
* Obtain signatures for a given set of key IDs.
|
||||
*
|
||||
|
@ -90,7 +68,9 @@ class RNPCryptoAPI extends CryptoAPI {
|
|||
*
|
||||
* @return {Promise<Array of Object>} - see extractSignatures()
|
||||
*/
|
||||
async getKeySignatures(keyId, ignoreUnknownUid = false) {}
|
||||
async getKeySignatures(keyId, ignoreUnknownUid = false) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the minimum key for the public key object:
|
||||
|
@ -106,7 +86,9 @@ class RNPCryptoAPI extends CryptoAPI {
|
|||
* - errorMsg (if exitCode != 0)
|
||||
* - keyData: BASE64-encded string of key data
|
||||
*/
|
||||
async getMinimalPubKey(fpr, email, subkeyDates) {}
|
||||
async getMinimalPubKey(fpr, email, subkeyDates) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a photo ID from a key, store it as file and return the file object.
|
||||
|
@ -116,7 +98,9 @@ class RNPCryptoAPI extends CryptoAPI {
|
|||
*
|
||||
* @return {nsIFile} object or null in case no data / error.
|
||||
*/
|
||||
async getPhotoFile(keyId, photoNumber) {}
|
||||
async getPhotoFile(keyId, photoNumber) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
async importKeyBlock(keyBlock) {
|
||||
// TODO: get status results
|
||||
|
@ -137,7 +121,9 @@ class RNPCryptoAPI extends CryptoAPI {
|
|||
* - {Number} importSum: total number of processed keys
|
||||
* - {Number} importUnchanged: number of unchanged keys
|
||||
*/
|
||||
async importKeyFromFile(inputFile) {}
|
||||
async importKeyFromFile(inputFile) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Export secret key(s) to a file
|
||||
|
@ -151,7 +137,9 @@ class RNPCryptoAPI extends CryptoAPI {
|
|||
* - {String} errorMsg: error message in case exitCode !== 0
|
||||
*/
|
||||
|
||||
async extractSecretKey(keyId, minimalKey) {}
|
||||
async extractSecretKey(keyId, minimalKey) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -160,7 +148,9 @@ class RNPCryptoAPI extends CryptoAPI {
|
|||
* @return {String or null} - the name of the attached file
|
||||
*/
|
||||
|
||||
async getFileName(byteData) {}
|
||||
async getFileName(byteData) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -173,7 +163,9 @@ class RNPCryptoAPI extends CryptoAPI {
|
|||
* The message will be an error message in this case.
|
||||
*/
|
||||
|
||||
async verifyAttachment(filePath, sigPath) {}
|
||||
async verifyAttachment(filePath, sigPath) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -186,7 +178,9 @@ class RNPCryptoAPI extends CryptoAPI {
|
|||
* retObj.errorMsg will be an error message in this case.
|
||||
*/
|
||||
|
||||
async decryptAttachment(encrypted) {}
|
||||
async decryptAttachment(encrypted) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
|
@ -1,376 +0,0 @@
|
|||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["GnuPGDecryption"];
|
||||
|
||||
const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
|
||||
const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
|
||||
const EnigmailDialog = ChromeUtils.import("chrome://openpgp/content/modules/dialog.jsm").EnigmailDialog;
|
||||
const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
|
||||
const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
|
||||
const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
|
||||
const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
|
||||
const EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules/errorHandling.jsm").EnigmailErrorHandling;
|
||||
const EnigmailKey = ChromeUtils.import("chrome://openpgp/content/modules/key.jsm").EnigmailKey;
|
||||
const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
|
||||
const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
|
||||
const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
|
||||
|
||||
const STATUS_ERROR = EnigmailConstants.BAD_SIGNATURE | EnigmailConstants.DECRYPTION_FAILED;
|
||||
const STATUS_DECRYPTION_OK = EnigmailConstants.DECRYPTION_OKAY;
|
||||
const STATUS_GOODSIG = EnigmailConstants.GOOD_SIGNATURE;
|
||||
|
||||
var GnuPGDecryption = {
|
||||
|
||||
/*
|
||||
* options:
|
||||
* - logFile (the actual file)
|
||||
* - keyserver
|
||||
* - keyserverProxy
|
||||
* - fromAddr
|
||||
* - noOutput
|
||||
* - verifyOnly
|
||||
* - mimeSignatureFile
|
||||
* - maxOutputLength
|
||||
*
|
||||
*/
|
||||
getDecryptionArgs: function(options) {
|
||||
var args = EnigmailGpg.getStandardArgs(true);
|
||||
|
||||
args.push("--log-file");
|
||||
args.push(EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePath(options.logFile)));
|
||||
|
||||
if (options.keyserver && options.keyserver !== "") {
|
||||
var keyserver = options.keyserver.trim();
|
||||
args.push("--keyserver-options");
|
||||
var keySrvArgs = "auto-key-retrieve";
|
||||
var srvProxy = options.keyserverProxy;
|
||||
if (srvProxy) {
|
||||
keySrvArgs += ",http-proxy=" + srvProxy;
|
||||
}
|
||||
args.push(keySrvArgs);
|
||||
args.push("--keyserver");
|
||||
args.push(keyserver);
|
||||
}
|
||||
|
||||
if (EnigmailGpg.getGpgFeature("supports-sender") && options.fromAddr) {
|
||||
args.push("--sender");
|
||||
args.push(options.fromAddr.toLowerCase());
|
||||
}
|
||||
|
||||
if (options.noOutput) {
|
||||
args.push("--verify");
|
||||
if (options.mimeSignatureFile) {
|
||||
args.push(options.mimeSignatureFile);
|
||||
args.push("-");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (options.maxOutputLength) {
|
||||
args.push("--max-output");
|
||||
args.push(String(options.maxOutputLength));
|
||||
}
|
||||
|
||||
args.push("--decrypt");
|
||||
}
|
||||
|
||||
return args;
|
||||
},
|
||||
decryptMessageEnd: function(stderrStr, exitCode, outputLen, verifyOnly, noOutput, uiFlags, retStatusObj) {
|
||||
EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: uiFlags=" + uiFlags + ", verifyOnly=" + verifyOnly + ", noOutput=" + noOutput + "\n");
|
||||
|
||||
stderrStr = stderrStr.replace(/\r\n/g, "\n");
|
||||
EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: stderrStr=\n" + stderrStr + "\n");
|
||||
|
||||
|
||||
var interactive = uiFlags & EnigmailConstants.UI_INTERACTIVE;
|
||||
var pgpMime = uiFlags & EnigmailConstants.UI_PGP_MIME;
|
||||
var allowImport = uiFlags & EnigmailConstants.UI_ALLOW_KEY_IMPORT;
|
||||
var unverifiedEncryptedOK = uiFlags & EnigmailConstants.UI_UNVERIFIED_ENC_OK;
|
||||
var j;
|
||||
|
||||
retStatusObj.statusFlags = 0;
|
||||
retStatusObj.errorMsg = "";
|
||||
retStatusObj.blockSeparation = "";
|
||||
|
||||
var errorMsg = EnigmailErrorHandling.parseErrorOutput(stderrStr, retStatusObj);
|
||||
if (retStatusObj.statusFlags & STATUS_ERROR) {
|
||||
retStatusObj.errorMsg = errorMsg;
|
||||
}
|
||||
else {
|
||||
retStatusObj.errorMsg = "";
|
||||
}
|
||||
|
||||
if (pgpMime) {
|
||||
retStatusObj.statusFlags |= verifyOnly ? EnigmailConstants.PGP_MIME_SIGNED : EnigmailConstants.PGP_MIME_ENCRYPTED;
|
||||
}
|
||||
|
||||
var statusMsg = retStatusObj.statusMsg;
|
||||
exitCode = EnigmailExecution.fixExitCode(exitCode, retStatusObj);
|
||||
if ((exitCode === 0) && !noOutput && !outputLen &&
|
||||
((retStatusObj.statusFlags & (STATUS_DECRYPTION_OK | STATUS_GOODSIG)) === 0)) {
|
||||
exitCode = -1;
|
||||
}
|
||||
|
||||
if (retStatusObj.statusFlags & EnigmailConstants.DISPLAY_MESSAGE && retStatusObj.extendedStatus.search(/\bdisp:/) >= 0) {
|
||||
EnigmailDialog.alert(null, statusMsg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
var errLines;
|
||||
if (statusMsg) {
|
||||
errLines = statusMsg.split(/\r?\n/);
|
||||
}
|
||||
else {
|
||||
// should not really happen ...
|
||||
errLines = stderrStr.split(/\r?\n/);
|
||||
}
|
||||
|
||||
// possible STATUS Patterns (see GPG dod DETAILS.txt):
|
||||
// one of these should be set for a signature:
|
||||
var newsigPat = /^NEWSIG ?.*$/i;
|
||||
var trustedsigPat = /^TRUST_(FULLY|ULTIMATE) ?.*$/i;
|
||||
var goodsigPat = /^GOODSIG (\w{16}) (.*)$/i;
|
||||
var badsigPat = /^BADSIG (\w{16}) (.*)$/i;
|
||||
var expsigPat = /^EXPSIG (\w{16}) (.*)$/i;
|
||||
var expkeysigPat = /^EXPKEYSIG (\w{16}) (.*)$/i;
|
||||
var revkeysigPat = /^REVKEYSIG (\w{16}) (.*)$/i;
|
||||
var errsigPat = /^ERRSIG (\w{16}) (.*)$/i;
|
||||
// additional infos for good signatures:
|
||||
var validSigPat = /^VALIDSIG (\w+) (.*) (\d+) (.*)/i;
|
||||
// hint for a certain key id:
|
||||
var userIdHintPat = /^USERID_HINT (\w{16}) (.*)$/i;
|
||||
// to find out for which recipients the email was encrypted:
|
||||
var encToPat = /^ENC_TO (\w{16}) (.*)$/i;
|
||||
|
||||
var matches;
|
||||
|
||||
var signed = false;
|
||||
var goodOrExpOrRevSignature = false;
|
||||
var sigKeyId = ""; // key of sender
|
||||
var sigUserId = ""; // user ID of sender
|
||||
var sigDetails = "";
|
||||
var sigTrusted = false;
|
||||
var encToDetails = "";
|
||||
var encToArray = []; // collect ENC_TO lines here
|
||||
|
||||
for (j = 0; j < errLines.length; j++) {
|
||||
EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: process: " + errLines[j] + "\n");
|
||||
|
||||
// ENC_TO entry
|
||||
// - collect them for later processing to print details
|
||||
matches = errLines[j].match(encToPat);
|
||||
if (matches && (matches.length > 2)) {
|
||||
encToArray.push("0x" + matches[1]);
|
||||
}
|
||||
|
||||
// USERID_HINT entry
|
||||
// - NOTE: NO END of loop
|
||||
// ERROR: wrong to set userId because ecom is NOT the sender:
|
||||
//matches = errLines[j].match(userIdHintPat);
|
||||
//if (matches && (matches.length > 2)) {
|
||||
// sigKeyId = matches[1];
|
||||
// sigUserId = matches[2];
|
||||
//}
|
||||
|
||||
// check for one of the possible SIG entries:
|
||||
|
||||
matches = errLines[j].match(newsigPat);
|
||||
if (matches) {
|
||||
if (signed) {
|
||||
EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: multiple SIGN entries - ignoring previous signature\n");
|
||||
}
|
||||
signed = true;
|
||||
goodOrExpOrRevSignature = false;
|
||||
sigKeyId = "";
|
||||
sigUserId = "";
|
||||
sigDetails = "";
|
||||
sigTrusted = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
matches = errLines[j].match(trustedsigPat);
|
||||
if (matches) {
|
||||
sigTrusted = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
matches = errLines[j].match(validSigPat);
|
||||
if (matches && (matches.length > 4)) {
|
||||
if (matches[4].length == 40) {
|
||||
// in case of several subkeys refer to the main key ID.
|
||||
// Only works with PGP V4 keys (Fingerprint length ==40)
|
||||
sigKeyId = matches[4];
|
||||
}
|
||||
if (matches && (matches.length > 2)) {
|
||||
sigDetails = errLines[j].substr(9);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// GOODSIG entry
|
||||
matches = errLines[j].match(goodsigPat);
|
||||
if (matches && (matches.length > 2)) {
|
||||
if (signed) {
|
||||
EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
|
||||
}
|
||||
signed = true;
|
||||
goodOrExpOrRevSignature = true;
|
||||
sigKeyId = matches[1];
|
||||
sigUserId = matches[2];
|
||||
}
|
||||
else {
|
||||
// BADSIG entry => signature found but bad
|
||||
matches = errLines[j].match(badsigPat);
|
||||
if (matches && (matches.length > 2)) {
|
||||
if (signed) {
|
||||
EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
|
||||
}
|
||||
signed = true;
|
||||
goodOrExpOrRevSignature = false;
|
||||
sigKeyId = matches[1];
|
||||
sigUserId = matches[2];
|
||||
}
|
||||
else {
|
||||
// EXPSIG entry => expired signature found
|
||||
matches = errLines[j].match(expsigPat);
|
||||
if (matches && (matches.length > 2)) {
|
||||
if (signed) {
|
||||
EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
|
||||
}
|
||||
signed = true;
|
||||
goodOrExpOrRevSignature = true;
|
||||
sigKeyId = matches[1];
|
||||
sigUserId = matches[2];
|
||||
}
|
||||
else {
|
||||
// EXPKEYSIG entry => signature found but key expired
|
||||
matches = errLines[j].match(expkeysigPat);
|
||||
if (matches && (matches.length > 2)) {
|
||||
if (signed) {
|
||||
EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
|
||||
}
|
||||
signed = true;
|
||||
goodOrExpOrRevSignature = true;
|
||||
sigKeyId = matches[1];
|
||||
sigUserId = matches[2];
|
||||
}
|
||||
else {
|
||||
// REVKEYSIG entry => signature found but key revoked
|
||||
matches = errLines[j].match(revkeysigPat);
|
||||
if (matches && (matches.length > 2)) {
|
||||
if (signed) {
|
||||
EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
|
||||
}
|
||||
signed = true;
|
||||
goodOrExpOrRevSignature = true;
|
||||
sigKeyId = matches[1];
|
||||
sigUserId = matches[2];
|
||||
}
|
||||
else {
|
||||
// ERRSIG entry => signature found but key not usable or unavailable
|
||||
matches = errLines[j].match(errsigPat);
|
||||
if (matches && (matches.length > 2)) {
|
||||
if (signed) {
|
||||
EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
|
||||
}
|
||||
signed = true;
|
||||
goodOrExpOrRevSignature = false;
|
||||
sigKeyId = matches[1];
|
||||
// no user id with ecom istatus entry
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // end loop of processing errLines
|
||||
|
||||
if (sigTrusted) {
|
||||
retStatusObj.statusFlags |= EnigmailConstants.TRUSTED_IDENTITY;
|
||||
}
|
||||
|
||||
if (sigUserId && sigKeyId && EnigmailPrefs.getPref("displaySecondaryUid")) {
|
||||
let keyObj = EnigmailKeyRing.getKeyById(sigKeyId);
|
||||
if (keyObj) {
|
||||
if (keyObj.photoAvailable) {
|
||||
retStatusObj.statusFlags |= EnigmailConstants.PHOTO_AVAILABLE;
|
||||
}
|
||||
sigUserId = EnigmailKeyRing.getValidUids(sigKeyId).join("\n");
|
||||
}
|
||||
}
|
||||
else if (sigUserId) {
|
||||
sigUserId = EnigmailData.convertToUnicode(sigUserId, "UTF-8");
|
||||
}
|
||||
|
||||
// add list of keys used for encryption if known (and their user IDs) if known
|
||||
// Parsed status messages are something like (here the German version):
|
||||
// [GNUPG:] ENC_TO AAAAAAAAAAAAAAAA 1 0
|
||||
// [GNUPG:] ENC_TO 5B820D2D4553884F 16 0
|
||||
// [GNUPG:] ENC_TO 37904DF2E631552F 1 0
|
||||
// [GNUPG:] ENC_TO BBBBBBBBBBBBBBBB 1 0
|
||||
// gpg: verschlüsselt mit 3072-Bit RSA Schlüssel, ID BBBBBBBB, erzeugt 2009-11-28
|
||||
// "Joe Doo <joe.doo@domain.de>"
|
||||
// [GNUPG:] NO_SECKEY E71712DF47BBCC40
|
||||
// gpg: verschlüsselt mit RSA Schlüssel, ID AAAAAAAA
|
||||
// [GNUPG:] NO_SECKEY AAAAAAAAAAAAAAAA
|
||||
if (encToArray.length > 0) {
|
||||
// for each key also show an associated user ID if known:
|
||||
for (var encIdx = 0; encIdx < encToArray.length; ++encIdx) {
|
||||
var localKeyId = encToArray[encIdx];
|
||||
// except for ID 00000000, which signals hidden keys
|
||||
if (localKeyId != "0x0000000000000000") {
|
||||
let localKey = EnigmailKeyRing.getKeyById(localKeyId);
|
||||
if (localKey) {
|
||||
encToArray[encIdx] += " (" + localKey.userId + ")";
|
||||
}
|
||||
}
|
||||
else {
|
||||
encToArray[encIdx] = EnigmailLocale.getString("hiddenKey");
|
||||
}
|
||||
}
|
||||
encToDetails = "\n " + encToArray.join(",\n ") + "\n";
|
||||
}
|
||||
|
||||
retStatusObj.userId = sigUserId;
|
||||
retStatusObj.keyId = sigKeyId;
|
||||
retStatusObj.sigDetails = sigDetails;
|
||||
retStatusObj.encToDetails = encToDetails;
|
||||
|
||||
if (signed) {
|
||||
if (goodOrExpOrRevSignature) {
|
||||
retStatusObj.errorMsg = EnigmailLocale.getString("prefGood", [sigUserId]);
|
||||
/* + ", " + EnigmailLocale.getString("keyId") + " 0x" + sigKeyId.substring(8,16); */
|
||||
}
|
||||
else {
|
||||
if (sigUserId.length > 0) {
|
||||
retStatusObj.errorMsg = EnigmailLocale.getString("prefBad", [sigUserId]);
|
||||
}
|
||||
if (!exitCode)
|
||||
exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (retStatusObj.statusFlags & EnigmailConstants.UNVERIFIED_SIGNATURE) {
|
||||
retStatusObj.keyId = EnigmailKey.extractPubkey(statusMsg);
|
||||
|
||||
if (retStatusObj.statusFlags & EnigmailConstants.DECRYPTION_OKAY) {
|
||||
exitCode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (exitCode !== 0) {
|
||||
// Error processing
|
||||
EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: command execution exit code: " + exitCode + "\n");
|
||||
}
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
};
|
|
@ -12,110 +12,15 @@
|
|||
|
||||
var EXPORTED_SYMBOLS = ["GnuPG_importKeyFromFile", "GnuPG_extractSecretKey"];
|
||||
|
||||
const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
|
||||
const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
|
||||
const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
|
||||
const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
|
||||
const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailLocale;
|
||||
|
||||
|
||||
async function GnuPG_importKeyFromFile(inputFile) {
|
||||
EnigmailLog.DEBUG("gnupg-key.jsm: importKeysFromFile: fileName=" + inputFile.path + "\n");
|
||||
var command = EnigmailGpg.agentPath;
|
||||
var args = EnigmailGpg.getStandardArgs(false).concat(["--no-tty", "--batch", "--no-verbose", "--status-fd", "2", "--no-auto-check-trustdb", "--import"]);
|
||||
|
||||
var fileName = EnigmailFiles.getEscapedFilename((inputFile.QueryInterface(Ci.nsIFile)).path);
|
||||
|
||||
args.push(fileName);
|
||||
|
||||
let res = await EnigmailExecution.execAsync(command, args, "");
|
||||
let statusMsg = res.statusMsg;
|
||||
|
||||
var keyList = [];
|
||||
let importedKeys = [];
|
||||
let importSum = 0;
|
||||
let importUnchanged = 0;
|
||||
|
||||
// IMPORT_RES <count> <no_user_id> <imported> 0 <unchanged>
|
||||
// <n_uids> <n_subk> <n_sigs> <n_revoc> <sec_read> <sec_imported> <sec_dups> <not_imported>
|
||||
if (statusMsg) {
|
||||
let import_res = statusMsg.match(/^IMPORT_RES ([0-9]+) ([0-9]+) ([0-9]+) 0 ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)/m);
|
||||
|
||||
if (import_res !== null) {
|
||||
let secCount = parseInt(import_res[9], 10); // number of secret keys found
|
||||
let secImported = parseInt(import_res[10], 10); // number of secret keys imported
|
||||
let secDups = parseInt(import_res[11], 10); // number of secret keys already on the keyring
|
||||
|
||||
if (secCount !== secImported + secDups) {
|
||||
res.errorMsg = EnigmailLocale.getString("import.secretKeyImportError");
|
||||
res.exitCode = 1;
|
||||
}
|
||||
else {
|
||||
importSum = parseInt(import_res[1], 10);
|
||||
importUnchanged = parseInt(import_res[4], 10);
|
||||
res.exitCode = 0;
|
||||
var statusLines = statusMsg.split(/\r?\n/);
|
||||
|
||||
for (let j = 0; j < statusLines.length; j++) {
|
||||
var matches = statusLines[j].match(/IMPORT_OK ([0-9]+) (\w+)/);
|
||||
if (matches && (matches.length > 2)) {
|
||||
if (typeof (keyList[matches[2]]) != "undefined") {
|
||||
keyList[matches[2]] |= Number(matches[1]);
|
||||
}
|
||||
else
|
||||
keyList[matches[2]] = Number(matches[1]);
|
||||
|
||||
importedKeys.push(matches[2]);
|
||||
EnigmailLog.DEBUG("gnupg-key.jsm: importKeysFromFile: imported " + matches[2] + ":" + matches[1] + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
exitCode: res.exitCode,
|
||||
errorMsg: res.errorMsg,
|
||||
importedKeys: importedKeys,
|
||||
importSum: importSum,
|
||||
importUnchanged: importUnchanged
|
||||
};
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
|
||||
async function GnuPG_extractSecretKey(userId, minimalKey) {
|
||||
let args = EnigmailGpg.getStandardArgs(true);
|
||||
let exitCode = -1,
|
||||
errorMsg = "";
|
||||
|
||||
if (minimalKey) {
|
||||
args.push("--export-options");
|
||||
args.push("export-minimal,no-export-attributes");
|
||||
}
|
||||
|
||||
args.push("-a");
|
||||
args.push("--export-secret-keys");
|
||||
|
||||
if (userId) {
|
||||
args = args.concat(userId.split(/[ ,\t]+/));
|
||||
}
|
||||
|
||||
let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, "");
|
||||
|
||||
if (res.stdoutData) {
|
||||
exitCode = 0;
|
||||
}
|
||||
|
||||
if (exitCode !== 0) {
|
||||
if (res.errorMsg) {
|
||||
errorMsg = EnigmailFiles.formatCmdLine(EnigmailGpg.agentPath, args);
|
||||
errorMsg += "\n" + res.errorMsg;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
keyData: res.stdoutData,
|
||||
exitCode: exitCode,
|
||||
errorMsg: errorMsg
|
||||
};
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
|
|
@ -10,13 +10,12 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["obtainKeyList", "createKeyObj",
|
||||
var EXPORTED_SYMBOLS = ["obtainKeyList",
|
||||
"getPhotoFileFromGnuPG", "extractSignatures", "getGpgKeyData"
|
||||
];
|
||||
|
||||
const EnigmailTime = ChromeUtils.import("chrome://openpgp/content/modules/time.jsm").EnigmailTime;
|
||||
const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
|
||||
const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
|
||||
const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
|
||||
const EnigmailTrust = ChromeUtils.import("chrome://openpgp/content/modules/trust.jsm").EnigmailTrust;
|
||||
const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
|
||||
|
@ -70,40 +69,7 @@ const NS_LOCALFILEOUTPUTSTREAM_CONTRACTID = "@mozilla.org/network/file-output-st
|
|||
*/
|
||||
async function obtainKeyList(onlyKeys = null) {
|
||||
EnigmailLog.DEBUG("gnupg-keylist.jsm: obtainKeyList()\n");
|
||||
|
||||
let secKeyList = [],
|
||||
pubKeyList = [];
|
||||
let commonArgs = EnigmailGpg.getStandardArgs(true);
|
||||
commonArgs = commonArgs.concat(["--with-fingerprint", "--fixed-list-mode", "--with-colons"]);
|
||||
|
||||
let args = commonArgs.concat(["--list-keys"]);
|
||||
if (onlyKeys) {
|
||||
args = args.concat(onlyKeys);
|
||||
}
|
||||
|
||||
let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, "");
|
||||
pubKeyList = res.stdoutData.split(/\n/);
|
||||
|
||||
let keyList = {
|
||||
keys: [],
|
||||
index: []
|
||||
};
|
||||
|
||||
EnigmailLog.DEBUG(`gnupg-keylist.jsm: obtainKeyList: #lines: ${pubKeyList.length}\n`);
|
||||
if (pubKeyList.length > 0) {
|
||||
appendKeyItems(pubKeyList, keyList);
|
||||
|
||||
args = commonArgs.concat(["--list-secret-keys"]);
|
||||
if (onlyKeys) {
|
||||
args = args.concat(onlyKeys);
|
||||
}
|
||||
|
||||
res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, "");
|
||||
secKeyList = res.stdoutData.split(/\n/);
|
||||
appendKeyItems(secKeyList, keyList);
|
||||
}
|
||||
|
||||
return keyList;
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
|
||||
|
@ -262,82 +228,7 @@ function appendUnkownSecretKey(keyId, aKeyList, startIndex, keyList) {
|
|||
*/
|
||||
async function getPhotoFileFromGnuPG(keyId, photoNumber) {
|
||||
EnigmailLog.DEBUG(`gnupg-keylist.jsm: getPhotoFileFromGnuPG, keyId=${keyId} photoNumber=${photoNumber}\n`);
|
||||
|
||||
const GPG_ADDITIONAL_OPTIONS = ["--no-secmem-warning", "--no-verbose", "--no-auto-check-trustdb",
|
||||
"--batch", "--no-tty", "--no-verbose", "--status-fd", "1", "--attribute-fd", "2",
|
||||
"--fixed-list-mode", "--list-keys", keyId
|
||||
];
|
||||
const args = EnigmailGpg.getStandardArgs(false).concat(GPG_ADDITIONAL_OPTIONS);
|
||||
|
||||
let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args);
|
||||
let photoData = res.stderrData;
|
||||
let outputTxt = res.stdoutData;
|
||||
|
||||
if (!outputTxt || !photoData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (EnigmailOS.isDosLike && EnigmailGpg.getGpgFeature("windows-photoid-bug")) {
|
||||
// workaround for error in gpg
|
||||
photoData = photoData.replace(/\r\n/g, "\n");
|
||||
}
|
||||
|
||||
// [GNUPG:] ATTRIBUTE A053069284158FC1E6770BDB57C9EB602B0717E2 2985
|
||||
let foundPicture = -1;
|
||||
let skipData = 0;
|
||||
let imgSize = -1;
|
||||
const statusLines = outputTxt.split(/[\n\r+]/);
|
||||
|
||||
for (let i = 0; i < statusLines.length; i++) {
|
||||
const matches = statusLines[i].match(/\[GNUPG:\] ATTRIBUTE ([A-F\d]+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+)/);
|
||||
if (matches && matches[3] == "1") {
|
||||
// attribute is an image
|
||||
foundPicture++;
|
||||
if (foundPicture === photoNumber) {
|
||||
imgSize = Number(matches[2]);
|
||||
break;
|
||||
} else {
|
||||
skipData += Number(matches[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundPicture >= 0 && foundPicture === photoNumber) {
|
||||
if (photoData.search(/^gpg: /) === 0) {
|
||||
// skip disturbing gpg output
|
||||
let i = photoData.search(/\n/) + 1;
|
||||
skipData += i;
|
||||
}
|
||||
|
||||
const pictureData = photoData.substr(16 + skipData, imgSize);
|
||||
if (!pictureData.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const flags = NS_WRONLY | NS_CREATE_FILE | NS_TRUNCATE;
|
||||
const picFile = EnigmailFiles.getTempDirObj();
|
||||
|
||||
picFile.append(keyId + ".jpg");
|
||||
picFile.createUnique(picFile.NORMAL_FILE_TYPE, STANDARD_FILE_PERMS);
|
||||
|
||||
const fileStream = Cc[NS_LOCALFILEOUTPUTSTREAM_CONTRACTID].createInstance(Ci.nsIFileOutputStream);
|
||||
fileStream.init(picFile, flags, STANDARD_FILE_PERMS, 0);
|
||||
if (fileStream.write(pictureData, pictureData.length) !== pictureData.length) {
|
||||
fileStream.close();
|
||||
throw Components.results.NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
fileStream.flush();
|
||||
fileStream.close();
|
||||
|
||||
// delete picFile upon exit
|
||||
let extAppLauncher = Cc["@mozilla.org/mime;1"].getService(Ci.nsPIExternalAppLauncher);
|
||||
extAppLauncher.deleteTemporaryFileOnExit(picFile);
|
||||
return picFile;
|
||||
} catch (ex) {}
|
||||
}
|
||||
return null;
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
|
||||
|
@ -423,81 +314,7 @@ function extractSignatures(gpgKeyList, ignoreUnknownUid) {
|
|||
return listObj;
|
||||
}
|
||||
|
||||
|
||||
async function getGpgKeyData(armorKeyString) {
|
||||
EnigmailLog.DEBUG("GnuPGCryptoAPI.jsm: getGpgKeyData()\n");
|
||||
|
||||
if (!EnigmailGpg.getGpgFeature("supports-show-only")) {
|
||||
throw "unsupported";
|
||||
}
|
||||
|
||||
let args = EnigmailGpg.getStandardArgs(false).concat(["--no-tty", "--batch", "--no-verbose", "--with-fingerprint", "--with-colons", "--import-options", "import-show", "--dry-run", "--import"]);
|
||||
|
||||
let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, armorKeyString);
|
||||
let lines = res.stdoutData.split(/\n/);
|
||||
|
||||
let key = {};
|
||||
let keyId = "";
|
||||
let keyList = [];
|
||||
/*
|
||||
pub:u:256:22:84F83BE88C892606:1525969855:1683649855::u:::scESC:::::ed25519:::0:
|
||||
fpr:::::::::AFE1B65C5F39ACA7960B22CD84F83BE88C892606:
|
||||
uid:u::::1525969914::22DB32406212400B52CDC74DA2B33418637430F1::Patrick (ECC) <patrick@enigmail.net>::::::::::0:
|
||||
uid:u::::1525969855::F70B7A77F085AA7BA003D6AFAB6FF0DB1FC901B0::enigmail <patrick@enigmail.net>::::::::::0:
|
||||
sub:u:256:18:329DAB3350400C40:1525969855:1683649855:::::e:::::cv25519::
|
||||
fpr:::::::::3B154538D4DFAA19BDADAAD0329DAB3350400C40:
|
||||
*/
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const lineTokens = lines[i].split(/:/);
|
||||
|
||||
switch (lineTokens[ENTRY_ID]) {
|
||||
case "pub":
|
||||
case "sec":
|
||||
key = {
|
||||
id: lineTokens[KEY_ID],
|
||||
fpr: null,
|
||||
name: null,
|
||||
isSecret: false,
|
||||
created: EnigmailTime.getDateTime(lineTokens[CREATED_ID], true, false),
|
||||
uids: []
|
||||
};
|
||||
|
||||
if (!(key.id in keyList)) {
|
||||
keyList[key.id] = key;
|
||||
}
|
||||
|
||||
if (lineTokens[ENTRY_ID] === "sec") {
|
||||
keyList[key.id].isSecret = true;
|
||||
}
|
||||
break;
|
||||
case "fpr":
|
||||
if (!key.fpr) {
|
||||
key.fpr = lineTokens[USERID_ID];
|
||||
}
|
||||
break;
|
||||
case "uid":
|
||||
if (!key.name) {
|
||||
key.name = lineTokens[USERID_ID];
|
||||
}
|
||||
else {
|
||||
key.uids.push(lineTokens[USERID_ID]);
|
||||
}
|
||||
break;
|
||||
case "rvs":
|
||||
case "rvk":
|
||||
keyId = lineTokens[KEY_ID];
|
||||
if (keyId in keyList) {
|
||||
keyList[keyId].revoke = true;
|
||||
} else {
|
||||
keyList[keyId] = {
|
||||
revoke: true,
|
||||
id: keyId
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return keyList;
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
|
|
@ -98,16 +98,6 @@ class CryptoAPI {
|
|||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get groups defined in gpg.conf in the same structure as KeyObject
|
||||
* [synchronous]
|
||||
*
|
||||
* @return {Array of KeyObject} with type = "grp"
|
||||
*/
|
||||
getGroups() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a photo ID from a key, store it as file and return the file object.
|
||||
*
|
||||
|
|
|
@ -21,7 +21,7 @@ function converter(charset) {
|
|||
|
||||
var EnigmailData = {
|
||||
getUnicodeData: function(data) {
|
||||
// convert output from subprocess to Unicode
|
||||
// convert output to Unicode
|
||||
var tmpStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
|
||||
tmpStream.setData(data, data.length);
|
||||
var inStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
|
||||
|
|
|
@ -16,13 +16,11 @@ const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/loca
|
|||
const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
|
||||
const EnigmailDialog = ChromeUtils.import("chrome://openpgp/content/modules/dialog.jsm").EnigmailDialog;
|
||||
const EnigmailHttpProxy = ChromeUtils.import("chrome://openpgp/content/modules/httpProxy.jsm").EnigmailHttpProxy;
|
||||
const EnigmailGpgAgent = ChromeUtils.import("chrome://openpgp/content/modules/gpgAgent.jsm").EnigmailGpgAgent;
|
||||
const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
|
||||
const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
|
||||
const EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules/errorHandling.jsm").EnigmailErrorHandling;
|
||||
const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
|
||||
const EnigmailKey = ChromeUtils.import("chrome://openpgp/content/modules/key.jsm").EnigmailKey;
|
||||
const EnigmailPassword = ChromeUtils.import("chrome://openpgp/content/modules/passwords.jsm").EnigmailPassword;
|
||||
const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
|
||||
const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
|
||||
const EnigmailCryptoAPI = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI.jsm").EnigmailCryptoAPI;
|
||||
|
@ -55,7 +53,8 @@ function newStatusObject() {
|
|||
|
||||
var EnigmailDecryption = {
|
||||
isReady: function(win) {
|
||||
return (EnigmailCore.getService(win)) && (!EnigmailKeyRing.isGeneratingKey());
|
||||
// this used to return false while generating a key. still necessary?
|
||||
return EnigmailCore.getService(win);
|
||||
},
|
||||
|
||||
getFromAddr: function(win) {
|
||||
|
|
|
@ -1,345 +0,0 @@
|
|||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This module provides DNS query functionality via subprocesses.
|
||||
* Supported record types: MX, SRV
|
||||
*
|
||||
* The following tools are currently supported:
|
||||
* Windows: nslookup
|
||||
* Unix/Linux: dig, kdig, host, nslookup
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["EnigmailDns"];
|
||||
|
||||
const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
|
||||
const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
|
||||
const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
|
||||
const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
|
||||
const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
|
||||
|
||||
const RESTYPE_WIN_NLSOOKUP = 1;
|
||||
const RESTYPE_UNIX_NLSOOKUP = 2;
|
||||
const RESTYPE_DIG = 3;
|
||||
const RESTYPE_HOST = 4;
|
||||
const RESTYPE_NOT_AVAILABLE = 99;
|
||||
|
||||
var gHandler = null,
|
||||
gResolverExecutable = null;
|
||||
|
||||
var EnigmailDns = {
|
||||
/**
|
||||
* Perform a DNS lookup
|
||||
*
|
||||
* @param {String} recordType: The resource record type to query. MX and SRV are currently supported.
|
||||
* @param {String} queryName: The name to search for, e.g. "enigmail.net"
|
||||
*
|
||||
* @return {Promise<Array{String}>}: array of server(s) handling
|
||||
*
|
||||
*/
|
||||
lookup: async function(recordType, queryName) {
|
||||
EnigmailLog.DEBUG(`dns.jsm: lookup(${recordType}, ${queryName})\n`);
|
||||
if (!determineResolver()) return null;
|
||||
|
||||
switch (recordType.toUpperCase()) {
|
||||
case "MX":
|
||||
case "SRV":
|
||||
break;
|
||||
default:
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
let dnsHandler = new gHandler(gResolverExecutable);
|
||||
return dnsHandler.execute(recordType, queryName);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine the DNS resolver tool to use (e.g. dig, nslookup)
|
||||
*
|
||||
* @return {Boolean}: true: tool found / false: no tool found
|
||||
*/
|
||||
|
||||
function determineResolver() {
|
||||
if (!gHandler) {
|
||||
gHandler = GenericHandler;
|
||||
if (EnigmailOS.isWin32) {
|
||||
gResolverExecutable = EnigmailFiles.resolvePathWithEnv("nslookup");
|
||||
if (gResolverExecutable) {
|
||||
EnigmailLog.DEBUG(`dns.jsm: determineResolver: executable = ${gResolverExecutable.path}\n`);
|
||||
gHandler = NsLookupHandler_Windows;
|
||||
}
|
||||
}
|
||||
else {
|
||||
determineLinuxResolver();
|
||||
}
|
||||
|
||||
if (!gResolverExecutable) EnigmailLog.DEBUG(`dns.jsm: determineResolver: no executable found\n`);
|
||||
|
||||
}
|
||||
|
||||
return gHandler !== GenericHandler;
|
||||
}
|
||||
|
||||
|
||||
function determineLinuxResolver() {
|
||||
EnigmailLog.DEBUG(`dns.jsm: determineLinuxResolver()\n`);
|
||||
const services = [{
|
||||
exe: "dig",
|
||||
class: DigHandler
|
||||
}, {
|
||||
exe: "kdig",
|
||||
class: DigHandler
|
||||
}, {
|
||||
exe: "host",
|
||||
class: HostHandler
|
||||
}, {
|
||||
exe: "nslookup",
|
||||
class: NsLookupHandler
|
||||
}];
|
||||
|
||||
for (let i of services) {
|
||||
gResolverExecutable = EnigmailFiles.resolvePathWithEnv(i.exe);
|
||||
if (gResolverExecutable) {
|
||||
EnigmailLog.DEBUG(`dns.jsm: determineLinuxResolver: found ${i.class.handlerType}\n`);
|
||||
|
||||
gHandler = i.class;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for executing DNS requests
|
||||
*/
|
||||
class GenericHandler {
|
||||
constructor(handlerFile) {
|
||||
this._handlerFile = handlerFile;
|
||||
this.recordType = "";
|
||||
this.hostName = "";
|
||||
}
|
||||
|
||||
getCmdArgs() {
|
||||
return [];
|
||||
}
|
||||
|
||||
execute(recordType, hostName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
this.recordType = recordType.toUpperCase();
|
||||
this.hostName = hostName;
|
||||
let args = this.getCmdArgs();
|
||||
|
||||
if (args.length === 0) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
|
||||
let stdoutData = "",
|
||||
stderrData = "";
|
||||
let self = this;
|
||||
|
||||
EnigmailLog.DEBUG(`dns.jsm: execute(): launching ${EnigmailFiles.formatCmdLine(this._handlerFile, args)}\n`);
|
||||
|
||||
subprocess.call({
|
||||
command: this._handlerFile,
|
||||
arguments: args,
|
||||
environment: EnigmailCore.getEnvList(),
|
||||
charset: null,
|
||||
stdout: function(data) {
|
||||
//EnigmailLog.DEBUG(`dns.jsm: execute.stdout: got data ${data}\n`);
|
||||
stdoutData += data;
|
||||
},
|
||||
stderr: function(data) {
|
||||
//EnigmailLog.DEBUG(`dns.jsm: execute.stderr: got data ${data}\n`);
|
||||
stderrData += data;
|
||||
},
|
||||
done: function(result) {
|
||||
EnigmailLog.DEBUG(`dns.jsm: execute.done(${result.exitCode})\n`);
|
||||
try {
|
||||
if (result.exitCode === 0) {
|
||||
resolve(self.parseResult(stdoutData));
|
||||
}
|
||||
else {
|
||||
resolve([]);
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
reject(ex);
|
||||
}
|
||||
},
|
||||
mergeStderr: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
parseResult() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler class for "dig" and "kdig"
|
||||
*/
|
||||
class DigHandler extends GenericHandler {
|
||||
constructor(handlerFile) {
|
||||
super(handlerFile);
|
||||
this.handlerType = "dig";
|
||||
}
|
||||
|
||||
getCmdArgs() {
|
||||
return ["-t", this.recordType, "+short", this.hostName];
|
||||
}
|
||||
|
||||
parseResult(stdoutData) {
|
||||
let hosts = [];
|
||||
let lines = stdoutData.split(/[\r\n]+/);
|
||||
|
||||
if (this.recordType === "MX") {
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let m = lines[i].match(/^(\d+ )(.*)\./);
|
||||
|
||||
if (m && m.length >= 3) hosts.push(m[2]);
|
||||
}
|
||||
}
|
||||
else if (this.recordType === "SRV") {
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let m = lines[i].match(/^(\d+) (\d+) (\d+) ([^ ]+)\.$/);
|
||||
|
||||
if (m && m.length >= 5) hosts.push(m[4] + ":" + m[3]);
|
||||
}
|
||||
}
|
||||
|
||||
return hosts;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler class for "host"
|
||||
*/
|
||||
|
||||
class HostHandler extends GenericHandler {
|
||||
constructor(handlerFile) {
|
||||
super(handlerFile);
|
||||
this.handlerType = "host";
|
||||
}
|
||||
|
||||
getCmdArgs() {
|
||||
return ["-t", this.recordType, this.hostName];
|
||||
}
|
||||
|
||||
parseResult(stdoutData) {
|
||||
if (stdoutData.search(/3\(NXDOMAIN\)/) >= 0) return [];
|
||||
|
||||
let hosts = [];
|
||||
let lines = stdoutData.split(/[\r\n]+/);
|
||||
|
||||
if (this.recordType === "MX") {
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let m = lines[i].match(/^(.* )([^ ]+)\.$/);
|
||||
|
||||
if (m && m.length >= 3) hosts.push(m[2]);
|
||||
}
|
||||
}
|
||||
else if (this.recordType === "SRV") {
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let m = lines[i].match(/^(.*) (\d+) ([^ ]+)\.$/);
|
||||
|
||||
if (m && m.length >= 4) hosts.push(m[3] + ":" + m[2]);
|
||||
}
|
||||
}
|
||||
|
||||
return hosts;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler class for "nslookup" (on Linux/Unix)
|
||||
*/
|
||||
|
||||
class NsLookupHandler extends GenericHandler {
|
||||
constructor(handlerFile) {
|
||||
super(handlerFile);
|
||||
this.handlerType = "nslookup";
|
||||
}
|
||||
|
||||
getCmdArgs() {
|
||||
return ["-type=" + this.recordType, this.hostName];
|
||||
}
|
||||
|
||||
parseResult(stdoutData) {
|
||||
let hosts = [];
|
||||
let lines = stdoutData.split(/[\r\n]+/);
|
||||
|
||||
if (lines.length > 3 && lines[3].search(/: NXDOMAIN/) > 0) return [];
|
||||
|
||||
if (this.recordType === "MX") {
|
||||
let reg = new RegExp("^" + this.hostName.toLowerCase() + "(.* )([^ \t]+.*[^\.])\\.?$");
|
||||
for (let i = 2; i < lines.length; i++) {
|
||||
let m = lines[i].match(reg);
|
||||
|
||||
if (m && m.length >= 3) hosts.push(m[2]);
|
||||
if (lines[i].length < 5) break;
|
||||
}
|
||||
}
|
||||
else if (this.recordType === "SRV") {
|
||||
for (let i = 2; i < lines.length; i++) {
|
||||
let m = lines[i].match(/^(.*) (\d+) ([^ ]+)\.$/);
|
||||
|
||||
if (m && m.length >= 3) hosts.push(m[3] + ":" + m[2]);
|
||||
if (lines[i].length < 5) break;
|
||||
}
|
||||
}
|
||||
|
||||
return hosts;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler class for "nslookup" on Windows
|
||||
*/
|
||||
|
||||
class NsLookupHandler_Windows extends NsLookupHandler {
|
||||
|
||||
parseResult(stdoutData) {
|
||||
let hosts = [];
|
||||
let lines = stdoutData.split(/[\r\n]+/);
|
||||
|
||||
if (this.recordType === "MX") {
|
||||
let reg = new RegExp("^" + this.hostName.toLowerCase() + "(.* )([^ \t]+.*[^\.])\\.?$");
|
||||
for (let i = 2; i < lines.length; i++) {
|
||||
let m = lines[i].match(reg);
|
||||
|
||||
if (m && m.length >= 3) hosts.push(m[2]);
|
||||
if (lines[i].length < 5) break;
|
||||
}
|
||||
}
|
||||
else if (this.recordType === "SRV") {
|
||||
let svc = null;
|
||||
for (let i = 2; i < lines.length; i++) {
|
||||
if (lines[i].search(/SRV service location:$/) > 0) {
|
||||
svc = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
let m = lines[i].match(/^[\t ]+(port|svr hostname)([\t =]+)([^ \t]+)$/);
|
||||
|
||||
if (m && m.length >= 4) {
|
||||
if (m[1] === "port" && svc === null) {
|
||||
svc = m[3];
|
||||
}
|
||||
else if (m[1] === "svr hostname" && svc) {
|
||||
hosts.push(m[3] + ":" + svc);
|
||||
}
|
||||
}
|
||||
if (lines[i].length < 5) break;
|
||||
}
|
||||
}
|
||||
|
||||
return hosts;
|
||||
}
|
||||
}
|
|
@ -16,12 +16,9 @@ const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs
|
|||
const EnigmailApp = ChromeUtils.import("chrome://openpgp/content/modules/app.jsm").EnigmailApp;
|
||||
const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
|
||||
const EnigmailDialog = ChromeUtils.import("chrome://openpgp/content/modules/dialog.jsm").EnigmailDialog;
|
||||
const EnigmailGpgAgent = ChromeUtils.import("chrome://openpgp/content/modules/gpgAgent.jsm").EnigmailGpgAgent;
|
||||
const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
|
||||
const EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules/errorHandling.jsm").EnigmailErrorHandling;
|
||||
const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
|
||||
const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
|
||||
const EnigmailPassword = ChromeUtils.import("chrome://openpgp/content/modules/passwords.jsm").EnigmailPassword;
|
||||
const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
|
||||
const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
|
||||
const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
|
||||
|
@ -396,15 +393,6 @@ var EnigmailEncryption = {
|
|||
}
|
||||
};
|
||||
|
||||
// GnuPG
|
||||
/*
|
||||
var proc = EnigmailExecution.execStart(EnigmailGpgAgent.agentPath, encryptArgs, signMsg, win, listener, statusFlagsObj);
|
||||
if (statusFlagsObj.value & EnigmailConstants.MISSING_PASSPHRASE) {
|
||||
EnigmailLog.ERROR("encryption.jsm: encryptMessageStart: Error - no passphrase supplied\n");
|
||||
errorMsgObj.value = "";
|
||||
}
|
||||
*/
|
||||
|
||||
let resultStatus = {};
|
||||
const cApi = EnigmailCryptoAPI();
|
||||
console.debug("listener: %o", listener);
|
||||
|
@ -441,9 +429,9 @@ var EnigmailEncryption = {
|
|||
return -1;
|
||||
}
|
||||
|
||||
EnigmailErrorHandling.parseErrorOutput(stderrStr, retStatusObj);
|
||||
//EnigmailErrorHandling.parseErrorOutput(stderrStr, retStatusObj);
|
||||
|
||||
exitCode = EnigmailExecution.fixExitCode(exitCode, retStatusObj);
|
||||
//exitCode = EnigmailExecution.fixExitCode(exitCode, retStatusObj);
|
||||
if ((exitCode === 0) && !outputLen) {
|
||||
exitCode = -1;
|
||||
}
|
||||
|
@ -498,7 +486,9 @@ var EnigmailEncryption = {
|
|||
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");
|
||||
throw new Error("Not implemented");
|
||||
|
||||
/*
|
||||
exitCodeObj.value = -1;
|
||||
statusFlagsObj.value = 0;
|
||||
errorMsgObj.value = "";
|
||||
|
@ -567,56 +557,6 @@ var EnigmailEncryption = {
|
|||
// 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 |= EnigmailConstants.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 & EnigmailConstants.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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,752 +0,0 @@
|
|||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/* exported BaseProcess, PromiseWorker */
|
||||
/* global ChromeWorker: false, */
|
||||
|
||||
var {
|
||||
results: Cr
|
||||
} = Components;
|
||||
|
||||
const XPCOMUtils = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
|
||||
Components.utils.importGlobalProperties(["TextDecoder", "TextEncoder"]);
|
||||
|
||||
var _AsyncShutdown, _setTimeout;
|
||||
|
||||
var SubScriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
|
||||
SubScriptLoader.loadSubScript("chrome://openpgp/content/modules/enigmailprocess_shared.js", this);
|
||||
|
||||
var EXPORTED_SYMBOLS = ["BaseProcess", "PromiseWorker", "SubprocessConstants"];
|
||||
|
||||
const BUFFER_SIZE = 32768;
|
||||
|
||||
let nextResponseId = 0;
|
||||
|
||||
|
||||
function getAsyncShutdown() {
|
||||
if (!_AsyncShutdown) {
|
||||
_AsyncShutdown = ChromeUtils.import("resource://gre/modules/AsyncShutdown.jsm").AsyncShutdown;
|
||||
}
|
||||
return _AsyncShutdown;
|
||||
}
|
||||
|
||||
function getSetTimeout() {
|
||||
if (!_setTimeout) {
|
||||
_setTimeout = ChromeUtils.import("resource://gre/modules/Timer.jsm").setTimeout;
|
||||
}
|
||||
return _setTimeout;
|
||||
}
|
||||
|
||||
/* global SubprocessConstants: true */
|
||||
|
||||
/**
|
||||
* Wraps a ChromeWorker so that messages sent to it return a promise which
|
||||
* resolves when the message has been received and the operation it triggers is
|
||||
* complete.
|
||||
*/
|
||||
class _PromiseWorker extends ChromeWorker {
|
||||
constructor(url) {
|
||||
super(url);
|
||||
|
||||
this.listeners = new Map();
|
||||
this.pendingResponses = new Map();
|
||||
|
||||
this.addListener("close", this.onClose.bind(this));
|
||||
this.addListener("failure", this.onFailure.bind(this));
|
||||
this.addListener("success", this.onSuccess.bind(this));
|
||||
this.addListener("debug", this.onDebug.bind(this));
|
||||
|
||||
this.addEventListener("message", this.onmessage);
|
||||
|
||||
this.shutdown = this.shutdown.bind(this);
|
||||
getAsyncShutdown().webWorkersShutdown.addBlocker(
|
||||
"Subprocess.jsm: Shut down IO worker",
|
||||
this.shutdown);
|
||||
}
|
||||
|
||||
onClose() {
|
||||
getAsyncShutdown().webWorkersShutdown.removeBlocker(this.shutdown);
|
||||
}
|
||||
|
||||
shutdown() {
|
||||
return this.call("shutdown", []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for the given message from the worker. Any message received
|
||||
* from the worker with a `data.msg` property matching the given `msg`
|
||||
* parameter are passed to the given listener.
|
||||
*
|
||||
* @param {string} msg
|
||||
* The message to listen for.
|
||||
* @param {function(Event)} listener
|
||||
* The listener to call when matching messages are received.
|
||||
*/
|
||||
addListener(msg, listener) {
|
||||
if (!this.listeners.has(msg)) {
|
||||
this.listeners.set(msg, new Set());
|
||||
}
|
||||
this.listeners.get(msg).add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given message listener.
|
||||
*
|
||||
* @param {string} msg
|
||||
* The message to stop listening for.
|
||||
* @param {function(Event)} listener
|
||||
* The listener to remove.
|
||||
*/
|
||||
removeListener(msg, listener) {
|
||||
let listeners = this.listeners.get(msg);
|
||||
if (listeners) {
|
||||
listeners.delete(listener);
|
||||
|
||||
if (!listeners.size) {
|
||||
this.listeners.delete(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onmessage(event) {
|
||||
let {
|
||||
msg
|
||||
} = event.data;
|
||||
let listeners = this.listeners.get(msg) || new Set();
|
||||
|
||||
for (let listener of listeners) {
|
||||
try {
|
||||
listener(event.data);
|
||||
}
|
||||
catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a message sent to the worker has failed, and rejects its
|
||||
* corresponding promise.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
onFailure({
|
||||
msgId, error
|
||||
}) {
|
||||
this.pendingResponses.get(msgId).reject(error);
|
||||
this.pendingResponses.delete(msgId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a message sent to the worker has succeeded, and resolves its
|
||||
* corresponding promise.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
onSuccess({
|
||||
msgId, data
|
||||
}) {
|
||||
this.pendingResponses.get(msgId).resolve(data);
|
||||
this.pendingResponses.delete(msgId);
|
||||
}
|
||||
|
||||
onDebug({
|
||||
message
|
||||
}) {
|
||||
//dump(`Worker debug: ${message}\n`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the given method in the worker, and returns a promise which resolves
|
||||
* or rejects when the method has completed.
|
||||
*
|
||||
* @param {string} method
|
||||
* The name of the method to call.
|
||||
* @param {Array} args
|
||||
* The arguments to pass to the method.
|
||||
* @param {Array} [transferList]
|
||||
* A list of objects to transfer to the worker, rather than cloning.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
call(method, args, transferList = []) {
|
||||
let msgId = nextResponseId++;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pendingResponses.set(msgId, {
|
||||
resolve, reject
|
||||
});
|
||||
|
||||
let message = {
|
||||
msg: method,
|
||||
msgId,
|
||||
args
|
||||
};
|
||||
|
||||
this.postMessage(message, transferList);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var PromiseWorker = _PromiseWorker;
|
||||
|
||||
/**
|
||||
* Represents an input or output pipe connected to a subprocess.
|
||||
*
|
||||
* @property {integer} fd
|
||||
* The file descriptor number of the pipe on the child process's side.
|
||||
* @readonly
|
||||
*/
|
||||
class Pipe {
|
||||
/**
|
||||
* @param {Process} process
|
||||
* The child process that this pipe is connected to.
|
||||
* @param {integer} fd
|
||||
* The file descriptor number of the pipe on the child process's side.
|
||||
* @param {integer} id
|
||||
* The internal ID of the pipe, which ties it to the corresponding Pipe
|
||||
* object on the Worker side.
|
||||
*/
|
||||
constructor(process, fd, id) {
|
||||
this.id = id;
|
||||
this.fd = fd;
|
||||
this.processId = process.id;
|
||||
this.worker = process.worker;
|
||||
|
||||
/**
|
||||
* @property {boolean} closed
|
||||
* True if the file descriptor has been closed, and can no longer
|
||||
* be read from or written to. Pending IO operations may still
|
||||
* complete, but new operations may not be initiated.
|
||||
* @readonly
|
||||
*/
|
||||
this.closed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the end of the pipe which belongs to this process.
|
||||
*
|
||||
* @param {boolean} force
|
||||
* If true, the pipe is closed immediately, regardless of any pending
|
||||
* IO operations. If false, the pipe is closed after any existing
|
||||
* pending IO operations have completed.
|
||||
* @returns {Promise<object>}
|
||||
* Resolves to an object with no properties once the pipe has been
|
||||
* closed.
|
||||
*/
|
||||
close(force = false) {
|
||||
this.closed = true;
|
||||
return this.worker.call("close", [this.id, force]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an output-only pipe, to which data may be written.
|
||||
*/
|
||||
class OutputPipe extends Pipe {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.encoder = new TextEncoder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given data to the stream.
|
||||
*
|
||||
* When given an array buffer or typed array, ownership of the buffer is
|
||||
* transferred to the IO worker, and it may no longer be used from this
|
||||
* thread.
|
||||
*
|
||||
* @param {ArrayBuffer|TypedArray|string} buffer
|
||||
* Data to write to the stream.
|
||||
* @returns {Promise<object>}
|
||||
* Resolves to an object with a `bytesWritten` property, containing
|
||||
* the number of bytes successfully written, once the operation has
|
||||
* completed.
|
||||
*
|
||||
* @rejects {object}
|
||||
* May be rejected with an Error object, or an object with similar
|
||||
* properties. The object will include an `errorCode` property with
|
||||
* one of the following values if it was rejected for the
|
||||
* corresponding reason:
|
||||
*
|
||||
* - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
|
||||
* all of the data in `buffer` could be written to it.
|
||||
*/
|
||||
write(buffer) {
|
||||
if (typeof buffer === "string") {
|
||||
buffer = this.encoder.encode(buffer);
|
||||
}
|
||||
|
||||
if (Cu.getClassName(buffer, true) !== "ArrayBuffer") {
|
||||
if (buffer.byteLength === buffer.buffer.byteLength) {
|
||||
buffer = buffer.buffer;
|
||||
}
|
||||
else {
|
||||
buffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
||||
}
|
||||
}
|
||||
|
||||
let args = [this.id, buffer];
|
||||
|
||||
return this.worker.call("write", args, [buffer]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an input-only pipe, from which data may be read.
|
||||
*/
|
||||
class InputPipe extends Pipe {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.buffers = [];
|
||||
|
||||
/**
|
||||
* @property {integer} dataAvailable
|
||||
* The number of readable bytes currently stored in the input
|
||||
* buffer.
|
||||
* @readonly
|
||||
*/
|
||||
this.dataAvailable = 0;
|
||||
|
||||
this.decoder = new TextDecoder();
|
||||
|
||||
this.pendingReads = [];
|
||||
|
||||
this._pendingBufferRead = null;
|
||||
|
||||
this.fillBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @property {integer} bufferSize
|
||||
* The current size of the input buffer. This varies depending on
|
||||
* the size of pending read operations.
|
||||
* @readonly
|
||||
*/
|
||||
get bufferSize() {
|
||||
if (this.pendingReads.length) {
|
||||
return Math.max(this.pendingReads[0].length, BUFFER_SIZE);
|
||||
}
|
||||
return BUFFER_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to fill the input buffer.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
fillBuffer() {
|
||||
let dataWanted = this.bufferSize - this.dataAvailable;
|
||||
|
||||
if (!this._pendingBufferRead && dataWanted > 0) {
|
||||
this._pendingBufferRead = this._read(dataWanted);
|
||||
|
||||
this._pendingBufferRead.then((result) => {
|
||||
this._pendingBufferRead = null;
|
||||
|
||||
if (result) {
|
||||
this.onInput(result.buffer);
|
||||
|
||||
this.fillBuffer();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_read(size) {
|
||||
let args = [this.id, size];
|
||||
|
||||
return this.worker.call("read", args).catch(e => {
|
||||
this.closed = true;
|
||||
|
||||
for (let {
|
||||
length, resolve, reject
|
||||
}
|
||||
of this.pendingReads.splice(0)) {
|
||||
if (length === null && e.errorCode === SubprocessConstants.ERROR_END_OF_FILE) {
|
||||
resolve(new ArrayBuffer(0));
|
||||
}
|
||||
else {
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given data to the end of the input buffer.
|
||||
*
|
||||
* @param {ArrayBuffer} buffer
|
||||
* An input buffer to append to the current buffered input.
|
||||
* @private
|
||||
*/
|
||||
onInput(buffer) {
|
||||
this.buffers.push(buffer);
|
||||
this.dataAvailable += buffer.byteLength;
|
||||
this.checkPendingReads();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the topmost pending read operations and fulfills as many as can be
|
||||
* filled from the current input buffer.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
checkPendingReads() {
|
||||
this.fillBuffer();
|
||||
|
||||
let reads = this.pendingReads;
|
||||
while (reads.length && this.dataAvailable &&
|
||||
reads[0].length <= this.dataAvailable) {
|
||||
let pending = this.pendingReads.shift();
|
||||
|
||||
let length = pending.length || this.dataAvailable;
|
||||
|
||||
let result;
|
||||
let byteLength = this.buffers[0].byteLength;
|
||||
if (byteLength == length) {
|
||||
result = this.buffers.shift();
|
||||
}
|
||||
else if (byteLength > length) {
|
||||
let buffer = this.buffers[0];
|
||||
|
||||
this.buffers[0] = buffer.slice(length);
|
||||
result = ArrayBuffer.transfer(buffer, length);
|
||||
}
|
||||
else {
|
||||
result = ArrayBuffer.transfer(this.buffers.shift(), length);
|
||||
let u8result = new Uint8Array(result);
|
||||
|
||||
while (byteLength < length) {
|
||||
let buffer = this.buffers[0];
|
||||
let u8buffer = new Uint8Array(buffer);
|
||||
|
||||
let remaining = length - byteLength;
|
||||
|
||||
if (buffer.byteLength <= remaining) {
|
||||
this.buffers.shift();
|
||||
|
||||
u8result.set(u8buffer, byteLength);
|
||||
}
|
||||
else {
|
||||
this.buffers[0] = buffer.slice(remaining);
|
||||
|
||||
u8result.set(u8buffer.subarray(0, remaining), byteLength);
|
||||
}
|
||||
|
||||
byteLength += Math.min(buffer.byteLength, remaining);
|
||||
}
|
||||
}
|
||||
|
||||
this.dataAvailable -= result.byteLength;
|
||||
pending.resolve(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads exactly `length` bytes of binary data from the input stream, or, if
|
||||
* length is not provided, reads the first chunk of data to become available.
|
||||
* In the latter case, returns an empty array buffer on end of file.
|
||||
*
|
||||
* The read operation will not complete until enough data is available to
|
||||
* fulfill the request. If the pipe closes without enough available data to
|
||||
* fulfill the read, the operation fails, and any remaining buffered data is
|
||||
* lost.
|
||||
*
|
||||
* @param {integer} [length]
|
||||
* The number of bytes to read.
|
||||
* @returns {Promise<ArrayBuffer>}
|
||||
*
|
||||
* @rejects {object}
|
||||
* May be rejected with an Error object, or an object with similar
|
||||
* properties. The object will include an `errorCode` property with
|
||||
* one of the following values if it was rejected for the
|
||||
* corresponding reason:
|
||||
*
|
||||
* - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
|
||||
* enough input could be read to satisfy the request.
|
||||
*/
|
||||
read(length = null) {
|
||||
if (length !== null && !(Number.isInteger(length) && length >= 0)) {
|
||||
throw new RangeError("Length must be a non-negative integer");
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
return Promise.resolve(new ArrayBuffer(0));
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pendingReads.push({
|
||||
length, resolve, reject
|
||||
});
|
||||
this.checkPendingReads();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads exactly `length` bytes from the input stream, and parses them as
|
||||
* UTF-8 JSON data.
|
||||
*
|
||||
* @param {integer} length
|
||||
* The number of bytes to read.
|
||||
* @returns {Promise<object>}
|
||||
*
|
||||
* @rejects {object}
|
||||
* May be rejected with an Error object, or an object with similar
|
||||
* properties. The object will include an `errorCode` property with
|
||||
* one of the following values if it was rejected for the
|
||||
* corresponding reason:
|
||||
*
|
||||
* - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
|
||||
* enough input could be read to satisfy the request.
|
||||
* - Subprocess.ERROR_INVALID_JSON: The data read from the pipe
|
||||
* could not be parsed as a valid JSON string.
|
||||
*/
|
||||
readJSON(length) {
|
||||
if (!Number.isInteger(length) || length <= 0) {
|
||||
throw new RangeError("Length must be a positive integer");
|
||||
}
|
||||
|
||||
return this.readString(length).then(string => {
|
||||
try {
|
||||
return JSON.parse(string);
|
||||
}
|
||||
catch (e) {
|
||||
e.errorCode = SubprocessConstants.ERROR_INVALID_JSON;
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a chunk of UTF-8 data from the input stream, and converts it to a
|
||||
* JavaScript string.
|
||||
*
|
||||
* If `length` is provided, reads exactly `length` bytes. Otherwise, reads the
|
||||
* first chunk of data to become available, and returns an empty string on end
|
||||
* of file. In the latter case, the chunk is decoded in streaming mode, and
|
||||
* any incomplete UTF-8 sequences at the end of a chunk are returned at the
|
||||
* start of a subsequent read operation.
|
||||
*
|
||||
* @param {integer} [length]
|
||||
* The number of bytes to read.
|
||||
* @param {object} [options]
|
||||
* An options object as expected by TextDecoder.decode.
|
||||
* @returns {Promise<string>}
|
||||
*
|
||||
* @rejects {object}
|
||||
* May be rejected with an Error object, or an object with similar
|
||||
* properties. The object will include an `errorCode` property with
|
||||
* one of the following values if it was rejected for the
|
||||
* corresponding reason:
|
||||
*
|
||||
* - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
|
||||
* enough input could be read to satisfy the request.
|
||||
*/
|
||||
readString(length = null, options = {
|
||||
stream: length === null
|
||||
}) {
|
||||
if (length !== null && !(Number.isInteger(length) && length >= 0)) {
|
||||
throw new RangeError("Length must be a non-negative integer");
|
||||
}
|
||||
|
||||
return this.read(length).then(buffer => {
|
||||
return this.decoder.decode(buffer, options);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads 4 bytes from the input stream, and parses them as an unsigned
|
||||
* integer, in native byte order.
|
||||
*
|
||||
* @returns {Promise<integer>}
|
||||
*
|
||||
* @rejects {object}
|
||||
* May be rejected with an Error object, or an object with similar
|
||||
* properties. The object will include an `errorCode` property with
|
||||
* one of the following values if it was rejected for the
|
||||
* corresponding reason:
|
||||
*
|
||||
* - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
|
||||
* enough input could be read to satisfy the request.
|
||||
*/
|
||||
readUint32() {
|
||||
return this.read(4).then(buffer => {
|
||||
return new Uint32Array(buffer)[0];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Process
|
||||
* @extends BaseProcess
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a currently-running process, and allows interaction with it.
|
||||
*/
|
||||
class _BaseProcess {
|
||||
/**
|
||||
* @param {PromiseWorker} worker
|
||||
* The worker instance which owns the process.
|
||||
* @param {integer} processId
|
||||
* The internal ID of the Process object, which ties it to the
|
||||
* corresponding process on the Worker side.
|
||||
* @param {integer[]} fds
|
||||
* An array of internal Pipe IDs, one for each standard file descriptor
|
||||
* in the child process.
|
||||
* @param {integer} pid
|
||||
* The operating system process ID of the process.
|
||||
*/
|
||||
constructor(worker, processId, fds, pid) {
|
||||
this.id = processId;
|
||||
this.worker = worker;
|
||||
|
||||
/**
|
||||
* @property {integer} pid
|
||||
* The process ID of the process, assigned by the operating system.
|
||||
* @readonly
|
||||
*/
|
||||
this.pid = pid;
|
||||
|
||||
this.exitCode = null;
|
||||
|
||||
this.exitPromise = new Promise(resolve => {
|
||||
this.worker.call("wait", [this.id]).then(({
|
||||
exitCode
|
||||
}) => {
|
||||
resolve(Object.freeze({
|
||||
exitCode
|
||||
}));
|
||||
this.exitCode = exitCode;
|
||||
});
|
||||
});
|
||||
|
||||
if (fds[0] !== undefined) {
|
||||
/**
|
||||
* @property {OutputPipe} stdin
|
||||
* A Pipe object which allows writing to the process's standard
|
||||
* input.
|
||||
* @readonly
|
||||
*/
|
||||
this.stdin = new OutputPipe(this, 0, fds[0]);
|
||||
}
|
||||
if (fds[1] !== undefined) {
|
||||
/**
|
||||
* @property {InputPipe} stdout
|
||||
* A Pipe object which allows reading from the process's standard
|
||||
* output.
|
||||
* @readonly
|
||||
*/
|
||||
this.stdout = new InputPipe(this, 1, fds[1]);
|
||||
}
|
||||
if (fds[2] !== undefined) {
|
||||
/**
|
||||
* @property {InputPipe} [stderr]
|
||||
* An optional Pipe object which allows reading from the
|
||||
* process's standard error output.
|
||||
* @readonly
|
||||
*/
|
||||
this.stderr = new InputPipe(this, 2, fds[2]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns a process, and resolves to a BaseProcess instance on success.
|
||||
*
|
||||
* @param {object} options
|
||||
* An options object as passed to `Subprocess.call`.
|
||||
*
|
||||
* @returns {Promise<BaseProcess>}
|
||||
*/
|
||||
static create(options) {
|
||||
let worker = this.getWorker();
|
||||
|
||||
return worker.call("spawn", [options]).then(({
|
||||
processId, fds, pid
|
||||
}) => {
|
||||
return new this(worker, processId, fds, pid);
|
||||
});
|
||||
}
|
||||
|
||||
static get WORKER_URL() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
static get WorkerClass() {
|
||||
return PromiseWorker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current subprocess worker, or spawns a new one if it does not
|
||||
* currently exist.
|
||||
*
|
||||
* @returns {PromiseWorker}
|
||||
*/
|
||||
static getWorker() {
|
||||
if (!this._worker) {
|
||||
this._worker = new this.WorkerClass(this.WORKER_URL);
|
||||
}
|
||||
return this._worker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kills the process.
|
||||
*
|
||||
* @param {integer} [timeout=300]
|
||||
* A timeout, in milliseconds, after which the process will be forcibly
|
||||
* killed. On platforms which support it, the process will be sent
|
||||
* a `SIGTERM` signal immediately, so that it has a chance to terminate
|
||||
* gracefully, and a `SIGKILL` signal if it hasn't exited within
|
||||
* `timeout` milliseconds. On other platforms (namely Windows), the
|
||||
* process will be forcibly terminated immediately.
|
||||
*
|
||||
* @returns {Promise<object>}
|
||||
* Resolves to an object with an `exitCode` property when the process
|
||||
* has exited.
|
||||
*/
|
||||
kill(timeout = 300) {
|
||||
// If the process has already exited, don't bother sending a signal.
|
||||
if (this.exitCode !== null) {
|
||||
return this.wait();
|
||||
}
|
||||
|
||||
let force = timeout <= 0;
|
||||
this.worker.call("kill", [this.id, force]);
|
||||
|
||||
if (!force) {
|
||||
getSetTimeout()(() => {
|
||||
if (this.exitCode === null) {
|
||||
this.worker.call("kill", [this.id, true]);
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
return this.wait();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise which resolves to the process's exit code, once it has
|
||||
* exited.
|
||||
*
|
||||
* @returns {Promise<object>}
|
||||
* Resolves to an object with an `exitCode` property, containing the
|
||||
* process's exit code, once the process has exited.
|
||||
*
|
||||
* On Unix-like systems, a negative exit code indicates that the
|
||||
* process was killed by a signal whose signal number is the absolute
|
||||
* value of the error code. On Windows, an exit code of -9 indicates
|
||||
* that the process was killed via the {@linkcode BaseProcess#kill kill()}
|
||||
* method.
|
||||
*/
|
||||
wait() {
|
||||
return this.exitPromise;
|
||||
}
|
||||
}
|
||||
|
||||
var BaseProcess = _BaseProcess;
|
|
@ -1,178 +0,0 @@
|
|||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* These modules are loosely based on the subprocess.jsm module created
|
||||
* by Jan Gerber and Patrick Brunschwig, though the implementation
|
||||
* differs drastically.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
let EXPORTED_SYMBOLS = ["SubprocessMain"];
|
||||
|
||||
/* exported SubprocessMain */
|
||||
|
||||
var {
|
||||
results: Cr
|
||||
} = Components;
|
||||
|
||||
Components.utils.importGlobalProperties(["TextEncoder"]);
|
||||
const AppConstants = ChromeUtils.import("resource://gre/modules/AppConstants.jsm").AppConstants;
|
||||
const XPCOMUtils = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
|
||||
var SubprocessConstants = ChromeUtils.import("chrome://openpgp/content/modules/enigmailprocess_common.jsm").SubprocessConstants;
|
||||
|
||||
if (AppConstants.platform == "win") {
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SubprocessImpl",
|
||||
"chrome://openpgp/content/modules/enigmailprocess_win.jsm"); /* global SubprocessImpl: false */
|
||||
}
|
||||
else {
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SubprocessImpl",
|
||||
"chrome://openpgp/content/modules/enigmailprocess_unix.jsm");
|
||||
}
|
||||
|
||||
function encodeEnvVar(name, value) {
|
||||
if (typeof name === "string" && typeof value === "string") {
|
||||
return `${name}=${value}`;
|
||||
}
|
||||
|
||||
let encoder = new TextEncoder("utf-8");
|
||||
function encode(val) {
|
||||
return typeof val === "string" ? encoder.encode(val) : val;
|
||||
}
|
||||
|
||||
return Uint8Array.of(...encode(name), ...encode("="), ...encode(value), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows for creation of and communication with OS-level sub-processes.
|
||||
* @namespace
|
||||
*/
|
||||
var SubprocessMain = {
|
||||
/**
|
||||
* Launches a process, and returns a handle to it.
|
||||
*
|
||||
* @param {object} options
|
||||
* An object describing the process to launch.
|
||||
*
|
||||
* @param {string} options.command
|
||||
* The full path of the execuable to launch. Relative paths are not
|
||||
* accepted, and `$PATH` is not searched.
|
||||
*
|
||||
* If a path search is necessary, the {@link SubprocessMain.pathSearch} method may
|
||||
* be used to map a bare executable name to a full path.
|
||||
*
|
||||
* @param {string[]} [options.arguments]
|
||||
* A list of strings to pass as arguments to the process.
|
||||
*
|
||||
* @param {object} [options.environment]
|
||||
* An object containing a key and value for each environment variable
|
||||
* to pass to the process. Only the object's own, enumerable properties
|
||||
* are added to the environment.
|
||||
*
|
||||
* @param {boolean} [options.environmentAppend]
|
||||
* If true, append the environment variables passed in `environment` to
|
||||
* the existing set of environment variables. Otherwise, the values in
|
||||
* 'environment' constitute the entire set of environment variables
|
||||
* passed to the new process.
|
||||
*
|
||||
* @param {string} [options.stderr]
|
||||
* Defines how the process's stderr output is handled. One of:
|
||||
*
|
||||
* - `"ignore"`: (default) The process's standard error is not redirected.
|
||||
* - `"stdout"`: The process's stderr is merged with its stdout.
|
||||
* - `"pipe"`: The process's stderr is redirected to a pipe, which can be read
|
||||
* from via its `stderr` property.
|
||||
*
|
||||
* @param {string} [options.workdir]
|
||||
* The working directory in which to launch the new process.
|
||||
*
|
||||
* @returns {Promise<Process>}
|
||||
*
|
||||
* @rejects {Error}
|
||||
* May be rejected with an Error object if the process can not be
|
||||
* launched. The object will include an `errorCode` property with
|
||||
* one of the following values if it was rejected for the
|
||||
* corresponding reason:
|
||||
*
|
||||
* - SubprocessMain.ERROR_BAD_EXECUTABLE: The given command could not
|
||||
* be found, or the file that it references is not executable.
|
||||
*
|
||||
* Note that if the process is successfully launched, but exits with
|
||||
* a non-zero exit code, the promise will still resolve successfully.
|
||||
*/
|
||||
call(options) {
|
||||
options = Object.assign({}, options);
|
||||
|
||||
options.stderr = options.stderr || "ignore";
|
||||
options.workdir = options.workdir || null;
|
||||
|
||||
let environment = {};
|
||||
if (!options.environment || options.environmentAppend) {
|
||||
environment = this.getEnvironment();
|
||||
}
|
||||
|
||||
if (options.environment) {
|
||||
Object.assign(environment, options.environment);
|
||||
}
|
||||
|
||||
options.environment = Object.entries(environment)
|
||||
.map(([key, val]) => encodeEnvVar(key, val));
|
||||
|
||||
options.arguments = Array.from(options.arguments || []);
|
||||
|
||||
return Promise.resolve(SubprocessImpl.isExecutableFile(options.command)).then(isExecutable => {
|
||||
if (!isExecutable) {
|
||||
let error = new Error(`File at path "${options.command}" does not exist, or is not executable`);
|
||||
error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
|
||||
throw error;
|
||||
}
|
||||
|
||||
options.arguments.unshift(options.command);
|
||||
|
||||
return SubprocessImpl.call(options);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an object with a key-value pair for every variable in the process's
|
||||
* current environment.
|
||||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
getEnvironment() {
|
||||
let environment = Object.create(null);
|
||||
for (let [k, v] of SubprocessImpl.getEnvironment()) {
|
||||
environment[k] = v;
|
||||
}
|
||||
return environment;
|
||||
},
|
||||
|
||||
/**
|
||||
* Searches for the given executable file in the system executable
|
||||
* file paths as specified by the PATH environment variable.
|
||||
*
|
||||
* On Windows, if the unadorned filename cannot be found, the
|
||||
* extensions in the semicolon-separated list in the PATHSEP
|
||||
* environment variable are successively appended to the original
|
||||
* name and searched for in turn.
|
||||
*
|
||||
* @param {string} command
|
||||
* The name of the executable to find.
|
||||
* @param {object} [environment]
|
||||
* An object containing a key for each environment variable to be used
|
||||
* in the search. If not provided, full the current process environment
|
||||
* is used.
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
pathSearch(command, environment = this.getEnvironment()) {
|
||||
let path = SubprocessImpl.pathSearch(command, environment);
|
||||
return Promise.resolve(path);
|
||||
}
|
||||
};
|
||||
|
||||
Object.assign(SubprocessMain, SubprocessConstants);
|
||||
Object.freeze(SubprocessMain);
|
|
@ -1,102 +0,0 @@
|
|||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/* exported Library, SubprocessConstants */
|
||||
|
||||
/* global ctypes: false */
|
||||
|
||||
if (!ArrayBuffer.transfer) {
|
||||
/**
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/transfer
|
||||
*
|
||||
* @param {ArrayBuffer} buffer
|
||||
* @param {integer} [size = buffer.byteLength]
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
ArrayBuffer.transfer = function(buffer, size = buffer.byteLength) {
|
||||
let u8out = new Uint8Array(size);
|
||||
let u8buffer = new Uint8Array(buffer, 0, Math.min(size, buffer.byteLength));
|
||||
|
||||
u8out.set(u8buffer);
|
||||
|
||||
return u8out.buffer;
|
||||
};
|
||||
}
|
||||
|
||||
var libraries = {};
|
||||
|
||||
class Library {
|
||||
constructor(name, names, definitions) {
|
||||
if (name in libraries) {
|
||||
return libraries[name];
|
||||
}
|
||||
|
||||
for (let name of names) {
|
||||
try {
|
||||
if (!this.library) {
|
||||
this.library = ctypes.open(name);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors until we've tried all the options.
|
||||
}
|
||||
}
|
||||
if (!this.library) {
|
||||
throw new Error("Could not load libc");
|
||||
}
|
||||
|
||||
libraries[name] = this;
|
||||
|
||||
for (let symbol of Object.keys(definitions)) {
|
||||
this.declare(symbol, ...definitions[symbol]);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
declare(name, ...args) {
|
||||
Object.defineProperty(this, name, {
|
||||
configurable: true,
|
||||
get() {
|
||||
Object.defineProperty(this, name, {
|
||||
configurable: true,
|
||||
value: this.library.declare(name, ...args)
|
||||
});
|
||||
|
||||
return this[name];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds constants which apply to various Subprocess operations.
|
||||
* @namespace
|
||||
* @lends Subprocess
|
||||
*/
|
||||
var SubprocessConstants = {
|
||||
/**
|
||||
* @property {integer} ERROR_END_OF_FILE
|
||||
* The operation failed because the end of the file was reached.
|
||||
* @constant
|
||||
*/
|
||||
ERROR_END_OF_FILE: 0xff7a0001,
|
||||
/**
|
||||
* @property {integer} ERROR_INVALID_JSON
|
||||
* The operation failed because an invalid JSON was encountered.
|
||||
* @constant
|
||||
*/
|
||||
ERROR_INVALID_JSON: 0xff7a0002,
|
||||
/**
|
||||
* @property {integer} ERROR_BAD_EXECUTABLE
|
||||
* The operation failed because the given file did not exist, or
|
||||
* could not be executed.
|
||||
* @constant
|
||||
*/
|
||||
ERROR_BAD_EXECUTABLE: 0xff7a0003
|
||||
};
|
||||
|
||||
Object.freeze(SubprocessConstants);
|
|
@ -1,169 +0,0 @@
|
|||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/* exported libc */
|
||||
|
||||
/* global ctypes: false, OS: false, Library: false */
|
||||
|
||||
const LIBC = OS.Constants.libc;
|
||||
|
||||
const LIBC_CHOICES = ["libc.so", "libSystem.B.dylib", "a.out"];
|
||||
|
||||
const unix = {
|
||||
pid_t: ctypes.int32_t,
|
||||
|
||||
pollfd: new ctypes.StructType("pollfd", [{
|
||||
"fd": ctypes.int
|
||||
}, {
|
||||
"events": ctypes.short
|
||||
}, {
|
||||
"revents": ctypes.short
|
||||
}]),
|
||||
|
||||
posix_spawn_file_actions_t: ctypes.uint8_t.array(
|
||||
LIBC.OSFILE_SIZEOF_POSIX_SPAWN_FILE_ACTIONS_T),
|
||||
|
||||
WEXITSTATUS(status) {
|
||||
return (status >> 8) & 0xff;
|
||||
},
|
||||
|
||||
WTERMSIG(status) {
|
||||
return status & 0x7f;
|
||||
}
|
||||
};
|
||||
|
||||
var libc = new Library("libc", LIBC_CHOICES, {
|
||||
environ: [ctypes.char.ptr.ptr],
|
||||
|
||||
// Darwin-only.
|
||||
_NSGetEnviron: [
|
||||
ctypes.default_abi,
|
||||
ctypes.char.ptr.ptr.ptr
|
||||
],
|
||||
|
||||
setenv: [
|
||||
ctypes.default_abi,
|
||||
ctypes.int,
|
||||
ctypes.char.ptr,
|
||||
ctypes.char.ptr,
|
||||
ctypes.int
|
||||
],
|
||||
|
||||
chdir: [
|
||||
ctypes.default_abi,
|
||||
ctypes.int,
|
||||
ctypes.char.ptr /* path */
|
||||
],
|
||||
|
||||
close: [
|
||||
ctypes.default_abi,
|
||||
ctypes.int,
|
||||
ctypes.int /* fildes */
|
||||
],
|
||||
|
||||
fcntl: [
|
||||
ctypes.default_abi,
|
||||
ctypes.int,
|
||||
ctypes.int, /* fildes */
|
||||
ctypes.int, /* cmd */
|
||||
ctypes.int /* ... */
|
||||
],
|
||||
|
||||
getcwd: [
|
||||
ctypes.default_abi,
|
||||
ctypes.char.ptr,
|
||||
ctypes.char.ptr, /* buf */
|
||||
ctypes.size_t /* size */
|
||||
],
|
||||
|
||||
kill: [
|
||||
ctypes.default_abi,
|
||||
ctypes.int,
|
||||
unix.pid_t, /* pid */
|
||||
ctypes.int /* signal */
|
||||
],
|
||||
|
||||
pipe: [
|
||||
ctypes.default_abi,
|
||||
ctypes.int,
|
||||
ctypes.int.array(2) /* pipefd */
|
||||
],
|
||||
|
||||
poll: [
|
||||
ctypes.default_abi,
|
||||
ctypes.int,
|
||||
unix.pollfd.array(), /* fds */
|
||||
ctypes.unsigned_int, /* nfds */
|
||||
ctypes.int /* timeout */
|
||||
],
|
||||
|
||||
posix_spawn: [
|
||||
ctypes.default_abi,
|
||||
ctypes.int,
|
||||
unix.pid_t.ptr, /* pid */
|
||||
ctypes.char.ptr, /* path */
|
||||
unix.posix_spawn_file_actions_t.ptr, /* file_actions */
|
||||
ctypes.voidptr_t, /* attrp */
|
||||
ctypes.char.ptr.ptr, /* argv */
|
||||
ctypes.char.ptr.ptr /* envp */
|
||||
],
|
||||
|
||||
posix_spawn_file_actions_addclose: [
|
||||
ctypes.default_abi,
|
||||
ctypes.int,
|
||||
unix.posix_spawn_file_actions_t.ptr, /* file_actions */
|
||||
ctypes.int /* fildes */
|
||||
],
|
||||
|
||||
posix_spawn_file_actions_adddup2: [
|
||||
ctypes.default_abi,
|
||||
ctypes.int,
|
||||
unix.posix_spawn_file_actions_t.ptr, /* file_actions */
|
||||
ctypes.int, /* fildes */
|
||||
ctypes.int /* newfildes */
|
||||
],
|
||||
|
||||
posix_spawn_file_actions_destroy: [
|
||||
ctypes.default_abi,
|
||||
ctypes.int,
|
||||
unix.posix_spawn_file_actions_t.ptr /* file_actions */
|
||||
],
|
||||
|
||||
posix_spawn_file_actions_init: [
|
||||
ctypes.default_abi,
|
||||
ctypes.int,
|
||||
unix.posix_spawn_file_actions_t.ptr /* file_actions */
|
||||
],
|
||||
|
||||
read: [
|
||||
ctypes.default_abi,
|
||||
ctypes.ssize_t,
|
||||
ctypes.int, /* fildes */
|
||||
ctypes.char.ptr, /* buf */
|
||||
ctypes.size_t /* nbyte */
|
||||
],
|
||||
|
||||
waitpid: [
|
||||
ctypes.default_abi,
|
||||
unix.pid_t,
|
||||
unix.pid_t, /* pid */
|
||||
ctypes.int.ptr, /* status */
|
||||
ctypes.int /* options */
|
||||
],
|
||||
|
||||
write: [
|
||||
ctypes.default_abi,
|
||||
ctypes.ssize_t,
|
||||
ctypes.int, /* fildes */
|
||||
ctypes.char.ptr, /* buf */
|
||||
ctypes.size_t /* nbyte */
|
||||
]
|
||||
});
|
||||
|
||||
unix.Fd = function(fd) {
|
||||
return ctypes.CDataFinalizer(ctypes.int(fd), libc.close);
|
||||
};
|
|
@ -1,581 +0,0 @@
|
|||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/* exported LIBC, Win, createPipe, libc */
|
||||
|
||||
/* global ctypes: false, OS: false, Library: false */
|
||||
/* eslint no-void: 0 */
|
||||
|
||||
const LIBC = OS.Constants.libc;
|
||||
|
||||
const Win = OS.Constants.Win;
|
||||
|
||||
const LIBC_CHOICES = ["kernel32.dll"];
|
||||
|
||||
var win32 = {
|
||||
// On Windows 64, winapi_abi is an alias for default_abi.
|
||||
WINAPI: ctypes.winapi_abi,
|
||||
|
||||
VOID: ctypes.void_t,
|
||||
|
||||
BYTE: ctypes.uint8_t,
|
||||
WORD: ctypes.uint16_t,
|
||||
DWORD: ctypes.uint32_t,
|
||||
LONG: ctypes.long,
|
||||
LARGE_INTEGER: ctypes.int64_t,
|
||||
ULONGLONG: ctypes.uint64_t,
|
||||
|
||||
UINT: ctypes.unsigned_int,
|
||||
UCHAR: ctypes.unsigned_char,
|
||||
|
||||
BOOL: ctypes.bool,
|
||||
|
||||
HANDLE: ctypes.voidptr_t,
|
||||
PVOID: ctypes.voidptr_t,
|
||||
LPVOID: ctypes.voidptr_t,
|
||||
|
||||
CHAR: ctypes.char,
|
||||
WCHAR: ctypes.jschar,
|
||||
|
||||
ULONG_PTR: ctypes.uintptr_t,
|
||||
|
||||
SIZE_T: ctypes.size_t,
|
||||
PSIZE_T: ctypes.size_t.ptr
|
||||
};
|
||||
|
||||
Object.assign(win32, {
|
||||
DWORD_PTR: win32.ULONG_PTR,
|
||||
|
||||
LPSTR: win32.CHAR.ptr,
|
||||
LPWSTR: win32.WCHAR.ptr,
|
||||
|
||||
LPBYTE: win32.BYTE.ptr,
|
||||
LPDWORD: win32.DWORD.ptr,
|
||||
LPHANDLE: win32.HANDLE.ptr,
|
||||
|
||||
// This is an opaque type.
|
||||
PROC_THREAD_ATTRIBUTE_LIST: ctypes.char.array(),
|
||||
LPPROC_THREAD_ATTRIBUTE_LIST: ctypes.char.ptr
|
||||
});
|
||||
|
||||
Object.assign(win32, {
|
||||
LPCSTR: win32.LPSTR,
|
||||
LPCWSTR: win32.LPWSTR,
|
||||
LPCVOID: win32.LPVOID
|
||||
});
|
||||
|
||||
Object.assign(win32, {
|
||||
INVALID_HANDLE_VALUE: ctypes.cast(ctypes.int64_t(-1), win32.HANDLE),
|
||||
NULL_HANDLE_VALUE: ctypes.cast(ctypes.uintptr_t(0), win32.HANDLE),
|
||||
CREATE_SUSPENDED: 0x00000004,
|
||||
CREATE_NEW_CONSOLE: 0x00000010,
|
||||
CREATE_UNICODE_ENVIRONMENT: 0x00000400,
|
||||
CREATE_NO_WINDOW: 0x08000000,
|
||||
CREATE_BREAKAWAY_FROM_JOB: 0x01000000,
|
||||
EXTENDED_STARTUPINFO_PRESENT: 0x00080000,
|
||||
|
||||
STARTF_USESTDHANDLES: 0x0100,
|
||||
|
||||
DUPLICATE_CLOSE_SOURCE: 0x01,
|
||||
DUPLICATE_SAME_ACCESS: 0x02,
|
||||
|
||||
ERROR_HANDLE_EOF: 38,
|
||||
ERROR_BROKEN_PIPE: 109,
|
||||
ERROR_INSUFFICIENT_BUFFER: 122,
|
||||
|
||||
FILE_FLAG_OVERLAPPED: 0x40000000,
|
||||
|
||||
PIPE_TYPE_BYTE: 0x00,
|
||||
|
||||
PIPE_ACCESS_INBOUND: 0x01,
|
||||
PIPE_ACCESS_OUTBOUND: 0x02,
|
||||
PIPE_ACCESS_DUPLEX: 0x03,
|
||||
|
||||
PIPE_WAIT: 0x00,
|
||||
PIPE_NOWAIT: 0x01,
|
||||
|
||||
STILL_ACTIVE: 259,
|
||||
|
||||
PROC_THREAD_ATTRIBUTE_HANDLE_LIST: 0x00020002,
|
||||
|
||||
JobObjectBasicLimitInformation: 2,
|
||||
JobObjectExtendedLimitInformation: 9,
|
||||
|
||||
JOB_OBJECT_LIMIT_BREAKAWAY_OK: 0x00000800,
|
||||
|
||||
// These constants are 32-bit unsigned integers, but Windows defines
|
||||
// them as negative integers cast to an unsigned type.
|
||||
STD_INPUT_HANDLE: -10 + 0x100000000,
|
||||
STD_OUTPUT_HANDLE: -11 + 0x100000000,
|
||||
STD_ERROR_HANDLE: -12 + 0x100000000,
|
||||
|
||||
WAIT_TIMEOUT: 0x00000102,
|
||||
WAIT_FAILED: 0xffffffff
|
||||
});
|
||||
|
||||
Object.assign(win32, {
|
||||
JOBOBJECT_BASIC_LIMIT_INFORMATION: new ctypes.StructType("JOBOBJECT_BASIC_LIMIT_INFORMATION", [{
|
||||
"PerProcessUserTimeLimit": win32.LARGE_INTEGER
|
||||
}, {
|
||||
"PerJobUserTimeLimit": win32.LARGE_INTEGER
|
||||
}, {
|
||||
"LimitFlags": win32.DWORD
|
||||
}, {
|
||||
"MinimumWorkingSetSize": win32.SIZE_T
|
||||
}, {
|
||||
"MaximumWorkingSetSize": win32.SIZE_T
|
||||
}, {
|
||||
"ActiveProcessLimit": win32.DWORD
|
||||
}, {
|
||||
"Affinity": win32.ULONG_PTR
|
||||
}, {
|
||||
"PriorityClass": win32.DWORD
|
||||
}, {
|
||||
"SchedulingClass": win32.DWORD
|
||||
}]),
|
||||
|
||||
IO_COUNTERS: new ctypes.StructType("IO_COUNTERS", [{
|
||||
"ReadOperationCount": win32.ULONGLONG
|
||||
}, {
|
||||
"WriteOperationCount": win32.ULONGLONG
|
||||
}, {
|
||||
"OtherOperationCount": win32.ULONGLONG
|
||||
}, {
|
||||
"ReadTransferCount": win32.ULONGLONG
|
||||
}, {
|
||||
"WriteTransferCount": win32.ULONGLONG
|
||||
}, {
|
||||
"OtherTransferCount": win32.ULONGLONG
|
||||
}])
|
||||
});
|
||||
|
||||
Object.assign(win32, {
|
||||
JOBOBJECT_EXTENDED_LIMIT_INFORMATION: new ctypes.StructType("JOBOBJECT_EXTENDED_LIMIT_INFORMATION", [{
|
||||
"BasicLimitInformation": win32.JOBOBJECT_BASIC_LIMIT_INFORMATION
|
||||
}, {
|
||||
"IoInfo": win32.IO_COUNTERS
|
||||
}, {
|
||||
"ProcessMemoryLimit": win32.SIZE_T
|
||||
}, {
|
||||
"JobMemoryLimit": win32.SIZE_T
|
||||
}, {
|
||||
"PeakProcessMemoryUsed": win32.SIZE_T
|
||||
}, {
|
||||
"PeakJobMemoryUsed": win32.SIZE_T
|
||||
}]),
|
||||
|
||||
OVERLAPPED: new ctypes.StructType("OVERLAPPED", [{
|
||||
"Internal": win32.ULONG_PTR
|
||||
}, {
|
||||
"InternalHigh": win32.ULONG_PTR
|
||||
}, {
|
||||
"Offset": win32.DWORD
|
||||
}, {
|
||||
"OffsetHigh": win32.DWORD
|
||||
}, {
|
||||
"hEvent": win32.HANDLE
|
||||
}]),
|
||||
|
||||
PROCESS_INFORMATION: new ctypes.StructType("PROCESS_INFORMATION", [{
|
||||
"hProcess": win32.HANDLE
|
||||
}, {
|
||||
"hThread": win32.HANDLE
|
||||
}, {
|
||||
"dwProcessId": win32.DWORD
|
||||
}, {
|
||||
"dwThreadId": win32.DWORD
|
||||
}]),
|
||||
|
||||
SECURITY_ATTRIBUTES: new ctypes.StructType("SECURITY_ATTRIBUTES", [{
|
||||
"nLength": win32.DWORD
|
||||
}, {
|
||||
"lpSecurityDescriptor": win32.LPVOID
|
||||
}, {
|
||||
"bInheritHandle": win32.BOOL
|
||||
}]),
|
||||
|
||||
STARTUPINFOW: new ctypes.StructType("STARTUPINFOW", [{
|
||||
"cb": win32.DWORD
|
||||
}, {
|
||||
"lpReserved": win32.LPWSTR
|
||||
}, {
|
||||
"lpDesktop": win32.LPWSTR
|
||||
}, {
|
||||
"lpTitle": win32.LPWSTR
|
||||
}, {
|
||||
"dwX": win32.DWORD
|
||||
}, {
|
||||
"dwY": win32.DWORD
|
||||
}, {
|
||||
"dwXSize": win32.DWORD
|
||||
}, {
|
||||
"dwYSize": win32.DWORD
|
||||
}, {
|
||||
"dwXCountChars": win32.DWORD
|
||||
}, {
|
||||
"dwYCountChars": win32.DWORD
|
||||
}, {
|
||||
"dwFillAttribute": win32.DWORD
|
||||
}, {
|
||||
"dwFlags": win32.DWORD
|
||||
}, {
|
||||
"wShowWindow": win32.WORD
|
||||
}, {
|
||||
"cbReserved2": win32.WORD
|
||||
}, {
|
||||
"lpReserved2": win32.LPBYTE
|
||||
}, {
|
||||
"hStdInput": win32.HANDLE
|
||||
}, {
|
||||
"hStdOutput": win32.HANDLE
|
||||
}, {
|
||||
"hStdError": win32.HANDLE
|
||||
}])
|
||||
});
|
||||
|
||||
Object.assign(win32, {
|
||||
STARTUPINFOEXW: new ctypes.StructType("STARTUPINFOEXW", [{
|
||||
"StartupInfo": win32.STARTUPINFOW
|
||||
}, {
|
||||
"lpAttributeList": win32.LPPROC_THREAD_ATTRIBUTE_LIST
|
||||
}])
|
||||
});
|
||||
|
||||
|
||||
var libc = new Library("libc", LIBC_CHOICES, {
|
||||
AssignProcessToJobObject: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.HANDLE, /* hJob */
|
||||
win32.HANDLE /* hProcess */
|
||||
],
|
||||
|
||||
CloseHandle: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.HANDLE /* hObject */
|
||||
],
|
||||
|
||||
CreateEventW: [
|
||||
win32.WINAPI,
|
||||
win32.HANDLE,
|
||||
win32.SECURITY_ATTRIBUTES.ptr, /* opt lpEventAttributes */
|
||||
win32.BOOL, /* bManualReset */
|
||||
win32.BOOL, /* bInitialState */
|
||||
win32.LPWSTR /* lpName */
|
||||
],
|
||||
|
||||
CreateFileW: [
|
||||
win32.WINAPI,
|
||||
win32.HANDLE,
|
||||
win32.LPWSTR, /* lpFileName */
|
||||
win32.DWORD, /* dwDesiredAccess */
|
||||
win32.DWORD, /* dwShareMode */
|
||||
win32.SECURITY_ATTRIBUTES.ptr, /* opt lpSecurityAttributes */
|
||||
win32.DWORD, /* dwCreationDisposition */
|
||||
win32.DWORD, /* dwFlagsAndAttributes */
|
||||
win32.HANDLE /* opt hTemplateFile */
|
||||
],
|
||||
|
||||
CreateJobObjectW: [
|
||||
win32.WINAPI,
|
||||
win32.HANDLE,
|
||||
win32.SECURITY_ATTRIBUTES.ptr, /* opt lpJobAttributes */
|
||||
win32.LPWSTR /* lpName */
|
||||
],
|
||||
|
||||
CreateNamedPipeW: [
|
||||
win32.WINAPI,
|
||||
win32.HANDLE,
|
||||
win32.LPWSTR, /* lpName */
|
||||
win32.DWORD, /* dwOpenMode */
|
||||
win32.DWORD, /* dwPipeMode */
|
||||
win32.DWORD, /* nMaxInstances */
|
||||
win32.DWORD, /* nOutBufferSize */
|
||||
win32.DWORD, /* nInBufferSize */
|
||||
win32.DWORD, /* nDefaultTimeOut */
|
||||
win32.SECURITY_ATTRIBUTES.ptr /* opt lpSecurityAttributes */
|
||||
],
|
||||
|
||||
CreatePipe: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.LPHANDLE, /* out hReadPipe */
|
||||
win32.LPHANDLE, /* out hWritePipe */
|
||||
win32.SECURITY_ATTRIBUTES.ptr, /* opt lpPipeAttributes */
|
||||
win32.DWORD /* nSize */
|
||||
],
|
||||
|
||||
CreateProcessW: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.LPCWSTR, /* lpApplicationName */
|
||||
win32.LPWSTR, /* lpCommandLine */
|
||||
win32.SECURITY_ATTRIBUTES.ptr, /* lpProcessAttributes */
|
||||
win32.SECURITY_ATTRIBUTES.ptr, /* lpThreadAttributes */
|
||||
win32.BOOL, /* bInheritHandle */
|
||||
win32.DWORD, /* dwCreationFlags */
|
||||
win32.LPVOID, /* opt lpEnvironment */
|
||||
win32.LPCWSTR, /* opt lpCurrentDirectory */
|
||||
win32.STARTUPINFOW.ptr, /* lpStartupInfo */
|
||||
win32.PROCESS_INFORMATION.ptr /* out lpProcessInformation */
|
||||
],
|
||||
|
||||
CreateSemaphoreW: [
|
||||
win32.WINAPI,
|
||||
win32.HANDLE,
|
||||
win32.SECURITY_ATTRIBUTES.ptr, /* opt lpSemaphoreAttributes */
|
||||
win32.LONG, /* lInitialCount */
|
||||
win32.LONG, /* lMaximumCount */
|
||||
win32.LPCWSTR /* opt lpName */
|
||||
],
|
||||
|
||||
DeleteProcThreadAttributeList: [
|
||||
win32.WINAPI,
|
||||
win32.VOID,
|
||||
win32.LPPROC_THREAD_ATTRIBUTE_LIST /* in/out lpAttributeList */
|
||||
],
|
||||
|
||||
DuplicateHandle: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.HANDLE, /* hSourceProcessHandle */
|
||||
win32.HANDLE, /* hSourceHandle */
|
||||
win32.HANDLE, /* hTargetProcessHandle */
|
||||
win32.LPHANDLE, /* out lpTargetHandle */
|
||||
win32.DWORD, /* dwDesiredAccess */
|
||||
win32.BOOL, /* bInheritHandle */
|
||||
win32.DWORD /* dwOptions */
|
||||
],
|
||||
|
||||
FreeEnvironmentStringsW: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.LPCWSTR /* lpszEnvironmentBlock */
|
||||
],
|
||||
|
||||
GetCurrentProcess: [
|
||||
win32.WINAPI,
|
||||
win32.HANDLE
|
||||
],
|
||||
|
||||
GetCurrentProcessId: [
|
||||
win32.WINAPI,
|
||||
win32.DWORD
|
||||
],
|
||||
|
||||
GetEnvironmentStringsW: [
|
||||
win32.WINAPI,
|
||||
win32.LPCWSTR
|
||||
],
|
||||
|
||||
GetExitCodeProcess: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.HANDLE, /* hProcess */
|
||||
win32.LPDWORD /* lpExitCode */
|
||||
],
|
||||
|
||||
GetOverlappedResult: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.HANDLE, /* hFile */
|
||||
win32.OVERLAPPED.ptr, /* lpOverlapped */
|
||||
win32.LPDWORD, /* lpNumberOfBytesTransferred */
|
||||
win32.BOOL /* bWait */
|
||||
],
|
||||
|
||||
GetStdHandle: [
|
||||
win32.WINAPI,
|
||||
win32.HANDLE,
|
||||
win32.DWORD /* nStdHandle */
|
||||
],
|
||||
|
||||
InitializeProcThreadAttributeList: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.LPPROC_THREAD_ATTRIBUTE_LIST, /* out opt lpAttributeList */
|
||||
win32.DWORD, /* dwAttributeCount */
|
||||
win32.DWORD, /* dwFlags */
|
||||
win32.PSIZE_T /* in/out lpSize */
|
||||
],
|
||||
|
||||
ReadFile: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.HANDLE, /* hFile */
|
||||
win32.LPVOID, /* out lpBuffer */
|
||||
win32.DWORD, /* nNumberOfBytesToRead */
|
||||
win32.LPDWORD, /* opt out lpNumberOfBytesRead */
|
||||
win32.OVERLAPPED.ptr /* opt in/out lpOverlapped */
|
||||
],
|
||||
|
||||
ReleaseSemaphore: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.HANDLE, /* hSemaphore */
|
||||
win32.LONG, /* lReleaseCount */
|
||||
win32.LONG.ptr /* opt out lpPreviousCount */
|
||||
],
|
||||
|
||||
ResumeThread: [
|
||||
win32.WINAPI,
|
||||
win32.DWORD,
|
||||
win32.HANDLE /* hThread */
|
||||
],
|
||||
|
||||
SetInformationJobObject: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.HANDLE, /* hJob */
|
||||
ctypes.int, /* JobObjectInfoClass */
|
||||
win32.LPVOID, /* lpJobObjectInfo */
|
||||
win32.DWORD /* cbJobObjectInfoLengt */
|
||||
],
|
||||
|
||||
TerminateJobObject: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.HANDLE, /* hJob */
|
||||
win32.UINT /* uExitCode */
|
||||
],
|
||||
|
||||
TerminateProcess: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.HANDLE, /* hProcess */
|
||||
win32.UINT /* uExitCode */
|
||||
],
|
||||
|
||||
UpdateProcThreadAttribute: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.LPPROC_THREAD_ATTRIBUTE_LIST, /* in/out lpAttributeList */
|
||||
win32.DWORD, /* dwFlags */
|
||||
win32.DWORD_PTR, /* Attribute */
|
||||
win32.PVOID, /* lpValue */
|
||||
win32.SIZE_T, /* cbSize */
|
||||
win32.PVOID, /* out opt lpPreviousValue */
|
||||
win32.PSIZE_T /* opt lpReturnSize */
|
||||
],
|
||||
|
||||
WaitForMultipleObjects: [
|
||||
win32.WINAPI,
|
||||
win32.DWORD,
|
||||
win32.DWORD, /* nCount */
|
||||
win32.HANDLE.ptr, /* hHandles */
|
||||
win32.BOOL, /* bWaitAll */
|
||||
win32.DWORD /* dwMilliseconds */
|
||||
],
|
||||
|
||||
WaitForSingleObject: [
|
||||
win32.WINAPI,
|
||||
win32.DWORD,
|
||||
win32.HANDLE, /* hHandle */
|
||||
win32.BOOL, /* bWaitAll */
|
||||
win32.DWORD /* dwMilliseconds */
|
||||
],
|
||||
|
||||
WriteFile: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.HANDLE, /* hFile */
|
||||
win32.LPCVOID, /* lpBuffer */
|
||||
win32.DWORD, /* nNumberOfBytesToRead */
|
||||
win32.LPDWORD, /* opt out lpNumberOfBytesWritten */
|
||||
win32.OVERLAPPED.ptr /* opt in/out lpOverlapped */
|
||||
]
|
||||
});
|
||||
|
||||
var user32 = new Library("user32", ["user32.dll"], {
|
||||
AllowSetForegroundWindow: [
|
||||
win32.WINAPI,
|
||||
win32.BOOL,
|
||||
win32.DWORD /* dwProcessId */
|
||||
]
|
||||
});
|
||||
|
||||
let nextNamedPipeId = 0;
|
||||
|
||||
win32.Handle = function(handle) {
|
||||
return ctypes.CDataFinalizer(win32.HANDLE(handle), libc.CloseHandle);
|
||||
};
|
||||
|
||||
win32.createPipe = function(secAttr, readFlags = 0, writeFlags = 0, size = 0) {
|
||||
readFlags |= win32.PIPE_ACCESS_INBOUND;
|
||||
writeFlags |= Win.FILE_ATTRIBUTE_NORMAL;
|
||||
|
||||
if (size == 0) {
|
||||
size = 4096;
|
||||
}
|
||||
|
||||
let pid = libc.GetCurrentProcessId();
|
||||
const pipePrefix = "\\\\.\\Pipe\\SubProcessPipe";
|
||||
let pipeName = String.raw `${pipePrefix}.${pid}.${nextNamedPipeId++}`;
|
||||
|
||||
let readHandle = libc.CreateNamedPipeW(
|
||||
pipeName, readFlags,
|
||||
win32.PIPE_TYPE_BYTE | win32.PIPE_WAIT,
|
||||
1, /* number of connections */
|
||||
size, /* output buffer size */
|
||||
size, /* input buffer size */
|
||||
0, /* timeout */
|
||||
secAttr.address());
|
||||
|
||||
let isInvalid = handle => String(handle) == String(win32.HANDLE(Win.INVALID_HANDLE_VALUE));
|
||||
|
||||
if (isInvalid(readHandle)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let writeHandle = libc.CreateFileW(
|
||||
pipeName, Win.GENERIC_WRITE, 0, secAttr.address(),
|
||||
Win.OPEN_EXISTING, writeFlags, null);
|
||||
|
||||
if (isInvalid(writeHandle)) {
|
||||
libc.CloseHandle(readHandle);
|
||||
return [];
|
||||
}
|
||||
|
||||
return [win32.Handle(readHandle),
|
||||
win32.Handle(writeHandle)
|
||||
];
|
||||
};
|
||||
|
||||
win32.createThreadAttributeList = function(handles) {
|
||||
try {
|
||||
void libc.InitializeProcThreadAttributeList;
|
||||
void libc.DeleteProcThreadAttributeList;
|
||||
void libc.UpdateProcThreadAttribute;
|
||||
} catch (e) {
|
||||
// This is only supported in Windows Vista and later.
|
||||
return null;
|
||||
}
|
||||
|
||||
let size = win32.SIZE_T();
|
||||
if (!libc.InitializeProcThreadAttributeList(null, 1, 0, size.address()) &&
|
||||
ctypes.winLastError != win32.ERROR_INSUFFICIENT_BUFFER) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let attrList = win32.PROC_THREAD_ATTRIBUTE_LIST(size.value);
|
||||
|
||||
if (!libc.InitializeProcThreadAttributeList(attrList, 1, 0, size.address())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let ok = libc.UpdateProcThreadAttribute(
|
||||
attrList, 0, win32.PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
|
||||
handles, handles.constructor.size, null, null);
|
||||
|
||||
if (!ok) {
|
||||
libc.DeleteProcThreadAttributeList(attrList);
|
||||
return null;
|
||||
}
|
||||
|
||||
return attrList;
|
||||
};
|
|
@ -1,209 +0,0 @@
|
|||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
|
||||
/* exported SubprocessImpl */
|
||||
|
||||
/* globals BaseProcess, PromiseWorker */
|
||||
|
||||
/* global libc: false, LIBC: false */
|
||||
|
||||
var {
|
||||
results: Cr
|
||||
} = Components;
|
||||
|
||||
var EXPORTED_SYMBOLS = ["SubprocessImpl"];
|
||||
|
||||
Components.utils.importGlobalProperties(["TextDecoder"]);
|
||||
const ctypes = ChromeUtils.import("resource://gre/modules/ctypes.jsm").ctypes;
|
||||
const OS = ChromeUtils.import("resource://gre/modules/osfile.jsm").OS;
|
||||
const Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
|
||||
var {
|
||||
SubprocessConstants,
|
||||
BaseProcess,
|
||||
PromiseWorker
|
||||
} = ChromeUtils.import("chrome://openpgp/content/modules/enigmailprocess_common.jsm", this);
|
||||
|
||||
Services.scriptloader.loadSubScript("chrome://openpgp/content/modules/enigmailprocess_shared.js", this);
|
||||
Services.scriptloader.loadSubScript("chrome://openpgp/content/modules/enigmailprocess_shared_unix.js", this);
|
||||
|
||||
class UnixPromiseWorker extends PromiseWorker {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
let fds = ctypes.int.array(2)();
|
||||
let res = libc.pipe(fds);
|
||||
if (res == -1) {
|
||||
throw new Error("Unable to create pipe");
|
||||
}
|
||||
|
||||
this.signalFd = fds[1];
|
||||
|
||||
libc.fcntl(fds[0], LIBC.F_SETFL, LIBC.O_NONBLOCK);
|
||||
libc.fcntl(fds[0], LIBC.F_SETFD, LIBC.FD_CLOEXEC);
|
||||
libc.fcntl(fds[1], LIBC.F_SETFD, LIBC.FD_CLOEXEC);
|
||||
|
||||
this.call("init", [{
|
||||
signalFd: fds[0]
|
||||
}]);
|
||||
}
|
||||
|
||||
closePipe() {
|
||||
if (this.signalFd) {
|
||||
libc.close(this.signalFd);
|
||||
this.signalFd = null;
|
||||
}
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.closePipe();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
signalWorker() {
|
||||
libc.write(this.signalFd, new ArrayBuffer(1), 1);
|
||||
}
|
||||
|
||||
postMessage(...args) {
|
||||
this.signalWorker();
|
||||
return super.postMessage(...args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Process extends BaseProcess {
|
||||
static get WORKER_URL() {
|
||||
return "chrome://openpgp/content/modules/enigmailprocess_worker_unix.js";
|
||||
}
|
||||
|
||||
static get WorkerClass() {
|
||||
return UnixPromiseWorker;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a null-terminated char pointer into a sized char array, and then
|
||||
// convert that into a JS typed array.
|
||||
// The resulting array will not be null-terminated.
|
||||
function ptrToUint8Array(input) {
|
||||
let {
|
||||
cast,
|
||||
uint8_t
|
||||
} = ctypes;
|
||||
|
||||
let len = 0;
|
||||
for (let ptr = cast(input, uint8_t.ptr); ptr.contents; ptr = ptr.increment()) {
|
||||
len++;
|
||||
}
|
||||
|
||||
let aryPtr = cast(input, uint8_t.array(len).ptr);
|
||||
return new Uint8Array(aryPtr.contents);
|
||||
}
|
||||
|
||||
var SubprocessUnix = {
|
||||
Process,
|
||||
|
||||
call(options) {
|
||||
return Process.create(options);
|
||||
},
|
||||
|
||||
* getEnvironment() {
|
||||
let environ;
|
||||
if (OS.Constants.Sys.Name == "Darwin") {
|
||||
environ = libc._NSGetEnviron().contents;
|
||||
} else {
|
||||
environ = libc.environ;
|
||||
}
|
||||
|
||||
const EQUAL = "=".charCodeAt(0);
|
||||
let decoder = new TextDecoder("utf-8", {
|
||||
fatal: true
|
||||
});
|
||||
|
||||
function decode(array) {
|
||||
try {
|
||||
return decoder.decode(array);
|
||||
} catch (e) {
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
for (let envp = environ; !envp.contents.isNull(); envp = envp.increment()) {
|
||||
let buf = ptrToUint8Array(envp.contents);
|
||||
|
||||
for (let i = 0; i < buf.length; i++) {
|
||||
if (buf[i] == EQUAL) {
|
||||
yield [decode(buf.subarray(0, i)),
|
||||
decode(buf.subarray(i + 1))
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async isExecutableFile(path) {
|
||||
if (!OS.Path.split(path).absolute) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
let info = await OS.File.stat(path);
|
||||
|
||||
// FIXME: We really want access(path, X_OK) here, but OS.File does not
|
||||
// support it.
|
||||
return !info.isDir && (info.unixMode & 0x49);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Searches for the given executable file in the system executable
|
||||
* file paths as specified by the PATH environment variable.
|
||||
*
|
||||
* On Windows, if the unadorned filename cannot be found, the
|
||||
* extensions in the semicolon-separated list in the PATHEXT
|
||||
* environment variable are successively appended to the original
|
||||
* name and searched for in turn.
|
||||
*
|
||||
* @param {string} bin
|
||||
* The name of the executable to find.
|
||||
* @param {object} environment
|
||||
* An object containing a key for each environment variable to be used
|
||||
* in the search.
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async pathSearch(bin, environment) {
|
||||
let split = OS.Path.split(bin);
|
||||
if (split.absolute) {
|
||||
if (await this.isExecutableFile(bin)) {
|
||||
return bin;
|
||||
}
|
||||
let error = new Error(`File at path "${bin}" does not exist, or is not executable`);
|
||||
error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
|
||||
throw error;
|
||||
}
|
||||
|
||||
let dirs = [];
|
||||
if (typeof environment.PATH === "string") {
|
||||
dirs = environment.PATH.split(":");
|
||||
}
|
||||
|
||||
for (let dir of dirs) {
|
||||
let path = OS.Path.join(dir, bin);
|
||||
|
||||
if (await this.isExecutableFile(path)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
let error = new Error(`Executable not found: ${bin}`);
|
||||
error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
var SubprocessImpl = SubprocessUnix;
|
|
@ -1,176 +0,0 @@
|
|||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/* exported SubprocessImpl */
|
||||
|
||||
/* global libc: false */
|
||||
|
||||
/* globals BaseProcess, PromiseWorker */
|
||||
|
||||
var {
|
||||
results: Cr
|
||||
} = Components;
|
||||
|
||||
var EXPORTED_SYMBOLS = ["SubprocessImpl"];
|
||||
|
||||
const AppConstants = ChromeUtils.import("resource://gre/modules/AppConstants.jsm").AppConstants;
|
||||
const ctypes = ChromeUtils.import("resource://gre/modules/ctypes.jsm").ctypes;
|
||||
const OS = ChromeUtils.import("resource://gre/modules/osfile.jsm").OS;
|
||||
const Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
|
||||
const XPCOMUtils = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
|
||||
var {
|
||||
SubprocessConstants,
|
||||
BaseProcess,
|
||||
PromiseWorker
|
||||
} = ChromeUtils.import("chrome://openpgp/content/modules/enigmailprocess_common.jsm", this);
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "env", "@mozilla.org/process/environment;1",
|
||||
"nsIEnvironment"); /* global env: false */
|
||||
|
||||
Services.scriptloader.loadSubScript("chrome://openpgp/content/modules/enigmailprocess_shared.js", this);
|
||||
Services.scriptloader.loadSubScript("chrome://openpgp/content/modules/enigmailprocess_shared_win.js", this);
|
||||
|
||||
class WinPromiseWorker extends PromiseWorker {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.signalEvent = libc.CreateSemaphoreW(null, 0, 32, null);
|
||||
|
||||
this.call("init", [{
|
||||
breakAwayFromJob: !AppConstants.isPlatformAndVersionAtLeast("win", "6.2"),
|
||||
comspec: env.get("COMSPEC"),
|
||||
signalEvent: String(ctypes.cast(this.signalEvent, ctypes.uintptr_t).value)
|
||||
}]);
|
||||
}
|
||||
|
||||
signalWorker() {
|
||||
libc.ReleaseSemaphore(this.signalEvent, 1, null);
|
||||
}
|
||||
|
||||
postMessage(...args) {
|
||||
this.signalWorker();
|
||||
return super.postMessage(...args);
|
||||
}
|
||||
}
|
||||
|
||||
class Process extends BaseProcess {
|
||||
static get WORKER_URL() {
|
||||
return "chrome://openpgp/content/modules/enigmailprocess_worker_win.js";
|
||||
}
|
||||
|
||||
static get WorkerClass() {
|
||||
return WinPromiseWorker;
|
||||
}
|
||||
}
|
||||
|
||||
var SubprocessWin = {
|
||||
Process,
|
||||
|
||||
call(options) {
|
||||
return Process.create(options);
|
||||
},
|
||||
|
||||
* getEnvironment() {
|
||||
let env = libc.GetEnvironmentStringsW();
|
||||
try {
|
||||
for (let p = env, q = env;; p = p.increment()) {
|
||||
if (p.contents == "\0") {
|
||||
if (String(p) == String(q)) {
|
||||
break;
|
||||
}
|
||||
|
||||
let str = q.readString();
|
||||
q = p.increment();
|
||||
|
||||
let idx = str.indexOf("=");
|
||||
if (idx == 0) {
|
||||
idx = str.indexOf("=", 1);
|
||||
}
|
||||
|
||||
if (idx >= 0) {
|
||||
yield [str.slice(0, idx), str.slice(idx + 1)];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
libc.FreeEnvironmentStringsW(env);
|
||||
}
|
||||
},
|
||||
|
||||
async isExecutableFile(path) {
|
||||
if (!OS.Path.split(path).absolute) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
let info = await OS.File.stat(path);
|
||||
return !(info.isDir || info.isSymlink);
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Searches for the given executable file in the system executable
|
||||
* file paths as specified by the PATH environment variable.
|
||||
*
|
||||
* On Windows, if the unadorned filename cannot be found, the
|
||||
* extensions in the semicolon-separated list in the PATHEXT
|
||||
* environment variable are successively appended to the original
|
||||
* name and searched for in turn.
|
||||
*
|
||||
* @param {string} bin
|
||||
* The name of the executable to find.
|
||||
* @param {object} environment
|
||||
* An object containing a key for each environment variable to be used
|
||||
* in the search.
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async pathSearch(bin, environment) {
|
||||
let split = OS.Path.split(bin);
|
||||
if (split.absolute) {
|
||||
if (await this.isExecutableFile(bin)) {
|
||||
return bin;
|
||||
}
|
||||
let error = new Error(`File at path "${bin}" does not exist, or is not a normal file`);
|
||||
error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
|
||||
throw error;
|
||||
}
|
||||
|
||||
let dirs = [];
|
||||
let exts = [];
|
||||
if (environment.PATH) {
|
||||
dirs = environment.PATH.split(";");
|
||||
}
|
||||
if (environment.PATHEXT) {
|
||||
exts = environment.PATHEXT.split(";");
|
||||
}
|
||||
|
||||
for (let dir of dirs) {
|
||||
let path = OS.Path.join(dir, bin);
|
||||
|
||||
if (await this.isExecutableFile(path)) {
|
||||
return path;
|
||||
}
|
||||
|
||||
for (let ext of exts) {
|
||||
let file = path + ext;
|
||||
|
||||
if (await this.isExecutableFile(file)) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
let error = new Error(`Executable not found: ${bin}`);
|
||||
error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
var SubprocessImpl = SubprocessWin;
|
|
@ -1,269 +0,0 @@
|
|||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/* exported BasePipe, BaseProcess, debug */
|
||||
/* globals Process, io */
|
||||
|
||||
/* global ctypes: false */
|
||||
/* eslint no-console: 0 */
|
||||
|
||||
function debug(message) {
|
||||
self.postMessage({
|
||||
msg: "debug",
|
||||
message
|
||||
});
|
||||
}
|
||||
|
||||
class BasePipe {
|
||||
constructor() {
|
||||
this.closing = false;
|
||||
this.closed = false;
|
||||
|
||||
this.closedPromise = new Promise(resolve => {
|
||||
this.resolveClosed = resolve;
|
||||
});
|
||||
|
||||
this.pending = [];
|
||||
}
|
||||
|
||||
shiftPending() {
|
||||
let result = this.pending.shift();
|
||||
|
||||
if (this.closing && this.pending.length == 0) {
|
||||
this.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
let nextProcessId = 0;
|
||||
|
||||
class BaseProcess {
|
||||
constructor(options) {
|
||||
this.id = nextProcessId++;
|
||||
|
||||
this.exitCode = null;
|
||||
|
||||
this.exitPromise = new Promise(resolve => {
|
||||
this.resolveExit = resolve;
|
||||
});
|
||||
this.exitPromise.then(() => {
|
||||
// The input file descriptors will be closed after poll
|
||||
// reports that their input buffers are empty. If we close
|
||||
// them now, we may lose output.
|
||||
this.pipes[0].close(true);
|
||||
});
|
||||
|
||||
this.pid = null;
|
||||
this.pipes = [];
|
||||
|
||||
this.stringArrays = [];
|
||||
|
||||
this.spawn(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the process to exit and all of its pending IO operations to
|
||||
* complete.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
awaitFinished() {
|
||||
return Promise.all([
|
||||
this.exitPromise,
|
||||
...this.pipes.map(pipe => pipe.closedPromise)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a null-terminated array of pointers to null-terminated C-strings,
|
||||
* and returns it.
|
||||
*
|
||||
* @param {string[]} strings
|
||||
* The strings to convert into a C string array.
|
||||
*
|
||||
* @returns {ctypes.char.ptr.array}
|
||||
*/
|
||||
stringArray(strings) {
|
||||
let result = ctypes.char.ptr.array(strings.length + 1)();
|
||||
|
||||
let cstrings = strings.map(str => ctypes.char.array()(str));
|
||||
for (let [i, cstring] of cstrings.entries()) {
|
||||
result[i] = cstring;
|
||||
}
|
||||
|
||||
// Char arrays used in char arg and environment vectors must be
|
||||
// explicitly kept alive in a JS object, or they will be reaped
|
||||
// by the GC if it runs before our process is started.
|
||||
this.stringArrays.push(cstrings);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
let requests = {
|
||||
init(details) {
|
||||
io.init(details);
|
||||
|
||||
return {
|
||||
data: {}
|
||||
};
|
||||
},
|
||||
|
||||
shutdown() {
|
||||
io.shutdown();
|
||||
|
||||
return {
|
||||
data: {}
|
||||
};
|
||||
},
|
||||
|
||||
close(pipeId, force = false) {
|
||||
let pipe = io.getPipe(pipeId);
|
||||
|
||||
return pipe.close(force).then(() => ({
|
||||
data: {}
|
||||
}));
|
||||
},
|
||||
|
||||
spawn(options) {
|
||||
let process = new Process(options);
|
||||
let processId = process.id;
|
||||
|
||||
io.addProcess(process);
|
||||
|
||||
let fds = process.pipes.map(pipe => pipe.id);
|
||||
|
||||
return {
|
||||
data: {
|
||||
processId,
|
||||
fds,
|
||||
pid: process.pid
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
kill(processId, force = false) {
|
||||
let process = io.getProcess(processId);
|
||||
|
||||
process.kill(force ? 9 : 15);
|
||||
|
||||
return {
|
||||
data: {}
|
||||
};
|
||||
},
|
||||
|
||||
wait(processId) {
|
||||
let process = io.getProcess(processId);
|
||||
|
||||
process.wait();
|
||||
|
||||
process.awaitFinished().then(() => {
|
||||
io.cleanupProcess(process);
|
||||
});
|
||||
|
||||
return process.exitPromise.then(exitCode => {
|
||||
return {
|
||||
data: {
|
||||
exitCode
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
read(pipeId, count) {
|
||||
let pipe = io.getPipe(pipeId);
|
||||
|
||||
return pipe.read(count).then(buffer => {
|
||||
return {
|
||||
data: {
|
||||
buffer
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
write(pipeId, buffer) {
|
||||
let pipe = io.getPipe(pipeId);
|
||||
|
||||
return pipe.write(buffer).then(bytesWritten => {
|
||||
return {
|
||||
data: {
|
||||
bytesWritten
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
getOpenFiles() {
|
||||
return {
|
||||
data: new Set(io.pipes.keys())
|
||||
};
|
||||
},
|
||||
|
||||
getProcesses() {
|
||||
let data = new Map(Array.from(io.processes.values())
|
||||
.filter(proc => proc.exitCode === null)
|
||||
.map(proc => [proc.id, proc.pid]));
|
||||
return {
|
||||
data
|
||||
};
|
||||
},
|
||||
|
||||
waitForNoProcesses() {
|
||||
return Promise.all(Array.from(io.processes.values(),
|
||||
proc => proc.awaitFinished()));
|
||||
}
|
||||
};
|
||||
|
||||
onmessage = event => {
|
||||
io.messageCount--;
|
||||
|
||||
let {
|
||||
msg,
|
||||
msgId,
|
||||
args
|
||||
} = event.data;
|
||||
|
||||
new Promise(resolve => {
|
||||
resolve(requests[msg](...args));
|
||||
}).then(result => {
|
||||
let response = {
|
||||
msg: "success",
|
||||
msgId,
|
||||
data: result.data
|
||||
};
|
||||
|
||||
self.postMessage(response, result.transfer || []);
|
||||
}).catch(error => {
|
||||
if (error instanceof Error) {
|
||||
error = {
|
||||
message: error.message,
|
||||
fileName: error.fileName,
|
||||
lineNumber: error.lineNumber,
|
||||
column: error.column,
|
||||
stack: error.stack,
|
||||
errorCode: error.errorCode
|
||||
};
|
||||
}
|
||||
|
||||
self.postMessage({
|
||||
msg: "failure",
|
||||
msgId,
|
||||
error
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
|
||||
self.postMessage({
|
||||
msg: "failure",
|
||||
msgId,
|
||||
error: {}
|
||||
});
|
||||
});
|
||||
};
|
|
@ -1,650 +0,0 @@
|
|||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/* exported Process */
|
||||
/* globals BaseProcess, BasePipe */
|
||||
|
||||
/* global importScripts: false */
|
||||
|
||||
importScripts("chrome://openpgp/content/modules/enigmailprocess_shared.js",
|
||||
"chrome://openpgp/content/modules/enigmailprocess_shared_unix.js",
|
||||
"chrome://openpgp/content/modules/enigmailprocess_worker_common.js");
|
||||
|
||||
/* global ctypes: false, LIBC: false, libc: false, unix: false, SubprocessConstants: false */
|
||||
/* global debug: false */
|
||||
/* eslint no-console: 0 */
|
||||
|
||||
const POLL_TIMEOUT = 5000;
|
||||
|
||||
let io;
|
||||
|
||||
let nextPipeId = 0;
|
||||
|
||||
class Pipe extends BasePipe {
|
||||
constructor(process, fd) {
|
||||
super();
|
||||
|
||||
this.process = process;
|
||||
this.fd = fd;
|
||||
this.id = nextPipeId++;
|
||||
}
|
||||
|
||||
get pollEvents() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the file descriptor.
|
||||
*
|
||||
* @param {boolean} [force=false]
|
||||
* If true, the file descriptor is closed immediately. If false, the
|
||||
* file descriptor is closed after all current pending IO operations
|
||||
* have completed.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
* Resolves when the file descriptor has been closed.
|
||||
*/
|
||||
close(force = false) {
|
||||
if (!force && this.pending.length) {
|
||||
this.closing = true;
|
||||
return this.closedPromise;
|
||||
}
|
||||
|
||||
for (let {
|
||||
reject
|
||||
}
|
||||
of this.pending) {
|
||||
let error = new Error("File closed");
|
||||
error.errorCode = SubprocessConstants.ERROR_END_OF_FILE;
|
||||
reject(error);
|
||||
}
|
||||
this.pending.length = 0;
|
||||
|
||||
if (!this.closed) {
|
||||
this.fd.dispose();
|
||||
|
||||
this.closed = true;
|
||||
this.resolveClosed();
|
||||
|
||||
io.pipes.delete(this.id);
|
||||
io.updatePollFds();
|
||||
}
|
||||
return this.closedPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an error occurred while polling our file descriptor.
|
||||
*/
|
||||
onError() {
|
||||
this.close(true);
|
||||
this.process.wait();
|
||||
}
|
||||
}
|
||||
|
||||
class InputPipe extends Pipe {
|
||||
/**
|
||||
* A bit mask of poll() events which we currently wish to be notified of on
|
||||
* this file descriptor.
|
||||
*/
|
||||
get pollEvents() {
|
||||
if (this.pending.length) {
|
||||
return LIBC.POLLIN;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously reads at most `length` bytes of binary data from the file
|
||||
* descriptor into an ArrayBuffer of the same size. Returns a promise which
|
||||
* resolves when the operation is complete.
|
||||
*
|
||||
* @param {integer} length
|
||||
* The number of bytes to read.
|
||||
*
|
||||
* @returns {Promise<ArrayBuffer>}
|
||||
*/
|
||||
read(length) {
|
||||
if (this.closing || this.closed) {
|
||||
throw new Error("Attempt to read from closed pipe");
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pending.push({
|
||||
resolve, reject, length
|
||||
});
|
||||
io.updatePollFds();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously reads at most `count` bytes of binary data into an
|
||||
* ArrayBuffer, and returns that buffer. If no data can be read without
|
||||
* blocking, returns null instead.
|
||||
*
|
||||
* @param {integer} count
|
||||
* The number of bytes to read.
|
||||
*
|
||||
* @returns {ArrayBuffer|null}
|
||||
*/
|
||||
readBuffer(count) {
|
||||
let buffer = new ArrayBuffer(count);
|
||||
|
||||
let read = Number(libc.read(this.fd, buffer, buffer.byteLength));
|
||||
if (read < 0 && ctypes.errno != LIBC.EAGAIN) {
|
||||
this.onError();
|
||||
}
|
||||
|
||||
if (read <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (read < buffer.byteLength) {
|
||||
return ArrayBuffer.transfer(buffer, read);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when one of the IO operations matching the `pollEvents` mask may be
|
||||
* performed without blocking.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* True if any data was successfully read.
|
||||
*/
|
||||
onReady() {
|
||||
let result = false;
|
||||
let reads = this.pending;
|
||||
while (reads.length) {
|
||||
let {
|
||||
resolve, length
|
||||
} = reads[0];
|
||||
|
||||
let buffer = this.readBuffer(length);
|
||||
if (buffer) {
|
||||
result = true;
|
||||
this.shiftPending();
|
||||
resolve(buffer);
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (reads.length == 0) {
|
||||
io.updatePollFds();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class OutputPipe extends Pipe {
|
||||
/**
|
||||
* A bit mask of poll() events which we currently wish to be notified of on
|
||||
* this file discriptor.
|
||||
*/
|
||||
get pollEvents() {
|
||||
if (this.pending.length) {
|
||||
return LIBC.POLLOUT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously writes the given buffer to our file descriptor, and returns
|
||||
* a promise which resolves when the operation is complete.
|
||||
*
|
||||
* @param {ArrayBuffer} buffer
|
||||
* The buffer to write.
|
||||
*
|
||||
* @returns {Promise<integer>}
|
||||
* Resolves to the number of bytes written when the operation is
|
||||
* complete.
|
||||
*/
|
||||
write(buffer) {
|
||||
if (this.closing || this.closed) {
|
||||
throw new Error("Attempt to write to closed pipe");
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pending.push({
|
||||
resolve, reject, buffer, length: buffer.byteLength
|
||||
});
|
||||
io.updatePollFds();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to synchronously write the given buffer to our file descriptor.
|
||||
* Writes only as many bytes as can be written without blocking, and returns
|
||||
* the number of byes successfully written.
|
||||
*
|
||||
* Closes the file descriptor if an IO error occurs.
|
||||
*
|
||||
* @param {ArrayBuffer} buffer
|
||||
* The buffer to write.
|
||||
*
|
||||
* @returns {integer}
|
||||
* The number of bytes successfully written.
|
||||
*/
|
||||
writeBuffer(buffer) {
|
||||
let bytesWritten = libc.write(this.fd, buffer, buffer.byteLength);
|
||||
|
||||
if (bytesWritten < 0 && ctypes.errno != LIBC.EAGAIN) {
|
||||
this.onError();
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when one of the IO operations matching the `pollEvents` mask may be
|
||||
* performed without blocking.
|
||||
*/
|
||||
onReady() {
|
||||
let writes = this.pending;
|
||||
while (writes.length) {
|
||||
let {
|
||||
buffer, resolve, length
|
||||
} = writes[0];
|
||||
|
||||
let written = this.writeBuffer(buffer);
|
||||
|
||||
if (written == buffer.byteLength) {
|
||||
resolve(length);
|
||||
this.shiftPending();
|
||||
}
|
||||
else if (written > 0) {
|
||||
writes[0].buffer = buffer.slice(written);
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (writes.length == 0) {
|
||||
io.updatePollFds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Signal {
|
||||
constructor(fd) {
|
||||
this.fd = fd;
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
libc.close(this.fd);
|
||||
this.fd = null;
|
||||
}
|
||||
|
||||
get pollEvents() {
|
||||
return LIBC.POLLIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an error occurred while polling our file descriptor.
|
||||
*/
|
||||
onError() {
|
||||
io.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when one of the IO operations matching the `pollEvents` mask may be
|
||||
* performed without blocking.
|
||||
*/
|
||||
onReady() {
|
||||
let buffer = new ArrayBuffer(16);
|
||||
let count = Number(libc.read(this.fd, buffer, buffer.byteLength));
|
||||
if (count > 0) {
|
||||
io.messageCount += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Process extends BaseProcess {
|
||||
/**
|
||||
* Each Process object opens an additional pipe from the target object, which
|
||||
* will be automatically closed when the process exits, but otherwise
|
||||
* carries no data.
|
||||
*
|
||||
* This property contains a bit mask of poll() events which we wish to be
|
||||
* notified of on this descriptor. We're not expecting any input from this
|
||||
* pipe, but we need to poll for input until the process exits in order to be
|
||||
* notified when the pipe closes.
|
||||
*/
|
||||
get pollEvents() {
|
||||
if (this.exitCode === null) {
|
||||
return LIBC.POLLIN;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kills the process with the given signal.
|
||||
*
|
||||
* @param {integer} signal
|
||||
*/
|
||||
kill(signal) {
|
||||
libc.kill(this.pid, signal);
|
||||
this.wait();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the IO pipes for use as standard input, output, and error
|
||||
* descriptors in the spawned process.
|
||||
*
|
||||
* @param {object} options
|
||||
* The Subprocess options object for this process.
|
||||
* @returns {unix.Fd[]}
|
||||
* The array of file descriptors belonging to the spawned process.
|
||||
*/
|
||||
initPipes(options) {
|
||||
let stderr = options.stderr;
|
||||
|
||||
let our_pipes = [];
|
||||
let their_pipes = new Map();
|
||||
|
||||
let pipe = (input, id) => {
|
||||
let fds = ctypes.int.array(2)();
|
||||
|
||||
let res = libc.pipe(fds);
|
||||
if (res == -1) {
|
||||
throw new Error("Unable to create pipe");
|
||||
}
|
||||
|
||||
fds = Array.from(fds, unix.Fd);
|
||||
|
||||
if (input) {
|
||||
fds.reverse();
|
||||
}
|
||||
|
||||
if (input) {
|
||||
our_pipes[id] = new InputPipe(this, fds[1]);
|
||||
}
|
||||
else {
|
||||
our_pipes[id] = new OutputPipe(this, fds[1]);
|
||||
}
|
||||
|
||||
libc.fcntl(fds[0], LIBC.F_SETFD, LIBC.FD_CLOEXEC);
|
||||
libc.fcntl(fds[1], LIBC.F_SETFD, LIBC.FD_CLOEXEC);
|
||||
libc.fcntl(fds[1], LIBC.F_SETFL, LIBC.O_NONBLOCK);
|
||||
|
||||
return fds[0];
|
||||
};
|
||||
|
||||
their_pipes.set(0, pipe(false, 0));
|
||||
their_pipes.set(1, pipe(true, 1));
|
||||
|
||||
if (stderr == "pipe") {
|
||||
their_pipes.set(2, pipe(true, 2));
|
||||
}
|
||||
else if (stderr == "stdout") {
|
||||
their_pipes.set(2, their_pipes.get(1));
|
||||
}
|
||||
|
||||
// Create an additional pipe that we can use to monitor for process exit.
|
||||
their_pipes.set(3, pipe(true, 3));
|
||||
this.fd = our_pipes[3].fd;
|
||||
delete our_pipes[3];
|
||||
|
||||
this.pipes = our_pipes;
|
||||
|
||||
return their_pipes;
|
||||
}
|
||||
|
||||
spawn(options) {
|
||||
let {
|
||||
command, arguments: args
|
||||
} = options;
|
||||
|
||||
let argv = this.stringArray(args);
|
||||
let envp = this.stringArray(options.environment);
|
||||
|
||||
let actions = unix.posix_spawn_file_actions_t();
|
||||
let actionsp = actions.address();
|
||||
|
||||
let fds = this.initPipes(options);
|
||||
|
||||
let cwd;
|
||||
try {
|
||||
if (options.workdir) {
|
||||
cwd = ctypes.char.array(LIBC.PATH_MAX)();
|
||||
libc.getcwd(cwd, cwd.length);
|
||||
|
||||
if (libc.chdir(options.workdir) < 0) {
|
||||
throw new Error(`Unable to change working directory to ${options.workdir}`);
|
||||
}
|
||||
}
|
||||
|
||||
libc.posix_spawn_file_actions_init(actionsp);
|
||||
for (let [i, fd] of fds.entries()) {
|
||||
libc.posix_spawn_file_actions_adddup2(actionsp, fd, i);
|
||||
}
|
||||
|
||||
let pid = unix.pid_t();
|
||||
let rv = libc.posix_spawn(pid.address(), command, actionsp, null, argv, envp);
|
||||
|
||||
if (rv != 0) {
|
||||
for (let pipe of this.pipes) {
|
||||
pipe.close();
|
||||
}
|
||||
throw new Error(`Failed to execute command "${command}"`);
|
||||
}
|
||||
|
||||
this.pid = pid.value;
|
||||
}
|
||||
finally {
|
||||
libc.posix_spawn_file_actions_destroy(actionsp);
|
||||
|
||||
this.stringArrays.length = 0;
|
||||
|
||||
if (cwd) {
|
||||
libc.chdir(cwd);
|
||||
}
|
||||
for (let fd of new Set(fds.values())) {
|
||||
fd.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when input is available on our sentinel file descriptor.
|
||||
*
|
||||
* @see pollEvents
|
||||
*/
|
||||
onReady() {
|
||||
// We're not actually expecting any input on this pipe. If we get any, we
|
||||
// can't poll the pipe any further without reading it.
|
||||
if (this.wait() == undefined) {
|
||||
this.kill(9);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an error occurred while polling our sentinel file descriptor.
|
||||
*
|
||||
* @see pollEvents
|
||||
*/
|
||||
onError() {
|
||||
this.wait();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to wait for the process's exit status, without blocking. If
|
||||
* successful, resolves the `exitPromise` to the process's exit value.
|
||||
*
|
||||
* @returns {integer|null}
|
||||
* The process's exit status, if it has already exited.
|
||||
*/
|
||||
wait() {
|
||||
if (this.exitCode !== null) {
|
||||
return this.exitCode;
|
||||
}
|
||||
|
||||
let status = ctypes.int();
|
||||
|
||||
let res = libc.waitpid(this.pid, status.address(), LIBC.WNOHANG);
|
||||
|
||||
const EINTR = 4; // TODO: change to LIBC.EINTR with TB 59
|
||||
|
||||
// If there's a failure here and we get any errno other than EINTR, it
|
||||
// means that the process has been reaped by another thread (most likely
|
||||
// the nspr process wait thread), and its actual exit status is not
|
||||
// available to us. In that case, we have to assume success.
|
||||
if (res == 0 || (res == -1 && ctypes.errno == EINTR)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let sig = unix.WTERMSIG(status.value);
|
||||
if (sig) {
|
||||
this.exitCode = -sig;
|
||||
}
|
||||
else {
|
||||
this.exitCode = unix.WEXITSTATUS(status.value);
|
||||
}
|
||||
|
||||
this.fd.dispose();
|
||||
io.updatePollFds();
|
||||
this.resolveExit(this.exitCode);
|
||||
return this.exitCode;
|
||||
}
|
||||
}
|
||||
|
||||
io = {
|
||||
pollFds: null,
|
||||
pollHandlers: null,
|
||||
|
||||
pipes: new Map(),
|
||||
|
||||
processes: new Map(),
|
||||
|
||||
messageCount: 0,
|
||||
|
||||
running: true,
|
||||
|
||||
init(details) {
|
||||
this.signal = new Signal(details.signalFd);
|
||||
this.updatePollFds();
|
||||
|
||||
setTimeout(this.loop.bind(this), 0);
|
||||
},
|
||||
|
||||
shutdown() {
|
||||
if (this.running) {
|
||||
this.running = false;
|
||||
|
||||
this.signal.cleanup();
|
||||
this.signal = null;
|
||||
|
||||
self.postMessage({
|
||||
msg: "close"
|
||||
});
|
||||
self.close();
|
||||
}
|
||||
},
|
||||
|
||||
getPipe(pipeId) {
|
||||
let pipe = this.pipes.get(pipeId);
|
||||
|
||||
if (!pipe) {
|
||||
let error = new Error("File closed");
|
||||
error.errorCode = SubprocessConstants.ERROR_END_OF_FILE;
|
||||
throw error;
|
||||
}
|
||||
return pipe;
|
||||
},
|
||||
|
||||
getProcess(processId) {
|
||||
let process = this.processes.get(processId);
|
||||
|
||||
if (!process) {
|
||||
throw new Error(`Invalid process ID: ${processId}`);
|
||||
}
|
||||
return process;
|
||||
},
|
||||
|
||||
updatePollFds() {
|
||||
let handlers = [this.signal,
|
||||
...this.pipes.values(),
|
||||
...this.processes.values()
|
||||
];
|
||||
|
||||
handlers = handlers.filter(handler => handler.pollEvents);
|
||||
|
||||
let pollfds = unix.pollfd.array(handlers.length)();
|
||||
|
||||
for (let [i, handler] of handlers.entries()) {
|
||||
let pollfd = pollfds[i];
|
||||
|
||||
pollfd.fd = handler.fd;
|
||||
pollfd.events = handler.pollEvents;
|
||||
pollfd.revents = 0;
|
||||
}
|
||||
|
||||
this.pollFds = pollfds;
|
||||
this.pollHandlers = handlers;
|
||||
},
|
||||
|
||||
loop() {
|
||||
this.poll();
|
||||
if (this.running) {
|
||||
setTimeout(this.loop.bind(this), 0);
|
||||
}
|
||||
},
|
||||
|
||||
poll() {
|
||||
let handlers = this.pollHandlers;
|
||||
let pollfds = this.pollFds;
|
||||
|
||||
let timeout = this.messageCount > 0 ? 0 : POLL_TIMEOUT;
|
||||
let count = libc.poll(pollfds, pollfds.length, timeout);
|
||||
|
||||
for (let i = 0; count && i < pollfds.length; i++) {
|
||||
let pollfd = pollfds[i];
|
||||
if (pollfd.revents) {
|
||||
count--;
|
||||
|
||||
let handler = handlers[i];
|
||||
try {
|
||||
let success = false;
|
||||
if (pollfd.revents & handler.pollEvents) {
|
||||
success = handler.onReady();
|
||||
}
|
||||
// Only call the error handler in this iteration if we didn't also
|
||||
// have a success. This is necessary because Linux systems set POLLHUP
|
||||
// on a pipe when it's closed but there's still buffered data to be
|
||||
// read, and Darwin sets POLLIN and POLLHUP on a closed pipe, even
|
||||
// when there's no data to be read.
|
||||
if (!success && (pollfd.revents & (LIBC.POLLERR | LIBC.POLLHUP | LIBC.POLLNVAL))) {
|
||||
handler.onError();
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
debug(`Worker error: ${e} :: ${e.stack}`);
|
||||
handler.onError();
|
||||
}
|
||||
|
||||
pollfd.revents = 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addProcess(process) {
|
||||
this.processes.set(process.id, process);
|
||||
|
||||
for (let pipe of process.pipes) {
|
||||
if (pipe !== undefined)
|
||||
this.pipes.set(pipe.id, pipe);
|
||||
}
|
||||
},
|
||||
|
||||
cleanupProcess(process) {
|
||||
this.processes.delete(process.id);
|
||||
}
|
||||
};
|
|
@ -1,753 +0,0 @@
|
|||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/* exported Process */
|
||||
/* globals BaseProcess, BasePipe, win32 */
|
||||
|
||||
/* global importScripts: false */
|
||||
|
||||
importScripts("chrome://openpgp/content/modules/enigmailprocess_shared.js",
|
||||
"chrome://openpgp/content/modules/enigmailprocess_shared_win.js",
|
||||
"chrome://openpgp/content/modules/enigmailprocess_worker_common.js");
|
||||
|
||||
/* global ctypes: false, libc: false, unix: false, SubprocessConstants: false, user32: false */
|
||||
/* global debug: false */
|
||||
/* eslint no-console: 0 */
|
||||
|
||||
const POLL_TIMEOUT = 5000;
|
||||
|
||||
// The exit code that we send when we forcibly terminate a process.
|
||||
const TERMINATE_EXIT_CODE = 0x7f;
|
||||
|
||||
let io;
|
||||
|
||||
let nextPipeId = 0;
|
||||
|
||||
class Pipe extends BasePipe {
|
||||
constructor(process, origHandle) {
|
||||
super();
|
||||
|
||||
let handle = win32.HANDLE();
|
||||
|
||||
let curProc = libc.GetCurrentProcess();
|
||||
libc.DuplicateHandle(curProc, origHandle, curProc, handle.address(),
|
||||
0, false /* inheritable */ , win32.DUPLICATE_SAME_ACCESS);
|
||||
|
||||
origHandle.dispose();
|
||||
|
||||
this.id = nextPipeId++;
|
||||
this.process = process;
|
||||
|
||||
this.handle = win32.Handle(handle);
|
||||
|
||||
let event = libc.CreateEventW(null, false, false, null);
|
||||
|
||||
this.overlapped = win32.OVERLAPPED();
|
||||
this.overlapped.hEvent = event;
|
||||
|
||||
this._event = win32.Handle(event);
|
||||
|
||||
this.buffer = null;
|
||||
}
|
||||
|
||||
get event() {
|
||||
if (this.pending.length) {
|
||||
return this._event;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
maybeClose() {}
|
||||
|
||||
/**
|
||||
* Closes the file handle.
|
||||
*
|
||||
* @param {boolean} [force=false]
|
||||
* If true, the file handle is closed immediately. If false, the
|
||||
* file handle is closed after all current pending IO operations
|
||||
* have completed.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
* Resolves when the file handle has been closed.
|
||||
*/
|
||||
close(force = false) {
|
||||
if (!force && this.pending.length) {
|
||||
this.closing = true;
|
||||
return this.closedPromise;
|
||||
}
|
||||
|
||||
for (let {reject} of this.pending) {
|
||||
let error = new Error("File closed");
|
||||
error.errorCode = SubprocessConstants.ERROR_END_OF_FILE;
|
||||
reject(error);
|
||||
}
|
||||
this.pending.length = 0;
|
||||
|
||||
this.buffer = null;
|
||||
|
||||
if (!this.closed) {
|
||||
this.handle.dispose();
|
||||
this._event.dispose();
|
||||
|
||||
io.pipes.delete(this.id);
|
||||
|
||||
this.handle = null;
|
||||
this.closed = true;
|
||||
this.resolveClosed();
|
||||
|
||||
io.updatePollEvents();
|
||||
}
|
||||
return this.closedPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an error occurred while attempting an IO operation on our file
|
||||
* handle.
|
||||
*/
|
||||
onError() {
|
||||
this.close(true);
|
||||
}
|
||||
}
|
||||
|
||||
class InputPipe extends Pipe {
|
||||
/**
|
||||
* Queues the next chunk of data to be read from the pipe if, and only if,
|
||||
* there is no IO operation currently pending.
|
||||
*/
|
||||
readNext() {
|
||||
if (this.buffer === null) {
|
||||
this.readBuffer(this.pending[0].length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the pipe if there is a pending read operation with no more
|
||||
* buffered data to be read.
|
||||
*/
|
||||
maybeClose() {
|
||||
if (this.buffer) {
|
||||
let read = win32.DWORD();
|
||||
|
||||
let ok = libc.GetOverlappedResult(
|
||||
this.handle, this.overlapped.address(),
|
||||
read.address(), false);
|
||||
|
||||
if (!ok) {
|
||||
this.onError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously reads at most `length` bytes of binary data from the file
|
||||
* descriptor into an ArrayBuffer of the same size. Returns a promise which
|
||||
* resolves when the operation is complete.
|
||||
*
|
||||
* @param {integer} length
|
||||
* The number of bytes to read.
|
||||
*
|
||||
* @returns {Promise<ArrayBuffer>}
|
||||
*/
|
||||
read(length) {
|
||||
if (this.closing || this.closed) {
|
||||
throw new Error("Attempt to read from closed pipe");
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pending.push({
|
||||
resolve,
|
||||
reject,
|
||||
length
|
||||
});
|
||||
this.readNext();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes an overlapped IO read operation to read exactly `count` bytes
|
||||
* into a new ArrayBuffer, which is stored in the `buffer` property until the
|
||||
* operation completes.
|
||||
*
|
||||
* @param {integer} count
|
||||
* The number of bytes to read.
|
||||
*/
|
||||
readBuffer(count) {
|
||||
this.buffer = new ArrayBuffer(count);
|
||||
|
||||
let ok = libc.ReadFile(this.handle, this.buffer, count,
|
||||
null, this.overlapped.address());
|
||||
|
||||
if (!ok && (!this.process.handle || libc.winLastError)) {
|
||||
this.onError();
|
||||
}
|
||||
else {
|
||||
io.updatePollEvents();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when our pending overlapped IO operation has completed, whether
|
||||
* successfully or in failure.
|
||||
*/
|
||||
onReady() {
|
||||
let read = win32.DWORD();
|
||||
|
||||
let ok = libc.GetOverlappedResult(
|
||||
this.handle, this.overlapped.address(),
|
||||
read.address(), false);
|
||||
|
||||
read = read.value;
|
||||
|
||||
if (!ok) {
|
||||
this.onError();
|
||||
}
|
||||
else if (read > 0) {
|
||||
let buffer = this.buffer;
|
||||
this.buffer = null;
|
||||
|
||||
let {resolve} = this.shiftPending();
|
||||
|
||||
if (read == buffer.byteLength) {
|
||||
resolve(buffer);
|
||||
}
|
||||
else {
|
||||
resolve(ArrayBuffer.transfer(buffer, read));
|
||||
}
|
||||
|
||||
if (this.pending.length) {
|
||||
this.readNext();
|
||||
}
|
||||
else {
|
||||
io.updatePollEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OutputPipe extends Pipe {
|
||||
/**
|
||||
* Queues the next chunk of data to be written to the pipe if, and only if,
|
||||
* there is no IO operation currently pending.
|
||||
*/
|
||||
writeNext() {
|
||||
if (this.buffer === null) {
|
||||
this.writeBuffer(this.pending[0].buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously writes the given buffer to our file descriptor, and returns
|
||||
* a promise which resolves when the operation is complete.
|
||||
*
|
||||
* @param {ArrayBuffer} buffer
|
||||
* The buffer to write.
|
||||
*
|
||||
* @returns {Promise<integer>}
|
||||
* Resolves to the number of bytes written when the operation is
|
||||
* complete.
|
||||
*/
|
||||
write(buffer) {
|
||||
if (this.closing || this.closed) {
|
||||
throw new Error("Attempt to write to closed pipe");
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pending.push({
|
||||
resolve,
|
||||
reject,
|
||||
buffer
|
||||
});
|
||||
this.writeNext();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes an overapped IO read operation to write the data in `buffer` to
|
||||
* our file descriptor.
|
||||
*
|
||||
* @param {ArrayBuffer} buffer
|
||||
* The buffer to write.
|
||||
*/
|
||||
writeBuffer(buffer) {
|
||||
this.buffer = buffer;
|
||||
|
||||
let ok = libc.WriteFile(this.handle, buffer, buffer.byteLength,
|
||||
null, this.overlapped.address());
|
||||
|
||||
if (!ok && libc.winLastError) {
|
||||
this.onError();
|
||||
}
|
||||
else {
|
||||
io.updatePollEvents();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when our pending overlapped IO operation has completed, whether
|
||||
* successfully or in failure.
|
||||
*/
|
||||
onReady() {
|
||||
let written = win32.DWORD();
|
||||
|
||||
let ok = libc.GetOverlappedResult(
|
||||
this.handle, this.overlapped.address(),
|
||||
written.address(), false);
|
||||
|
||||
written = written.value;
|
||||
|
||||
if (!ok || written != this.buffer.byteLength) {
|
||||
this.onError();
|
||||
}
|
||||
else if (written > 0) {
|
||||
let {resolve} = this.shiftPending();
|
||||
|
||||
this.buffer = null;
|
||||
resolve(written);
|
||||
|
||||
if (this.pending.length) {
|
||||
this.writeNext();
|
||||
}
|
||||
else {
|
||||
io.updatePollEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Signal {
|
||||
constructor(event) {
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
libc.CloseHandle(this.event);
|
||||
this.event = null;
|
||||
}
|
||||
|
||||
onError() {
|
||||
io.shutdown();
|
||||
}
|
||||
|
||||
onReady() {
|
||||
io.messageCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
class Process extends BaseProcess {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.killed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns our process handle for use as an event in a WaitForMultipleObjects
|
||||
* call.
|
||||
*/
|
||||
get event() {
|
||||
return this.handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forcibly terminates the process and all children.
|
||||
*/
|
||||
kill() {
|
||||
this.killed = true;
|
||||
libc.TerminateJobObject(this.jobHandle, TERMINATE_EXIT_CODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the IO pipes for use as standard input, output, and error
|
||||
* descriptors in the spawned process.
|
||||
*
|
||||
* @returns {win32.Handle[]}
|
||||
* The array of file handles belonging to the spawned process.
|
||||
*/
|
||||
initPipes({stderr}) {
|
||||
let our_pipes = [];
|
||||
let their_pipes = [];
|
||||
|
||||
let secAttr = new win32.SECURITY_ATTRIBUTES();
|
||||
secAttr.nLength = win32.SECURITY_ATTRIBUTES.size;
|
||||
secAttr.bInheritHandle = true;
|
||||
|
||||
let pipe = input => {
|
||||
if (input) {
|
||||
let handles = win32.createPipe(secAttr, win32.FILE_FLAG_OVERLAPPED);
|
||||
our_pipes.push(new InputPipe(this, handles[0]));
|
||||
return handles[1];
|
||||
}
|
||||
let handles = win32.createPipe(secAttr, 0, win32.FILE_FLAG_OVERLAPPED);
|
||||
our_pipes.push(new OutputPipe(this, handles[1]));
|
||||
return handles[0];
|
||||
};
|
||||
|
||||
their_pipes[0] = pipe(false);
|
||||
their_pipes[1] = pipe(true);
|
||||
|
||||
if (stderr == "pipe") {
|
||||
their_pipes[2] = pipe(true);
|
||||
}
|
||||
else {
|
||||
let srcHandle;
|
||||
if (stderr == "stdout") {
|
||||
srcHandle = their_pipes[1];
|
||||
}
|
||||
else {
|
||||
srcHandle = libc.GetStdHandle(win32.STD_ERROR_HANDLE);
|
||||
}
|
||||
|
||||
// If we don't have a valid stderr handle, just pass it along without duplicating.
|
||||
if (String(srcHandle) == win32.INVALID_HANDLE_VALUE ||
|
||||
String(srcHandle) == win32.NULL_HANDLE_VALUE) {
|
||||
their_pipes[2] = srcHandle;
|
||||
}
|
||||
else {
|
||||
|
||||
let handle = win32.HANDLE();
|
||||
|
||||
let curProc = libc.GetCurrentProcess();
|
||||
let ok = libc.DuplicateHandle(curProc, srcHandle, curProc, handle.address(),
|
||||
0, true /* inheritable */ ,
|
||||
win32.DUPLICATE_SAME_ACCESS);
|
||||
|
||||
their_pipes[2] = ok && win32.Handle(handle);
|
||||
}
|
||||
}
|
||||
|
||||
if (!their_pipes.every(handle => handle)) {
|
||||
throw new Error("Failed to create pipe");
|
||||
}
|
||||
|
||||
this.pipes = our_pipes;
|
||||
|
||||
return their_pipes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a null-separated, null-terminated string list.
|
||||
*
|
||||
* @param {Array<string>} strings
|
||||
* @returns {win32.WCHAR.array}
|
||||
*/
|
||||
stringList(strings) {
|
||||
// Remove empty strings, which would terminate the list early.
|
||||
strings = strings.filter(string => string);
|
||||
|
||||
let string = strings.join("\0") + "\0\0";
|
||||
|
||||
return win32.WCHAR.array()(string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quotes a string for use as a single command argument, using Windows quoting
|
||||
* conventions.
|
||||
*
|
||||
* @see https://msdn.microsoft.com/en-us/library/17w5ykft(v=vs.85).aspx
|
||||
*
|
||||
* @param {string} str
|
||||
* The argument string to quote.
|
||||
* @returns {string}
|
||||
*/
|
||||
quoteString(str) {
|
||||
if (!/[\s"]/.test(str)) {
|
||||
return str;
|
||||
}
|
||||
|
||||
let escaped = str.replace(/(\\*)("|$)/g, (m0, m1, m2) => {
|
||||
if (m2) {
|
||||
m2 = `${m2}`;
|
||||
}
|
||||
return `${m1}${m1}${m2}`;
|
||||
});
|
||||
|
||||
return `"${escaped}"`;
|
||||
}
|
||||
|
||||
spawn(options) {
|
||||
let {command, arguments: args} = options;
|
||||
|
||||
if (/\\cmd\.exe$/i.test(command) && args.length == 3 && /^(\/S)?\/C$/i.test(args[1])) {
|
||||
// cmd.exe is insane and requires special treatment.
|
||||
args = [this.quoteString(args[0]), "/S/C", `"${args[2]}"`];
|
||||
}
|
||||
else {
|
||||
args = args.map(arg => this.quoteString(arg));
|
||||
}
|
||||
|
||||
if (/\.(bat|cmd)$/i.test(command)) {
|
||||
command = io.comspec;
|
||||
args = ["cmd.exe", "/s/c", `"${args.join(" ")}"`];
|
||||
}
|
||||
|
||||
let envp = this.stringList(options.environment);
|
||||
|
||||
let handles = this.initPipes(options);
|
||||
|
||||
let processFlags = win32.CREATE_NO_WINDOW | win32.CREATE_SUSPENDED | win32.CREATE_UNICODE_ENVIRONMENT;
|
||||
|
||||
if (io.breakAwayFromJob) {
|
||||
processFlags |= win32.CREATE_BREAKAWAY_FROM_JOB;
|
||||
}
|
||||
|
||||
let startupInfoEx = new win32.STARTUPINFOEXW();
|
||||
let startupInfo = startupInfoEx.StartupInfo;
|
||||
|
||||
startupInfo.cb = win32.STARTUPINFOW.size;
|
||||
startupInfo.dwFlags = win32.STARTF_USESTDHANDLES;
|
||||
|
||||
startupInfo.hStdInput = handles[0];
|
||||
startupInfo.hStdOutput = handles[1];
|
||||
startupInfo.hStdError = handles[2];
|
||||
|
||||
// Note: This needs to be kept alive until we destroy the attribute list.
|
||||
let handleArray = win32.HANDLE.array()(handles);
|
||||
|
||||
let threadAttrs = win32.createThreadAttributeList(handleArray);
|
||||
if (threadAttrs) {
|
||||
// If have thread attributes to pass, pass the size of the full extended
|
||||
// startup info struct.
|
||||
processFlags |= win32.EXTENDED_STARTUPINFO_PRESENT;
|
||||
startupInfo.cb = win32.STARTUPINFOEXW.size;
|
||||
|
||||
startupInfoEx.lpAttributeList = threadAttrs;
|
||||
}
|
||||
|
||||
let procInfo = new win32.PROCESS_INFORMATION();
|
||||
|
||||
let errorMessage = "Failed to create process";
|
||||
let ok = libc.CreateProcessW(
|
||||
command, args.join(" "),
|
||||
null, /* Security attributes */
|
||||
null, /* Thread security attributes */
|
||||
true, /* Inherits handles */
|
||||
processFlags, envp, options.workdir,
|
||||
startupInfo.address(),
|
||||
procInfo.address());
|
||||
|
||||
for (let handle of new Set(handles)) {
|
||||
// If any of our handles are invalid, they don't have finalizers.
|
||||
if (handle && handle.dispose) {
|
||||
handle.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if (threadAttrs) {
|
||||
libc.DeleteProcThreadAttributeList(threadAttrs);
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
this.jobHandle = win32.Handle(libc.CreateJobObjectW(null, null));
|
||||
|
||||
let info = win32.JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
|
||||
info.BasicLimitInformation.LimitFlags = win32.JOB_OBJECT_LIMIT_BREAKAWAY_OK;
|
||||
|
||||
ok = libc.SetInformationJobObject(this.jobHandle, win32.JobObjectExtendedLimitInformation,
|
||||
ctypes.cast(info.address(), ctypes.voidptr_t),
|
||||
info.constructor.size);
|
||||
errorMessage = `Failed to set job limits: 0x${(ctypes.winLastError || 0).toString(16)}`;
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
ok = libc.AssignProcessToJobObject(this.jobHandle, procInfo.hProcess);
|
||||
if (!ok) {
|
||||
errorMessage = `Failed to attach process to job object: 0x${(ctypes.winLastError || 0).toString(16)}`;
|
||||
libc.TerminateProcess(procInfo.hProcess, TERMINATE_EXIT_CODE);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
for (let pipe of this.pipes) {
|
||||
pipe.close();
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
// Allow child processes to obtain focus via Alt-Tab
|
||||
try {
|
||||
user32.AllowSetForegroundWindow(procInfo.dwProcessId);
|
||||
} catch (x) {}
|
||||
|
||||
this.handle = win32.Handle(procInfo.hProcess);
|
||||
this.pid = procInfo.dwProcessId;
|
||||
|
||||
libc.ResumeThread(procInfo.hThread);
|
||||
libc.CloseHandle(procInfo.hThread);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when our process handle is signaled as active, meaning the process
|
||||
* has exited.
|
||||
*/
|
||||
onReady() {
|
||||
this.wait();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to wait for the process's exit status, without blocking. If
|
||||
* successful, resolves the `exitPromise` to the process's exit value.
|
||||
*
|
||||
* @returns {integer|null}
|
||||
* The process's exit status, if it has already exited.
|
||||
*/
|
||||
wait() {
|
||||
if (this.exitCode !== null) {
|
||||
return this.exitCode;
|
||||
}
|
||||
|
||||
let status = win32.DWORD();
|
||||
|
||||
let ok = libc.GetExitCodeProcess(this.handle, status.address());
|
||||
if (ok && status.value != win32.STILL_ACTIVE) {
|
||||
let exitCode = status.value;
|
||||
if (this.killed && exitCode == TERMINATE_EXIT_CODE) {
|
||||
// If we forcibly terminated the process, return the force kill exit
|
||||
// code that we return on other platforms.
|
||||
exitCode = -9;
|
||||
}
|
||||
|
||||
this.resolveExit(exitCode);
|
||||
this.exitCode = exitCode;
|
||||
|
||||
this.handle.dispose();
|
||||
this.handle = null;
|
||||
|
||||
//libc.TerminateJobObject(this.jobHandle, TERMINATE_EXIT_CODE);
|
||||
this.jobHandle.dispose();
|
||||
this.jobHandle = null;
|
||||
|
||||
for (let pipe of this.pipes) {
|
||||
pipe.maybeClose();
|
||||
}
|
||||
|
||||
io.updatePollEvents();
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
io = {
|
||||
events: null,
|
||||
eventHandlers: null,
|
||||
|
||||
pipes: new Map(),
|
||||
|
||||
processes: new Map(),
|
||||
|
||||
messageCount: 0,
|
||||
|
||||
running: true,
|
||||
|
||||
init(details) {
|
||||
this.comspec = details.comspec;
|
||||
|
||||
let signalEvent = ctypes.cast(ctypes.uintptr_t(details.signalEvent),
|
||||
win32.HANDLE);
|
||||
this.signal = new Signal(signalEvent);
|
||||
this.updatePollEvents();
|
||||
|
||||
this.breakAwayFromJob = details.breakAwayFromJob;
|
||||
|
||||
setTimeout(this.loop.bind(this), 0);
|
||||
},
|
||||
|
||||
shutdown() {
|
||||
if (this.running) {
|
||||
this.running = false;
|
||||
|
||||
this.signal.cleanup();
|
||||
this.signal = null;
|
||||
|
||||
self.postMessage({
|
||||
msg: "close"
|
||||
});
|
||||
self.close();
|
||||
}
|
||||
},
|
||||
|
||||
getPipe(pipeId) {
|
||||
let pipe = this.pipes.get(pipeId);
|
||||
|
||||
if (!pipe) {
|
||||
let error = new Error("File closed");
|
||||
error.errorCode = SubprocessConstants.ERROR_END_OF_FILE;
|
||||
throw error;
|
||||
}
|
||||
return pipe;
|
||||
},
|
||||
|
||||
getProcess(processId) {
|
||||
let process = this.processes.get(processId);
|
||||
|
||||
if (!process) {
|
||||
throw new Error(`Invalid process ID: ${processId}`);
|
||||
}
|
||||
return process;
|
||||
},
|
||||
|
||||
updatePollEvents() {
|
||||
let handlers = [this.signal,
|
||||
...this.pipes.values(),
|
||||
...this.processes.values()
|
||||
];
|
||||
|
||||
handlers = handlers.filter(handler => handler.event);
|
||||
|
||||
this.eventHandlers = handlers;
|
||||
|
||||
let handles = handlers.map(handler => handler.event);
|
||||
this.events = win32.HANDLE.array()(handles);
|
||||
},
|
||||
|
||||
loop() {
|
||||
this.poll();
|
||||
if (this.running) {
|
||||
setTimeout(this.loop.bind(this), 0);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
poll() {
|
||||
let timeout = this.messageCount > 0 ? 0 : POLL_TIMEOUT;
|
||||
for (;; timeout = 0) {
|
||||
let events = this.events;
|
||||
let handlers = this.eventHandlers;
|
||||
|
||||
let result = libc.WaitForMultipleObjects(events.length, events,
|
||||
false, timeout);
|
||||
|
||||
if (result < handlers.length) {
|
||||
try {
|
||||
handlers[result].onReady();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
debug(`Worker error: ${e} :: ${e.stack}`);
|
||||
handlers[result].onError();
|
||||
}
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addProcess(process) {
|
||||
this.processes.set(process.id, process);
|
||||
|
||||
for (let pipe of process.pipes) {
|
||||
this.pipes.set(pipe.id, pipe);
|
||||
}
|
||||
},
|
||||
|
||||
cleanupProcess(process) {
|
||||
this.processes.delete(process.id);
|
||||
}
|
||||
};
|
|
@ -12,7 +12,6 @@ const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm
|
|||
const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
|
||||
const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
|
||||
const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
|
||||
const EnigmailSystem = ChromeUtils.import("chrome://openpgp/content/modules/system.jsm").EnigmailSystem;
|
||||
const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
|
||||
const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
|
||||
|
||||
|
@ -129,7 +128,7 @@ function handleErrorCode(c, errorNumber) {
|
|||
case 93: // dirmngr error
|
||||
c.statusFlags |= EnigmailConstants.DISPLAY_MESSAGE;
|
||||
c.retStatusObj.extendedStatus += "disp:get_passphrase ";
|
||||
c.retStatusObj.statusMsg = EnigmailLocale.getString("errorHandling.dirmngrError") + "\n\n" + EnigmailLocale.getString("errorHandling.readFaq");
|
||||
c.retStatusObj.statusMsg = "";
|
||||
c.isError = true;
|
||||
break;
|
||||
case 2:
|
||||
|
@ -484,62 +483,7 @@ function buildErrorMessageForCardCtrl(c, errCode, detectedCard) {
|
|||
return errorMsg;
|
||||
}
|
||||
|
||||
function parseErrorOutputWith(c) {
|
||||
EnigmailLog.DEBUG("errorHandling.jsm: parseErrorOutputWith: status message: \n" + c.errOutput + "\n");
|
||||
|
||||
c.errLines = splitErrorOutput(c.errOutput);
|
||||
c.isError = false; // set to true if a hard error was found
|
||||
|
||||
// parse all error lines
|
||||
c.inDecryptionFailed = false; // to save details of encryption failed messages
|
||||
for (var j = 0; j < c.errLines.length; j++) {
|
||||
var errLine = c.errLines[j];
|
||||
parseErrorLine(errLine, c);
|
||||
if (c.isError) break;
|
||||
}
|
||||
|
||||
detectForgedInsets(c);
|
||||
|
||||
c.retStatusObj.blockSeparation = c.retStatusObj.blockSeparation.replace(/ $/, "");
|
||||
c.retStatusObj.statusFlags = c.statusFlags;
|
||||
if (c.retStatusObj.statusMsg.length === 0) c.retStatusObj.statusMsg = c.statusArray.join("\n");
|
||||
if (c.errorMsg.length === 0) {
|
||||
c.errorMsg = c.errArray.map(function f(str, idx) {
|
||||
return EnigmailSystem.convertNativeToUnicode(str);
|
||||
}, EnigmailSystem).join("\n");
|
||||
} else {
|
||||
c.errorMsg = EnigmailSystem.convertNativeToUnicode(c.errorMsg);
|
||||
}
|
||||
|
||||
if ((c.statusFlags & EnigmailConstants.CARDCTRL) && c.errCode > 0) {
|
||||
c.errorMsg = buildErrorMessageForCardCtrl(c, c.errCode, c.detectedCard);
|
||||
c.statusFlags |= EnigmailConstants.DISPLAY_MESSAGE;
|
||||
}
|
||||
|
||||
let inDecryption = 0;
|
||||
let m;
|
||||
for (let i in c.statusArray) {
|
||||
if (c.statusArray[i].search(/^BEGIN_DECRYPTION( .*)?$/) === 0) {
|
||||
inDecryption = 1;
|
||||
} else if (c.statusArray[i].search(/^END_DECRYPTION( .*)?$/) === 0) {
|
||||
inDecryption = 0;
|
||||
} else if (inDecryption >0) {
|
||||
m = c.statusArray[i].match(/^(PLAINTEXT [0-9]+ [0-9]+ )(.*)$/);
|
||||
if (m && m.length >= 3) c.retStatusObj.encryptedFileName = m[2];
|
||||
}
|
||||
}
|
||||
|
||||
EnigmailLog.DEBUG("errorHandling.jsm: parseErrorOutputWith: statusFlags = " + EnigmailData.bytesToHex(EnigmailData.pack(c.statusFlags, 4)) + "\n");
|
||||
EnigmailLog.DEBUG("errorHandling.jsm: parseErrorOutputWith: return with c.errorMsg = " + c.errorMsg + "\n");
|
||||
return c.errorMsg;
|
||||
}
|
||||
|
||||
var EnigmailErrorHandling = {
|
||||
parseErrorOutput: function(errOutput, retStatusObj) {
|
||||
var context = newContext(errOutput, retStatusObj);
|
||||
return parseErrorOutputWith(context);
|
||||
},
|
||||
|
||||
/**
|
||||
* Determin why a given key or userID cannot be used for signing
|
||||
*
|
||||
|
@ -639,4 +583,4 @@ var EnigmailErrorHandling = {
|
|||
} catch (ex) {}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,525 +0,0 @@
|
|||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
|
||||
"use strict";
|
||||
|
||||
const EXPORTED_SYMBOLS = ["EnigmailExecution"];
|
||||
|
||||
const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
|
||||
const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
|
||||
const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
|
||||
const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
|
||||
const EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules/errorHandling.jsm").EnigmailErrorHandling;
|
||||
const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
|
||||
const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
|
||||
const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
|
||||
|
||||
const loadOS = EnigmailLazy.loader("enigmail/os.jsm", "EnigmailOS");
|
||||
|
||||
var EnigmailExecution = {
|
||||
agentType: "",
|
||||
|
||||
/**
|
||||
* execStart Listener Object
|
||||
*
|
||||
* The listener object must implement at least the following methods:
|
||||
*
|
||||
* stdin(pipe) - OPTIONAL - write data to subprocess stdin via |pipe| hanlde
|
||||
* stdout(data) - receive |data| from subprocess stdout
|
||||
* stderr(data) - receive |data| from subprocess stderr
|
||||
* done(exitCode) - receive signal when subprocess has terminated
|
||||
*/
|
||||
|
||||
/**
|
||||
* start a subprocess (usually gpg) that gets and/or receives data via stdin/stdout/stderr.
|
||||
*
|
||||
* @param {String/nsIFile} command: either full path to executable
|
||||
* or: object referencing executable
|
||||
* @param {Array of Strings} args: command line parameters for executable
|
||||
* @param {Boolean} needPassphrase: is a passphrase required for the action?
|
||||
* (this is currently a no-op)
|
||||
* @param {nsIWindow} domWindow: window on top of which password dialog is shown
|
||||
* @param {Object} listener: Listener to interact with subprocess; see spec. above
|
||||
* @param {Object} statusflagsObj: .value will hold status Flags
|
||||
*
|
||||
* @return {Object}: handle to subprocess
|
||||
*/
|
||||
execStart: function(command, args, needPassphrase, domWindow, listener, statusFlagsObj) {
|
||||
EnigmailLog.WRITE("execution.jsm: execStart: " +
|
||||
"command = " + EnigmailFiles.formatCmdLine(command, args) +
|
||||
", needPassphrase=" + needPassphrase +
|
||||
", domWindow=" + domWindow +
|
||||
", listener=" + listener + "\n");
|
||||
|
||||
listener = listener || {};
|
||||
|
||||
statusFlagsObj.value = 0;
|
||||
|
||||
let proc = null;
|
||||
|
||||
listener.command = command;
|
||||
|
||||
EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
|
||||
|
||||
try {
|
||||
proc = subprocess.call({
|
||||
command: command,
|
||||
arguments: args,
|
||||
environment: EnigmailCore.getEnvList(),
|
||||
charset: null,
|
||||
bufferedOutput: true,
|
||||
stdin: function(pipe) {
|
||||
if (listener.stdin) listener.stdin(pipe);
|
||||
},
|
||||
stdout: function(data) {
|
||||
listener.stdout(data);
|
||||
},
|
||||
stderr: function(data) {
|
||||
listener.stderr(data);
|
||||
},
|
||||
done: function(result) {
|
||||
try {
|
||||
listener.done(result.exitCode);
|
||||
}
|
||||
catch (ex) {
|
||||
EnigmailLog.writeException("execution.jsm", ex);
|
||||
}
|
||||
},
|
||||
mergeStderr: false
|
||||
});
|
||||
}
|
||||
catch (ex) {
|
||||
EnigmailLog.ERROR("execution.jsm: execStart: subprocess.call failed with '" + ex.toString() + "'\n");
|
||||
EnigmailLog.DEBUG(" enigmail> DONE with FAILURE\n");
|
||||
return null;
|
||||
}
|
||||
EnigmailLog.DEBUG(" enigmail> DONE\n");
|
||||
|
||||
return proc;
|
||||
},
|
||||
|
||||
/*
|
||||
requirements for listener object:
|
||||
exitCode
|
||||
stderrData
|
||||
*/
|
||||
execEnd: function(listener, statusFlagsObj, statusMsgObj, cmdLineObj, errorMsgObj, blockSeparationObj) {
|
||||
EnigmailLog.DEBUG("execution.jsm: execEnd:\n");
|
||||
|
||||
cmdLineObj.value = listener.command;
|
||||
|
||||
let exitCode = listener.exitCode;
|
||||
const errOutput = listener.stderrData;
|
||||
|
||||
EnigmailLog.DEBUG("execution.jsm: execEnd: exitCode = " + exitCode + "\n");
|
||||
EnigmailLog.DEBUG("execution.jsm: execEnd: errOutput = " + errOutput.substr(0, 500) + "\n");
|
||||
|
||||
const retObj = {};
|
||||
errorMsgObj.value = EnigmailErrorHandling.parseErrorOutput(errOutput, retObj);
|
||||
statusFlagsObj.value = retObj.statusFlags;
|
||||
statusMsgObj.value = retObj.statusMsg;
|
||||
statusFlagsObj.encryptedFileName = retObj.encryptedFileName;
|
||||
if (!blockSeparationObj) blockSeparationObj = {};
|
||||
blockSeparationObj.value = retObj.blockSeparation;
|
||||
|
||||
if (errOutput.search(/jpeg image of size \d+/) > -1) {
|
||||
statusFlagsObj.value |= EnigmailConstants.PHOTO_AVAILABLE;
|
||||
}
|
||||
if (blockSeparationObj && blockSeparationObj.value.indexOf(" ") > 0) {
|
||||
exitCode = 2;
|
||||
}
|
||||
|
||||
EnigmailLog.CONSOLE(EnigmailData.convertFromUnicode(errorMsgObj.value) + "\n");
|
||||
|
||||
return exitCode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolve the path to the command and execute it if available
|
||||
* Returns output from simpleExecCmd
|
||||
*/
|
||||
resolveAndSimpleExec: function(command, args, exitCodeObj, errorMsgObj) {
|
||||
const resolvedCommand = EnigmailFiles.resolvePathWithEnv(command);
|
||||
if (resolvedCommand === null) {
|
||||
return null;
|
||||
}
|
||||
return EnigmailExecution.simpleExecCmd(resolvedCommand, args, exitCodeObj, errorMsgObj);
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute a command and return the output from stdout
|
||||
* No input and no statusFlags are returned.
|
||||
*/
|
||||
simpleExecCmd: function(command, args, exitCodeObj, errorMsgObj) {
|
||||
EnigmailLog.WRITE("execution.jsm: EnigmailExecution.simpleExecCmd: command = " + command + " " + args.join(" ") + "\n");
|
||||
|
||||
let outputData = "";
|
||||
let errOutput = "";
|
||||
errorMsgObj.value = "";
|
||||
exitCodeObj.value = -1;
|
||||
|
||||
EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
|
||||
|
||||
try {
|
||||
subprocess.call({
|
||||
command: command,
|
||||
arguments: args,
|
||||
charset: null,
|
||||
environment: EnigmailCore.getEnvList(),
|
||||
done: function(result) {
|
||||
exitCodeObj.value = result.exitCode;
|
||||
outputData = result.stdout;
|
||||
errOutput = result.stderr;
|
||||
},
|
||||
mergeStderr: false
|
||||
}).wait();
|
||||
}
|
||||
catch (ex) {
|
||||
EnigmailLog.ERROR("execution.jsm: EnigmailExecution.simpleExecCmd: " + command.path + " failed\n");
|
||||
EnigmailLog.DEBUG(" enigmail> DONE with FAILURE\n");
|
||||
exitCodeObj.value = -1;
|
||||
}
|
||||
EnigmailLog.DEBUG(" enigmail> DONE\n");
|
||||
|
||||
if (errOutput) {
|
||||
errorMsgObj.value = errOutput;
|
||||
}
|
||||
|
||||
EnigmailLog.DEBUG("execution.jsm: EnigmailExecution.simpleExecCmd: exitCode = " + exitCodeObj.value + "\n");
|
||||
EnigmailLog.DEBUG("execution.jsm: EnigmailExecution.simpleExecCmd: errOutput = " + errOutput.substr(0, 500) + "\n");
|
||||
|
||||
return outputData;
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute a command and return the output from stdout.
|
||||
* Accepts input and returns error message and statusFlags.
|
||||
*/
|
||||
execCmd: function(command, args, input, exitCodeObj, statusFlagsObj, statusMsgObj,
|
||||
errorMsgObj, retStatusObj) {
|
||||
EnigmailLog.WRITE("execution.jsm: EnigmailExecution.execCmd: subprocess = '" + command.path + "'\n");
|
||||
|
||||
if ((typeof input) != "string") input = "";
|
||||
|
||||
let preInput = "";
|
||||
let outputData = "";
|
||||
let errOutput = "";
|
||||
EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
|
||||
|
||||
const procBuilder = new EnigmailExecution.processBuilder();
|
||||
procBuilder.setCommand(command);
|
||||
procBuilder.setArguments(args);
|
||||
procBuilder.setEnvironment(EnigmailCore.getEnvList());
|
||||
procBuilder.setStdin(
|
||||
function(pipe) {
|
||||
if (input.length > 0 || preInput.length > 0) {
|
||||
pipe.write(preInput + input);
|
||||
}
|
||||
pipe.close();
|
||||
}
|
||||
);
|
||||
procBuilder.setStdout(
|
||||
function(data) {
|
||||
outputData += data;
|
||||
}
|
||||
);
|
||||
procBuilder.setStderr(
|
||||
function(data) {
|
||||
errOutput += data;
|
||||
}
|
||||
);
|
||||
procBuilder.setDone(
|
||||
function(result) {
|
||||
exitCodeObj.value = result.exitCode;
|
||||
}
|
||||
);
|
||||
|
||||
const proc = procBuilder.build();
|
||||
try {
|
||||
subprocess.call(proc).wait();
|
||||
}
|
||||
catch (ex) {
|
||||
EnigmailLog.ERROR("execution.jsm: EnigmailExecution.execCmd: subprocess.call failed with '" + ex.toString() + "'\n");
|
||||
EnigmailLog.DEBUG(" enigmail> DONE with FAILURE\n");
|
||||
exitCodeObj.value = -1;
|
||||
}
|
||||
EnigmailLog.DEBUG(" enigmail> DONE\n");
|
||||
|
||||
if (proc.resultData) outputData = proc.resultData;
|
||||
if (proc.errorData) errOutput = proc.errorData;
|
||||
|
||||
EnigmailLog.DEBUG("execution.jsm: EnigmailExecution.execCmd: exitCode = " + exitCodeObj.value + "\n");
|
||||
EnigmailLog.DEBUG("execution.jsm: EnigmailExecution.execCmd: errOutput = " + errOutput.substr(0, 500) + "\n");
|
||||
|
||||
|
||||
if (!retStatusObj) {
|
||||
retStatusObj = {};
|
||||
}
|
||||
|
||||
errorMsgObj.value = EnigmailErrorHandling.parseErrorOutput(errOutput, retStatusObj);
|
||||
|
||||
statusFlagsObj.value = retStatusObj.statusFlags;
|
||||
statusMsgObj.value = retStatusObj.statusMsg;
|
||||
const blockSeparation = retStatusObj.blockSeparation;
|
||||
|
||||
exitCodeObj.value = EnigmailExecution.fixExitCode(exitCodeObj.value, statusFlagsObj);
|
||||
|
||||
if (blockSeparation.indexOf(" ") > 0) {
|
||||
exitCodeObj.value = 2;
|
||||
}
|
||||
|
||||
EnigmailLog.CONSOLE(errorMsgObj.value + "\n");
|
||||
|
||||
return outputData;
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute a command and asynchronously, and return a Promise
|
||||
* Accepts input and returns error message and statusFlags.
|
||||
*
|
||||
* @param {String/nsIFile} command: either full path to executable
|
||||
* or: object referencing executable
|
||||
* @param {Array of Strings} args: command line parameters for executable
|
||||
* @param {String} input: data to pass to subprocess via stdin
|
||||
* @param {Object} subprocessHandle: handle to subprocess. The subprocess may be
|
||||
* killed via subprocessHandle.value.killProcess();
|
||||
*
|
||||
* @return {Promise<Object>}: Object with:
|
||||
* - {Number} exitCode
|
||||
* - {String} stdoutData - unmodified data from stdout
|
||||
* - {String} stderrData - unmodified data from stderr
|
||||
* - {String} errorMsg - error message from parseErrorOutput()
|
||||
* - {Number} statusFlags
|
||||
* - {String} statusMsg - pre-processed status messages (without [GNUPG:])
|
||||
* - blockSeparation
|
||||
* - isKilled: 0
|
||||
*/
|
||||
|
||||
execAsync: function(command, args, input, subprocessHandle = null) {
|
||||
EnigmailLog.WRITE("execution.jsm: execAsync: command = '" + command.path + "'\n");
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if ((typeof input) != "string") input = "";
|
||||
|
||||
let outputData = "";
|
||||
let errOutput = "";
|
||||
let returnObj = {
|
||||
exitCode: -1,
|
||||
stdoutData: "",
|
||||
stderrData: "",
|
||||
errorMsg: "",
|
||||
statusFlags: 0,
|
||||
statusMsg: "",
|
||||
blockSeparation: "",
|
||||
isKilled: 0
|
||||
};
|
||||
|
||||
EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
|
||||
EnigmailLog.CONSOLE(input + "\n");
|
||||
|
||||
const procBuilder = new EnigmailExecution.processBuilder();
|
||||
procBuilder.setCommand(command);
|
||||
procBuilder.setArguments(args);
|
||||
procBuilder.setEnvironment(EnigmailCore.getEnvList());
|
||||
procBuilder.setStdin(
|
||||
function(pipe) {
|
||||
if (input.length > 0) {
|
||||
pipe.write(input);
|
||||
}
|
||||
pipe.close();
|
||||
}
|
||||
);
|
||||
procBuilder.setStdout(
|
||||
function(data) {
|
||||
outputData += data;
|
||||
}
|
||||
);
|
||||
procBuilder.setStderr(
|
||||
function(data) {
|
||||
errOutput += data;
|
||||
}
|
||||
);
|
||||
|
||||
procBuilder.setDone(
|
||||
function(result) {
|
||||
let exitCode = result.exitCode;
|
||||
EnigmailLog.DEBUG(" enigmail> DONE\n");
|
||||
EnigmailLog.DEBUG("execution.jsm: execAsync: exitCode = " + exitCode + "\n");
|
||||
EnigmailLog.DEBUG("execution.jsm: execAsync: errOutput = " + errOutput.substr(0, 500) + "\n");
|
||||
|
||||
let retStatusObj = {};
|
||||
let errorMsg = EnigmailErrorHandling.parseErrorOutput(errOutput, retStatusObj);
|
||||
|
||||
let statusFlagsObj = {
|
||||
value: retStatusObj.statusFlags
|
||||
};
|
||||
|
||||
exitCode = EnigmailExecution.fixExitCode(exitCode, statusFlagsObj);
|
||||
|
||||
if (retStatusObj.blockSeparation.indexOf(" ") > 0) {
|
||||
exitCode = 2;
|
||||
}
|
||||
|
||||
EnigmailLog.CONSOLE(errorMsg + "\n");
|
||||
returnObj.exitCode = exitCode;
|
||||
returnObj.stdoutData = outputData;
|
||||
returnObj.stderrData = errOutput;
|
||||
returnObj.errorMsg = errorMsg;
|
||||
returnObj.statusFlags = statusFlagsObj.value;
|
||||
returnObj.statusMsg = retStatusObj.statusMsg;
|
||||
returnObj.blockSeparation = retStatusObj.blockSeparation;
|
||||
|
||||
resolve(returnObj);
|
||||
}
|
||||
);
|
||||
const proc = procBuilder.build();
|
||||
try {
|
||||
let p = subprocess.call(proc);
|
||||
if (subprocessHandle) {
|
||||
p.killProcess = function(hardKill) {
|
||||
returnObj.isKilled = 1;
|
||||
this.kill(hardKill);
|
||||
};
|
||||
subprocessHandle.value = p;
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
EnigmailLog.ERROR("execution.jsm: execAsync: subprocess.call failed with '" + ex.toString() + "'\n");
|
||||
EnigmailLog.DEBUG(" enigmail> DONE with FAILURE\n");
|
||||
reject(returnObj);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fix the exit code of GnuPG (which may be wrong in some circumstances)
|
||||
*
|
||||
* @exitCode: Number - the exitCode obtained from GnuPG
|
||||
* @statusFlagsObj: Object - the statusFlagsObj as received from parseErrorOutput()
|
||||
*
|
||||
* @return: Number - fixed exit code
|
||||
*/
|
||||
fixExitCode: function(exitCode, statusFlagsObj) {
|
||||
EnigmailLog.DEBUG("execution.jsm: EnigmailExecution.fixExitCode: agentType: " + EnigmailExecution.agentType + " exitCode: " + exitCode + " statusFlags " + statusFlagsObj.statusFlags + "\n");
|
||||
|
||||
const statusFlags = statusFlagsObj.statusFlags;
|
||||
|
||||
if (exitCode !== 0) {
|
||||
if ((statusFlags & (EnigmailConstants.BAD_PASSPHRASE | EnigmailConstants.UNVERIFIED_SIGNATURE)) &&
|
||||
(statusFlags & EnigmailConstants.DECRYPTION_OKAY)) {
|
||||
EnigmailLog.DEBUG("enigmailCommon.jsm: Enigmail.fixExitCode: Changing exitCode for decrypted msg " + exitCode + "->0\n");
|
||||
exitCode = 0;
|
||||
}
|
||||
if ((EnigmailExecution.agentType === "gpg") && (exitCode == 256) && (loadOS().getOS() == "WINNT")) {
|
||||
EnigmailLog.WARNING("enigmailCommon.jsm: Enigmail.fixExitCode: Using gpg and exit code is 256. You seem to use cygwin-gpg, activating countermeasures.\n");
|
||||
if (statusFlags & (EnigmailConstants.BAD_PASSPHRASE | EnigmailConstants.UNVERIFIED_SIGNATURE)) {
|
||||
EnigmailLog.WARNING("enigmailCommon.jsm: Enigmail.fixExitCode: Changing exitCode 256->2\n");
|
||||
exitCode = 2;
|
||||
}
|
||||
else {
|
||||
EnigmailLog.WARNING("enigmailCommon.jsm: Enigmail.fixExitCode: Changing exitCode 256->0\n");
|
||||
exitCode = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (statusFlags & (EnigmailConstants.INVALID_RECIPIENT | EnigmailConstants.DECRYPTION_FAILED | EnigmailConstants.BAD_ARMOR |
|
||||
EnigmailConstants.MISSING_PASSPHRASE | EnigmailConstants.BAD_PASSPHRASE)) {
|
||||
exitCode = 1;
|
||||
}
|
||||
else if (typeof(statusFlagsObj.extendedStatus) === "string" && statusFlagsObj.extendedStatus.search(/\bdisp:/) >= 0) {
|
||||
exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return exitCode;
|
||||
},
|
||||
|
||||
processBuilder: function() {
|
||||
this.process = {};
|
||||
this.setCommand = function(command) {
|
||||
this.process.command = command;
|
||||
};
|
||||
this.setArguments = function(args) {
|
||||
this.process.arguments = args;
|
||||
};
|
||||
this.setEnvironment = function(envList) {
|
||||
this.process.environment = envList;
|
||||
};
|
||||
this.setStdin = function(stdin) {
|
||||
this.process.stdin = stdin;
|
||||
};
|
||||
this.setStdout = function(stdout) {
|
||||
this.process.stdout = stdout;
|
||||
};
|
||||
this.setStderr = function(stderr) {
|
||||
this.process.stderr = stderr;
|
||||
};
|
||||
this.setDone = function(done) {
|
||||
this.process.done = done;
|
||||
};
|
||||
this.build = function() {
|
||||
this.process.charset = null;
|
||||
this.process.mergeStderr = false;
|
||||
this.process.resultData = "";
|
||||
this.process.errorData = "";
|
||||
this.process.exitCode = -1;
|
||||
return this.process;
|
||||
};
|
||||
return this;
|
||||
},
|
||||
|
||||
execCmd2: function(command, args, stdinFunc, stdoutFunc, doneFunc) {
|
||||
EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
|
||||
const procBuilder = new EnigmailExecution.processBuilder();
|
||||
procBuilder.setCommand(command);
|
||||
procBuilder.setArguments(args);
|
||||
procBuilder.setEnvironment(EnigmailCore.getEnvList());
|
||||
procBuilder.setStdin(stdinFunc);
|
||||
procBuilder.setStdout(stdoutFunc);
|
||||
procBuilder.setDone(doneFunc);
|
||||
const proc = procBuilder.build();
|
||||
subprocess.call(proc).wait();
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* simple listener for using with execStart
|
||||
*
|
||||
* stdinFunc: optional function to write to stdin
|
||||
* doneFunc : optional function that is called when the process is terminated
|
||||
*/
|
||||
newSimpleListener: function(stdinFunc, doneFunc) {
|
||||
const simpleListener = {
|
||||
stdoutData: "",
|
||||
stderrData: "",
|
||||
exitCode: -1,
|
||||
stdin: function(pipe) {
|
||||
if (stdinFunc) {
|
||||
stdinFunc(pipe);
|
||||
}
|
||||
else {
|
||||
pipe.close();
|
||||
}
|
||||
},
|
||||
stdout: function(data) {
|
||||
simpleListener.stdoutData += data;
|
||||
},
|
||||
stderr: function(data) {
|
||||
simpleListener.stderrData += data;
|
||||
},
|
||||
done: function(exitCode) {
|
||||
simpleListener.exitCode = exitCode;
|
||||
if (doneFunc) {
|
||||
doneFunc(exitCode);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return simpleListener;
|
||||
}
|
||||
};
|
|
@ -17,13 +17,10 @@ const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files
|
|||
const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
|
||||
const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
|
||||
const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
|
||||
const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
|
||||
const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
|
||||
const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
|
||||
const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
|
||||
const EnigmailVersioning = ChromeUtils.import("chrome://openpgp/content/modules/versioning.jsm").EnigmailVersioning;
|
||||
const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
|
||||
const getGpgAgent = EnigmailLazy.loader("enigmail/gpgAgent.jsm", "EnigmailGpgAgent");
|
||||
const getDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog");
|
||||
|
||||
const MINIMUM_GPG_VERSION = "2.0.14";
|
||||
|
@ -46,54 +43,6 @@ function pushTrimmedStr(arr, str, splitStr) {
|
|||
return (str.length > 0);
|
||||
}
|
||||
|
||||
function getDirmngrTorStatus(exitCodeObj) {
|
||||
const command = getGpgAgent().resolveToolPath("gpg-connect-agent");
|
||||
if (command === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const args = ["--dirmngr"];
|
||||
|
||||
EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
|
||||
|
||||
let stdout = "";
|
||||
try {
|
||||
exitCodeObj.value = subprocess.call({
|
||||
command: command,
|
||||
arguments: args,
|
||||
environment: EnigmailCore.getEnvList(),
|
||||
stdin: function(stdin) {
|
||||
stdin.write("GETINFO tor\r\n");
|
||||
stdin.write("bye\r\n");
|
||||
stdin.write("\r\n");
|
||||
stdin.close();
|
||||
},
|
||||
stdout: function(data) {
|
||||
stdout += data;
|
||||
}
|
||||
}).wait();
|
||||
} catch (ex) {
|
||||
exitCodeObj.value = -1;
|
||||
EnigmailLog.DEBUG("enigmail> DONE with FAILURE\n");
|
||||
}
|
||||
|
||||
return stdout;
|
||||
}
|
||||
|
||||
function dirmngrConfiguredWithTor() {
|
||||
if (!EnigmailGpg.getGpgFeature("supports-dirmngr")) return false;
|
||||
|
||||
const exitCodeObj = {
|
||||
value: null
|
||||
};
|
||||
const output = getDirmngrTorStatus(exitCodeObj);
|
||||
|
||||
if (output === null || exitCodeObj.value < 0) {
|
||||
return false;
|
||||
}
|
||||
return output.match(/Tor mode is enabled/) !== null;
|
||||
}
|
||||
|
||||
var EnigmailGpg = {
|
||||
agentVersion: "",
|
||||
_agentPath: null,
|
||||
|
@ -124,7 +73,6 @@ var EnigmailGpg = {
|
|||
genkey-no-protection - is "%no-protection" supported for generting keys (true for gpg >= 2.1)
|
||||
search-keys-cmd - what command to use to terminate the --search-key operation. ("save" for gpg > 2.1; "quit" otherwise)
|
||||
socks-on-windows - is SOCKS proxy supported on Windows (true for gpg >= 2.0.20)
|
||||
supports-dirmngr - is dirmngr supported (true for gpg >= 2.1)
|
||||
supports-ecc-keys - are ECC (elliptic curve) keys supported (true for gpg >= 2.1)
|
||||
supports-sender - does gnupg understand the --sender argument (true for gpg >= 2.1.15)
|
||||
supports-wkd - does gpg support wkd (web key directory) (true for gpg >= 2.1.19)
|
||||
|
@ -162,8 +110,6 @@ var EnigmailGpg = {
|
|||
return EnigmailVersioning.greaterThan(gpgVersion, "2.1");
|
||||
case "windows-photoid-bug":
|
||||
return EnigmailVersioning.lessThan(gpgVersion, "2.0.16");
|
||||
case "supports-dirmngr":
|
||||
return EnigmailVersioning.greaterThan(gpgVersion, "2.1");
|
||||
case "supports-ecc-keys":
|
||||
return EnigmailVersioning.greaterThan(gpgVersion, "2.1");
|
||||
case "socks-on-windows":
|
||||
|
@ -193,149 +139,6 @@ var EnigmailGpg = {
|
|||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* get the standard arguments to pass to every GnuPG subprocess
|
||||
*
|
||||
* @withBatchOpts: Boolean - true: use --batch and some more options
|
||||
* false: don't use --batch and co.
|
||||
*
|
||||
* @return: Array of String - the list of arguments
|
||||
*/
|
||||
getStandardArgs: function(withBatchOpts) {
|
||||
// return the arguments to pass to every GnuPG subprocess
|
||||
let r = ["--charset", "utf-8", "--display-charset", "utf-8", "--no-auto-check-trustdb"]; // mandatory parameters to add in all cases
|
||||
|
||||
try {
|
||||
let p = EnigmailPrefs.getPref("agentAdditionalParam").replace(/\\\\/g, "\\");
|
||||
|
||||
let i = 0;
|
||||
let last = 0;
|
||||
let foundSign = "";
|
||||
let startQuote = -1;
|
||||
|
||||
while ((i = p.substr(last).search(/['"]/)) >= 0) {
|
||||
if (startQuote == -1) {
|
||||
startQuote = i;
|
||||
foundSign = p.substr(last).charAt(i);
|
||||
last = i + 1;
|
||||
} else if (p.substr(last).charAt(i) == foundSign) {
|
||||
// found enquoted part
|
||||
if (startQuote > 1) pushTrimmedStr(r, p.substr(0, startQuote), true);
|
||||
|
||||
pushTrimmedStr(r, p.substr(startQuote + 1, last + i - startQuote - 1), false);
|
||||
p = p.substr(last + i + 1);
|
||||
last = 0;
|
||||
startQuote = -1;
|
||||
foundSign = "";
|
||||
} else {
|
||||
last = last + i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
pushTrimmedStr(r, p, true);
|
||||
} catch (ex) {}
|
||||
|
||||
|
||||
if (withBatchOpts) {
|
||||
r = r.concat(GPG_BATCH_OPT_LIST);
|
||||
}
|
||||
|
||||
return r;
|
||||
},
|
||||
|
||||
// returns the output of --with-colons --list-config
|
||||
getGnupgConfig: function(exitCodeObj, errorMsgObj) {
|
||||
if (!EnigmailGpg.agentPath) {
|
||||
exitCodeObj.value = 0;
|
||||
return "";
|
||||
}
|
||||
|
||||
const args = EnigmailGpg.getStandardArgs(true).
|
||||
concat(["--fixed-list-mode", "--with-colons", "--list-config"]);
|
||||
|
||||
const statusMsgObj = {};
|
||||
const cmdErrorMsgObj = {};
|
||||
const statusFlagsObj = {};
|
||||
|
||||
const listText = EnigmailExecution.execCmd(EnigmailGpg.agentPath, args, "", exitCodeObj, statusFlagsObj, statusMsgObj, cmdErrorMsgObj);
|
||||
|
||||
if (exitCodeObj.value !== 0) {
|
||||
errorMsgObj.value = EnigmailLocale.getString("badCommand");
|
||||
if (cmdErrorMsgObj.value) {
|
||||
errorMsgObj.value += "\n" + EnigmailFiles.formatCmdLine(EnigmailGpg.agentPath, args);
|
||||
errorMsgObj.value += "\n" + cmdErrorMsgObj.value;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
return listText.replace(/(\r\n|\r)/g, "\n");
|
||||
},
|
||||
|
||||
/**
|
||||
* return an array containing the aliases and the email addresses
|
||||
* of groups defined in gpg.conf
|
||||
*
|
||||
* @return: array of objects with the following properties:
|
||||
* - alias: group name as used by GnuPG
|
||||
* - keylist: list of keys (any form that GnuPG accepts), separated by ";"
|
||||
*
|
||||
* (see docu for gnupg parameter --group)
|
||||
*/
|
||||
getGpgGroups: function() {
|
||||
const exitCodeObj = {};
|
||||
const errorMsgObj = {};
|
||||
|
||||
const cfgStr = EnigmailGpg.getGnupgConfig(exitCodeObj, errorMsgObj);
|
||||
|
||||
if (exitCodeObj.value !== 0) {
|
||||
getDialog().alert(errorMsgObj.value);
|
||||
return null;
|
||||
}
|
||||
|
||||
const groups = [];
|
||||
const cfg = cfgStr.split(/\n/);
|
||||
|
||||
for (let i = 0; i < cfg.length; i++) {
|
||||
if (cfg[i].indexOf("cfg:group") === 0) {
|
||||
const groupArr = cfg[i].split(/:/);
|
||||
groups.push({
|
||||
alias: groupArr[2],
|
||||
keylist: groupArr[3]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return groups;
|
||||
},
|
||||
|
||||
/**
|
||||
* Force GnuPG to recalculate the trust db. This is sometimes required after importing keys.
|
||||
*
|
||||
* no return value
|
||||
*/
|
||||
recalcTrustDb: function() {
|
||||
EnigmailLog.DEBUG("enigmailCommon.jsm: recalcTrustDb:\n");
|
||||
|
||||
const command = EnigmailGpg.agentPath;
|
||||
const args = EnigmailGpg.getStandardArgs(false).
|
||||
concat(["--check-trustdb"]);
|
||||
|
||||
try {
|
||||
const proc = subprocess.call({
|
||||
command: EnigmailGpg.agentPath,
|
||||
arguments: args,
|
||||
environment: EnigmailCore.getEnvList(),
|
||||
charset: null,
|
||||
mergeStderr: false
|
||||
});
|
||||
proc.wait();
|
||||
} catch (ex) {
|
||||
EnigmailLog.ERROR("enigmailCommon.jsm: recalcTrustDb: subprocess.call failed with '" + ex.toString() + "'\n");
|
||||
throw ex;
|
||||
}
|
||||
},
|
||||
|
||||
signingAlgIdToString: function(id) {
|
||||
// RFC 4880 Sec. 9.1, RFC 6637 Sec. 5 and draft-koch-eddsa-for-openpgp-03 Sec. 8
|
||||
switch (parseInt(id, 10)) {
|
||||
|
@ -381,11 +184,4 @@ var EnigmailGpg = {
|
|||
return EnigmailLocale.getString("unknownHashAlg", [parseInt(id, 10)]);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* For versions of GPG 2.1 and higher, checks to see if the dirmngr is configured to use Tor
|
||||
*
|
||||
* @return Boolean - True if dirmngr is configured with Tor. False otherwise
|
||||
*/
|
||||
dirmngrConfiguredWithTor: dirmngrConfiguredWithTor
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,803 +0,0 @@
|
|||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["EnigmailGpgAgent"];
|
||||
|
||||
|
||||
|
||||
const ctypes = ChromeUtils.import("resource://gre/modules/ctypes.jsm").ctypes;
|
||||
const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
|
||||
const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
|
||||
const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
|
||||
const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
|
||||
const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
|
||||
const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
|
||||
const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
|
||||
const EnigmailWindows = ChromeUtils.import("chrome://openpgp/content/modules/windows.jsm").EnigmailWindows;
|
||||
const EnigmailApp = ChromeUtils.import("chrome://openpgp/content/modules/app.jsm").EnigmailApp;
|
||||
const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
|
||||
const EnigmailPassword = ChromeUtils.import("chrome://openpgp/content/modules/passwords.jsm").EnigmailPassword;
|
||||
const EnigmailSystem = ChromeUtils.import("chrome://openpgp/content/modules/system.jsm").EnigmailSystem;
|
||||
const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
|
||||
const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
|
||||
const getEnigmailGpg = EnigmailLazy.loader("enigmail/gpg.jsm", "EnigmailGpg");
|
||||
const getDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const NS_LOCAL_FILE_CONTRACTID = "@mozilla.org/file/local;1";
|
||||
const DIR_SERV_CONTRACTID = "@mozilla.org/file/directory_service;1";
|
||||
const NS_LOCALFILEOUTPUTSTREAM_CONTRACTID = "@mozilla.org/network/file-output-stream;1";
|
||||
|
||||
const DEFAULT_FILE_PERMS = 0o600;
|
||||
|
||||
// Making this a var makes it possible to test windows things on linux
|
||||
var nsIWindowsRegKey = Ci.nsIWindowsRegKey;
|
||||
|
||||
var gIsGpgAgent = -1;
|
||||
|
||||
const DUMMY_AGENT_INFO = "none";
|
||||
|
||||
function cloneOrNull(v) {
|
||||
if (v && typeof v.clone === "function") {
|
||||
return v.clone();
|
||||
}
|
||||
else {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
function extractAgentInfo(fullStr) {
|
||||
if (fullStr) {
|
||||
return fullStr.
|
||||
replace(/[\r\n]/g, "").
|
||||
replace(/^.*=/, "").
|
||||
replace(/;.*$/, "");
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function getHomedirFromParam(param) {
|
||||
let i = param.search(/--homedir/);
|
||||
if (i >= 0) {
|
||||
param = param.substr(i + 9);
|
||||
|
||||
let m = param.match(/^(\s*)([^\\]".+[^\\]")/);
|
||||
if (m && m.length > 2) {
|
||||
param = m[2].substr(1);
|
||||
let j = param.search(/[^\\]"/);
|
||||
return param.substr(1, j);
|
||||
}
|
||||
|
||||
m = param.match(/^(\s*)([^\\]'.+[^\\]')/);
|
||||
if (m && m.length > 2) {
|
||||
param = m[2].substr(1);
|
||||
let j = param.search(/[^\\]'/);
|
||||
return param.substr(1, j);
|
||||
}
|
||||
|
||||
m = param.match(/^(\s*)(\S+)/);
|
||||
if (m && m.length > 2) {
|
||||
return m[2];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
var EnigmailGpgAgent = {
|
||||
agentType: "",
|
||||
agentPath: null,
|
||||
connGpgAgentPath: null,
|
||||
gpgconfPath: null,
|
||||
gpgAgentInfo: {
|
||||
preStarted: false,
|
||||
envStr: ""
|
||||
},
|
||||
gpgAgentProcess: null,
|
||||
gpgAgentIsOptional: true,
|
||||
|
||||
isDummy: function() {
|
||||
return EnigmailGpgAgent.gpgAgentInfo.envStr === DUMMY_AGENT_INFO;
|
||||
},
|
||||
|
||||
resetGpgAgent: function() {
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: resetGpgAgent\n");
|
||||
gIsGpgAgent = -1;
|
||||
},
|
||||
|
||||
isCmdGpgAgent: function(pid) {
|
||||
console.debug("reaching disabled EnigmailGpgAgent.isCmdGpgAgent()");
|
||||
return;
|
||||
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: isCmdGpgAgent:\n");
|
||||
|
||||
const environment = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
|
||||
let ret = false;
|
||||
|
||||
let path = environment.get("PATH");
|
||||
if (!path || path.length === 0) {
|
||||
path = "/bin:/usr/bin:/usr/local/bin";
|
||||
}
|
||||
|
||||
const psCmd = EnigmailFiles.resolvePath("ps", path, false);
|
||||
let outStr = "";
|
||||
|
||||
const proc = {
|
||||
command: psCmd,
|
||||
arguments: ["-o", "comm", "-p", pid],
|
||||
environment: EnigmailCore.getEnvList(),
|
||||
charset: null,
|
||||
stdout: function(data) {
|
||||
outStr += data;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
subprocess.call(proc).wait();
|
||||
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: isCmdGpgAgent: got data: '" + outStr + "'\n");
|
||||
var data = outStr.replace(/[\r\n]/g, " ");
|
||||
if (data.search(/gpg-agent/) >= 0) {
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
catch (ex) {}
|
||||
|
||||
return ret;
|
||||
|
||||
},
|
||||
|
||||
isAgentTypeGpgAgent: function() {
|
||||
console.debug("reaching disabled EnigmailGpgAgent.isAgentTypeGpgAgent()");
|
||||
return;
|
||||
|
||||
// determine if the used agent is a gpg-agent
|
||||
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: isAgentTypeGpgAgent:\n");
|
||||
|
||||
// to my knowledge there is no other agent than gpg-agent on Windows
|
||||
if (EnigmailOS.getOS() == "WINNT") return true;
|
||||
|
||||
if (gIsGpgAgent >= 0) {
|
||||
return gIsGpgAgent == 1;
|
||||
}
|
||||
|
||||
let pid = -1;
|
||||
let exitCode = -1;
|
||||
let outStr = "";
|
||||
if (!EnigmailCore.getService()) return false;
|
||||
|
||||
const proc = {
|
||||
command: EnigmailGpgAgent.connGpgAgentPath,
|
||||
arguments: [],
|
||||
charset: null,
|
||||
environment: EnigmailCore.getEnvList(),
|
||||
stdin: function(pipe) {
|
||||
pipe.write("/subst\n");
|
||||
pipe.write("/serverpid\n");
|
||||
pipe.write("/echo pid: ${get serverpid}\n");
|
||||
pipe.write("/bye\n");
|
||||
pipe.close();
|
||||
},
|
||||
stdout: function(data) {
|
||||
outStr += data;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
exitCode = subprocess.call(proc).wait();
|
||||
if (exitCode) pid = -2;
|
||||
|
||||
const data = outStr.replace(/[\r\n]/g, "");
|
||||
if (data.search(/^pid: [0-9]+$/) === 0) {
|
||||
pid = data.replace(/^pid: /, "");
|
||||
}
|
||||
}
|
||||
catch (ex) {}
|
||||
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: isAgentTypeGpgAgent: pid=" + pid + "\n");
|
||||
|
||||
EnigmailGpgAgent.isCmdGpgAgent(pid);
|
||||
let isAgent = false;
|
||||
|
||||
try {
|
||||
isAgent = EnigmailGpgAgent.isCmdGpgAgent(pid);
|
||||
gIsGpgAgent = isAgent ? 1 : 0;
|
||||
}
|
||||
catch (ex) {}
|
||||
|
||||
return isAgent;
|
||||
},
|
||||
|
||||
getAgentMaxIdle: function() {
|
||||
console.debug("reaching disabled EnigmailGpgAgent.getAgentMaxIdle()");
|
||||
return;
|
||||
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: getAgentMaxIdle:\n");
|
||||
let maxIdle = -1;
|
||||
|
||||
if (!EnigmailCore.getService()) return maxIdle;
|
||||
|
||||
const DEFAULT = 7;
|
||||
const CFGVALUE = 9;
|
||||
let outStr = "";
|
||||
|
||||
const proc = {
|
||||
command: EnigmailGpgAgent.gpgconfPath,
|
||||
arguments: ["--list-options", "gpg-agent"],
|
||||
charset: null,
|
||||
environment: EnigmailCore.getEnvList(),
|
||||
stdout: function(data) {
|
||||
outStr += data;
|
||||
}
|
||||
};
|
||||
|
||||
subprocess.call(proc).wait();
|
||||
|
||||
const lines = outStr.split(/[\r\n]/);
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: getAgentMaxIdle: line: " + lines[i] + "\n");
|
||||
|
||||
if (lines[i].search(/^default-cache-ttl:/) === 0) {
|
||||
const m = lines[i].split(/:/);
|
||||
if (m[CFGVALUE].length === 0) {
|
||||
maxIdle = Math.round(m[DEFAULT] / 60);
|
||||
}
|
||||
else {
|
||||
maxIdle = Math.round(m[CFGVALUE] / 60);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return maxIdle;
|
||||
},
|
||||
|
||||
setAgentMaxIdle: function(idleMinutes) {
|
||||
console.debug("reaching disabled EnigmailGpgAgent.setAgentMaxIdle()");
|
||||
return;
|
||||
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: setAgentMaxIdle:\n");
|
||||
if (!EnigmailCore.getService()) return;
|
||||
|
||||
const RUNTIME = 8;
|
||||
|
||||
const proc = {
|
||||
command: EnigmailGpgAgent.gpgconfPath,
|
||||
arguments: ["--runtime", "--change-options", "gpg-agent"],
|
||||
environment: EnigmailCore.getEnvList(),
|
||||
charset: null,
|
||||
mergeStderr: true,
|
||||
stdin: function(pipe) {
|
||||
pipe.write("default-cache-ttl:" + RUNTIME + ":" + (idleMinutes * 60) + "\n");
|
||||
pipe.write("max-cache-ttl:" + RUNTIME + ":" + (idleMinutes * 600) + "\n");
|
||||
pipe.close();
|
||||
},
|
||||
stdout: function(data) {
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: setAgentMaxIdle.stdout: " + data + "\n");
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
let exitCode = subprocess.call(proc);
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: setAgentMaxIdle.stdout: gpgconf exitCode=" + exitCode + "\n");
|
||||
}
|
||||
catch (ex) {
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: setAgentMaxIdle: exception: " + ex.toString() + "\n");
|
||||
}
|
||||
},
|
||||
|
||||
getMaxIdlePref: function(win) {
|
||||
let maxIdle = EnigmailPrefs.getPref("maxIdleMinutes");
|
||||
|
||||
try {
|
||||
if (EnigmailCore.getService(win)) {
|
||||
if (EnigmailGpgAgent.gpgconfPath &&
|
||||
EnigmailGpgAgent.connGpgAgentPath) {
|
||||
|
||||
if (EnigmailGpgAgent.isAgentTypeGpgAgent()) {
|
||||
const m = EnigmailGpgAgent.getAgentMaxIdle();
|
||||
if (m > -1) maxIdle = m;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ex) {}
|
||||
|
||||
return maxIdle;
|
||||
},
|
||||
|
||||
setMaxIdlePref: function(minutes) {
|
||||
EnigmailPrefs.setPref("maxIdleMinutes", minutes);
|
||||
|
||||
if (EnigmailGpgAgent.isAgentTypeGpgAgent()) {
|
||||
try {
|
||||
EnigmailGpgAgent.setAgentMaxIdle(minutes);
|
||||
}
|
||||
catch (ex) {}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine the "gpg home dir", i.e. the directory where gpg.conf and the keyring are
|
||||
* stored using the "additional parameter" and gpgconf.
|
||||
*
|
||||
* @return String - directory name, or NULL (in case the command did not succeed)
|
||||
*/
|
||||
getGpgHomeDir: function() {
|
||||
console.debug("reaching disabled EnigmailGpgAgent.getGpgHomeDir()");
|
||||
return;
|
||||
|
||||
let param = EnigmailPrefs.getPref("agentAdditionalParam");
|
||||
|
||||
if (param) {
|
||||
let hd = getHomedirFromParam(param);
|
||||
|
||||
if (hd) return hd;
|
||||
}
|
||||
|
||||
if (EnigmailGpgAgent.gpgconfPath === null) return null;
|
||||
|
||||
const command = EnigmailGpgAgent.gpgconfPath;
|
||||
let args = ["--list-dirs"];
|
||||
|
||||
let exitCode = -1;
|
||||
let outStr = "";
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: getGpgHomeDir: calling subprocess with '" + command.path + "'\n");
|
||||
|
||||
EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
|
||||
|
||||
const proc = {
|
||||
command: command,
|
||||
arguments: args,
|
||||
environment: EnigmailCore.getEnvList(),
|
||||
charset: null,
|
||||
stdout: function(data) {
|
||||
outStr += data;
|
||||
},
|
||||
mergeStderr: false
|
||||
};
|
||||
|
||||
try {
|
||||
exitCode = subprocess.call(proc).wait();
|
||||
}
|
||||
catch (ex) {
|
||||
EnigmailLog.ERROR("gpgAgent.jsm: getGpgHomeDir: subprocess.call failed with '" + ex.toString() + "'\n");
|
||||
EnigmailLog.DEBUG(" enigmail> DONE with FAILURE\n");
|
||||
throw ex;
|
||||
}
|
||||
|
||||
let m = outStr.match(/^(homedir:)(.*)$/mi);
|
||||
if (m && m.length > 2) {
|
||||
return EnigmailData.convertGpgToUnicode(unescape(m[2]));
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param domWindow: Object - parent window, may be NULL
|
||||
* @param esvc: Object - Enigmail service object
|
||||
* @param preferredPath: String - try to use specific path to locate gpg
|
||||
*/
|
||||
setAgentPath: function(domWindow, esvc, preferredPath) {
|
||||
console.debug("reaching disabled EnigmailGpgAgent.setAgentPath()");
|
||||
return;
|
||||
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: setAgentPath()\n");
|
||||
let agentPath = "";
|
||||
try {
|
||||
if (preferredPath) {
|
||||
agentPath = preferredPath;
|
||||
}
|
||||
else {
|
||||
agentPath = EnigmailPrefs.getPrefBranch().getCharPref("agentPath");
|
||||
}
|
||||
}
|
||||
catch (ex) {}
|
||||
|
||||
var agentType = "gpg";
|
||||
var agentName = "";
|
||||
|
||||
EnigmailGpgAgent.resetGpgAgent();
|
||||
|
||||
if (agentPath) {
|
||||
// Locate GnuPG executable
|
||||
|
||||
// Append default .exe extension for DOS-Like systems, if needed
|
||||
if (EnigmailOS.isDosLike && (agentPath.search(/\.\w+$/) < 0)) {
|
||||
agentPath += ".exe";
|
||||
}
|
||||
|
||||
try {
|
||||
let pathDir = Cc[NS_LOCAL_FILE_CONTRACTID].createInstance(Ci.nsIFile);
|
||||
|
||||
if (!EnigmailFiles.isAbsolutePath(agentPath, EnigmailOS.isDosLike)) {
|
||||
// path relative to Mozilla installation dir
|
||||
const ds = Cc[DIR_SERV_CONTRACTID].getService();
|
||||
const dsprops = ds.QueryInterface(Ci.nsIProperties);
|
||||
pathDir = dsprops.get("CurProcD", Ci.nsIFile);
|
||||
|
||||
const dirs = agentPath.split(new RegExp(EnigmailOS.isDosLike ? "\\\\" : "/"));
|
||||
for (let i = 0; i < dirs.length; i++) {
|
||||
if (dirs[i] != ".") {
|
||||
pathDir.append(dirs[i]);
|
||||
}
|
||||
}
|
||||
if (pathDir.exists()) {
|
||||
pathDir.normalize();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// absolute path
|
||||
EnigmailFiles.initPath(pathDir, agentPath);
|
||||
}
|
||||
if (!(pathDir.isFile() /* && pathDir.isExecutable()*/ )) {
|
||||
throw Components.results.NS_ERROR_FAILURE;
|
||||
}
|
||||
agentPath = pathDir.QueryInterface(Ci.nsIFile);
|
||||
|
||||
}
|
||||
catch (ex) {
|
||||
esvc.initializationError = EnigmailLocale.getString("gpgNotFound", [agentPath]);
|
||||
EnigmailLog.ERROR("gpgAgent.jsm: initialize: Error - " + esvc.initializationError + "\n");
|
||||
throw Components.results.NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
agentPath = this.resolveGpgPath(esvc.environment);
|
||||
if (!agentPath) {
|
||||
esvc.initializationError = EnigmailLocale.getString("gpgNotInPath");
|
||||
EnigmailLog.ERROR("gpgAgent.jsm: Error - " + esvc.initializationError + "\n");
|
||||
throw Components.results.NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
agentPath.normalize(); // replace a/../b with b
|
||||
EnigmailLog.CONSOLE("EnigmailAgentPath=" + EnigmailFiles.getFilePathDesc(agentPath) + "\n\n");
|
||||
|
||||
EnigmailGpgAgent.agentType = agentType;
|
||||
EnigmailGpgAgent.agentPath = agentPath;
|
||||
getEnigmailGpg().setAgentPath(agentPath);
|
||||
EnigmailExecution.agentType = agentType;
|
||||
|
||||
const command = agentPath;
|
||||
let args = [];
|
||||
if (agentType == "gpg") {
|
||||
args = ["--batch", "--no-tty", "--charset", "utf-8", "--display-charset", "utf-8", "--version", "--version"];
|
||||
}
|
||||
|
||||
let exitCode = -1;
|
||||
let outStr = "";
|
||||
let errStr = "";
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: setAgentPath: calling subprocess with '" + command.path + "'\n");
|
||||
|
||||
EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
|
||||
|
||||
const proc = {
|
||||
command: command,
|
||||
arguments: args,
|
||||
environment: EnigmailCore.getEnvList(),
|
||||
charset: null,
|
||||
stdout: function(data) {
|
||||
outStr += data;
|
||||
},
|
||||
stderr: function(data) {
|
||||
errStr += data;
|
||||
},
|
||||
mergeStderr: false
|
||||
};
|
||||
|
||||
try {
|
||||
exitCode = subprocess.call(proc).wait();
|
||||
}
|
||||
catch (ex) {
|
||||
EnigmailLog.ERROR("gpgAgent.jsm: setAgentPath: subprocess.call failed with '" + ex.toString() + "'\n");
|
||||
EnigmailLog.DEBUG(" enigmail> DONE with FAILURE\n");
|
||||
throw ex;
|
||||
}
|
||||
EnigmailLog.DEBUG(" enigmail> DONE\n");
|
||||
|
||||
outStr = EnigmailSystem.convertNativeToUnicode(outStr);
|
||||
|
||||
if (exitCode !== 0) {
|
||||
EnigmailLog.ERROR("gpgAgent.jsm: setAgentPath: gpg failed with exitCode " + exitCode + " msg='" + outStr + " " + errStr + "'\n");
|
||||
throw Components.results.NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
EnigmailLog.CONSOLE(outStr + "\n");
|
||||
|
||||
// detection for Gpg4Win wrapper
|
||||
if (outStr.search(/^gpgwrap.*;/) === 0) {
|
||||
const outLines = outStr.split(/[\n\r]+/);
|
||||
const firstLine = outLines[0];
|
||||
outLines.splice(0, 1);
|
||||
outStr = outLines.join("\n");
|
||||
agentPath = firstLine.replace(/^.*;[ \t]*/, "");
|
||||
|
||||
EnigmailLog.CONSOLE("gpg4win-gpgwrapper detected; EnigmailAgentPath=" + agentPath + "\n\n");
|
||||
}
|
||||
|
||||
const versionParts = outStr.replace(/[\r\n].*/g, "").replace(/ *\(gpg4win.*\)/i, "").split(/ /);
|
||||
const gpgVersion = versionParts[versionParts.length - 1];
|
||||
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: detected GnuPG version '" + gpgVersion + "'\n");
|
||||
getEnigmailGpg().agentVersion = gpgVersion;
|
||||
|
||||
if (!getEnigmailGpg().getGpgFeature("version-supported")) {
|
||||
if (!domWindow) {
|
||||
domWindow = EnigmailWindows.getBestParentWin();
|
||||
}
|
||||
getDialog().alert(domWindow, EnigmailLocale.getString("oldGpgVersion20", [gpgVersion, getEnigmailGpg().getMinimumGpgVersion()]));
|
||||
throw Components.results.NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
EnigmailGpgAgent.gpgconfPath = EnigmailGpgAgent.resolveToolPath("gpgconf");
|
||||
EnigmailGpgAgent.connGpgAgentPath = EnigmailGpgAgent.resolveToolPath("gpg-connect-agent");
|
||||
|
||||
EnigmailGpgAgent.checkGpgHomeDir(domWindow, esvc);
|
||||
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: setAgentPath: gpgconf found: " + (EnigmailGpgAgent.gpgconfPath ? "yes" : "no") + "\n");
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Determine the location of the GnuPG executable
|
||||
*
|
||||
* @param env: Object: nsIEnvironment to use
|
||||
*
|
||||
* @return Object: nsIFile pointing to gpg, or NULL
|
||||
*/
|
||||
resolveGpgPath: function(env) {
|
||||
console.debug("reaching disabled EnigmailGpgAgent.resolveGpgPath()");
|
||||
return;
|
||||
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: resolveGpgPath()\n");
|
||||
|
||||
let agentName = "";
|
||||
if (EnigmailOS.isDosLike) {
|
||||
agentName = "gpg2.exe;gpg.exe";
|
||||
}
|
||||
else {
|
||||
agentName = "gpg2;gpg";
|
||||
}
|
||||
|
||||
// Resolve relative path using PATH environment variable
|
||||
const envPath = env.get("PATH");
|
||||
let agentPath = EnigmailFiles.resolvePath(agentName, envPath, EnigmailOS.isDosLike);
|
||||
|
||||
if (!agentPath && EnigmailOS.isDosLike) {
|
||||
// DOS-like systems: search for GPG in c:\gnupg, c:\gnupg\bin, d:\gnupg, d:\gnupg\bin
|
||||
let gpgPath = "c:\\gnupg;c:\\gnupg\\bin;d:\\gnupg;d:\\gnupg\\bin";
|
||||
agentPath = EnigmailFiles.resolvePath(agentName, gpgPath, EnigmailOS.isDosLike);
|
||||
}
|
||||
|
||||
if ((!agentPath) && EnigmailOS.isWin32) {
|
||||
// Look up in Windows Registry
|
||||
const installDir = ["Software\\GNU\\GNUPG", "Software\\GNUPG"];
|
||||
|
||||
try {
|
||||
for (let i = 0; i < installDir.length && !agentPath; i++) {
|
||||
let gpgPath = EnigmailOS.getWinRegistryString(installDir[i], "Install Directory", nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE);
|
||||
|
||||
agentPath = EnigmailFiles.resolvePath(agentName, gpgPath, EnigmailOS.isDosLike());
|
||||
if (!agentPath) {
|
||||
gpgPath += "\\bin";
|
||||
agentPath = EnigmailFiles.resolvePath(agentName, gpgPath, EnigmailOS.isDosLike());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ex) {}
|
||||
|
||||
if (!agentPath) {
|
||||
// try to determine the default PATH from the registry after the installation
|
||||
// if we could not get any information from the registry
|
||||
try {
|
||||
let winPath = EnigmailOS.getWinRegistryString("SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", "Path", nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE);
|
||||
agentPath = EnigmailFiles.resolvePath(agentName, winPath, EnigmailOS.isDosLike);
|
||||
}
|
||||
catch (ex) {}
|
||||
}
|
||||
|
||||
if (!agentPath) {
|
||||
// default for gpg4win 3.0
|
||||
let gpgPath = "C:\\Program Files\\GnuPG\\bin;C:\\Program Files (x86)\\GnuPG\\bin";
|
||||
agentPath = EnigmailFiles.resolvePath(agentName, gpgPath, EnigmailOS.isDosLike);
|
||||
}
|
||||
}
|
||||
|
||||
if (!agentPath && !EnigmailOS.isDosLike) {
|
||||
// Unix-like systems: check /usr/bin and /usr/local/bin
|
||||
let gpgPath = "/usr/bin:/usr/local/bin";
|
||||
agentPath = EnigmailFiles.resolvePath(agentName, gpgPath, EnigmailOS.isDosLike);
|
||||
}
|
||||
|
||||
if (!agentPath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return agentPath.QueryInterface(Ci.nsIFile);
|
||||
},
|
||||
|
||||
// resolve the path for GnuPG helper tools
|
||||
resolveToolPath: function(fileName) {
|
||||
let filePath = cloneOrNull(EnigmailGpgAgent.agentPath);
|
||||
|
||||
if (filePath) {
|
||||
// try to get the install directory of gpg/gpg2 executable
|
||||
filePath.normalize();
|
||||
filePath = filePath.parent;
|
||||
}
|
||||
|
||||
if (filePath) {
|
||||
filePath.append(EnigmailFiles.potentialWindowsExecutable(fileName));
|
||||
if (filePath.exists()) {
|
||||
filePath.normalize();
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
|
||||
return EnigmailFiles.resolvePathWithEnv(fileName);
|
||||
},
|
||||
|
||||
detectGpgAgent: function(domWindow, esvc) {
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: detectGpgAgent\n");
|
||||
|
||||
var gpgAgentInfo = esvc.environment.get("GPG_AGENT_INFO");
|
||||
if (gpgAgentInfo && gpgAgentInfo.length > 0) {
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: detectGpgAgent: GPG_AGENT_INFO variable available\n");
|
||||
// env. variable suggests running gpg-agent
|
||||
EnigmailGpgAgent.gpgAgentInfo.preStarted = true;
|
||||
EnigmailGpgAgent.gpgAgentInfo.envStr = gpgAgentInfo;
|
||||
EnigmailGpgAgent.gpgAgentIsOptional = false;
|
||||
}
|
||||
else {
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: detectGpgAgent: no GPG_AGENT_INFO variable set\n");
|
||||
EnigmailGpgAgent.gpgAgentInfo.preStarted = false;
|
||||
|
||||
if (!getEnigmailGpg().getGpgFeature("supports-gpg-agent")) {
|
||||
esvc.initializationError = EnigmailLocale.getString("gpgAgent.noAutostart", getEnigmailGpg().agentVersion);
|
||||
EnigmailLog.ERROR("gpgAgent.jsm: Error - " + esvc.initializationError + "\n");
|
||||
throw Components.results.NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
var command = null;
|
||||
var outStr = "";
|
||||
var errorStr = "";
|
||||
var exitCode = -1;
|
||||
EnigmailGpgAgent.gpgAgentIsOptional = false;
|
||||
|
||||
|
||||
EnigmailGpgAgent.gpgAgentInfo.envStr = DUMMY_AGENT_INFO;
|
||||
var envFile = Components.classes[NS_LOCAL_FILE_CONTRACTID].createInstance(Ci.nsIFile);
|
||||
EnigmailFiles.initPath(envFile, EnigmailGpgAgent.determineGpgHomeDir(esvc));
|
||||
envFile.append("gpg-agent.conf");
|
||||
|
||||
if (!envFile.exists()) {
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: detectGpgAgent: writing gpg-agent.conf file\n");
|
||||
let data = "default-cache-ttl " + (EnigmailPassword.getMaxIdleMinutes() * 60) + "\n";
|
||||
data += "max-cache-ttl 999999\n";
|
||||
try {
|
||||
var flags = 0x02 | 0x08 | 0x20;
|
||||
var fileOutStream = Cc[NS_LOCALFILEOUTPUTSTREAM_CONTRACTID].createInstance(Ci.nsIFileOutputStream);
|
||||
fileOutStream.init(envFile, flags, 384, 0); // 0600
|
||||
fileOutStream.write(data, data.length);
|
||||
fileOutStream.flush();
|
||||
fileOutStream.close();
|
||||
}
|
||||
catch (ex) {} // ignore file write errors
|
||||
}
|
||||
|
||||
}
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: detectGpgAgent: GPG_AGENT_INFO='" + EnigmailGpgAgent.gpgAgentInfo.envStr + "'\n");
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine the GnuPG home directory based on the same logic as GnuPG, but without involving
|
||||
* any external tool.
|
||||
*
|
||||
* @return String - the path to the gpg home directory
|
||||
*/
|
||||
determineGpgHomeDir: function(esvc) {
|
||||
|
||||
let param = EnigmailPrefs.getPref("agentAdditionalParam");
|
||||
if (param) {
|
||||
let hd = getHomedirFromParam(param);
|
||||
|
||||
if (hd) return hd;
|
||||
}
|
||||
|
||||
let homeDir = esvc.environment.get("GNUPGHOME");
|
||||
|
||||
if (!homeDir && EnigmailOS.isWin32) {
|
||||
homeDir = EnigmailOS.getWinRegistryString("Software\\GNU\\GNUPG", "HomeDir", nsIWindowsRegKey.ROOT_KEY_CURRENT_USER);
|
||||
|
||||
if (!homeDir) {
|
||||
homeDir = esvc.environment.get("USERPROFILE") || esvc.environment.get("SystemRoot");
|
||||
|
||||
if (homeDir) homeDir += "\\Application Data\\GnuPG";
|
||||
}
|
||||
|
||||
if (!homeDir) homeDir = "C:\\gnupg";
|
||||
}
|
||||
|
||||
if (!homeDir) homeDir = esvc.environment.get("HOME") + "/.gnupg";
|
||||
|
||||
return homeDir;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the users directory for GnuPG exists and is writeable.
|
||||
* Throw exception if directory cannot be created or adjusted.
|
||||
*/
|
||||
checkGpgHomeDir: function(domWindow, esvc) {
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: checkGpgHomeDir:\n");
|
||||
|
||||
let homeDir = EnigmailGpgAgent.getGpgHomeDir();
|
||||
if (!homeDir) homeDir = EnigmailGpgAgent.determineGpgHomeDir(esvc);
|
||||
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: checkGpgHomeDir: got homedir = '" + homeDir + "'\n");
|
||||
|
||||
let homeDirObj = Components.classes[NS_LOCAL_FILE_CONTRACTID].createInstance(Ci.nsIFile);
|
||||
EnigmailFiles.initPath(homeDirObj, homeDir);
|
||||
|
||||
if (homeDirObj.exists()) {
|
||||
homeDirObj.normalize(); // resolve symlinks etc.
|
||||
}
|
||||
|
||||
let dirType = EnigmailFiles.ensureWritableDirectory(homeDirObj, 0x1C0); // 0700
|
||||
let errMsg = "";
|
||||
switch (dirType) {
|
||||
case 1:
|
||||
errMsg = "gpghomedir.notexists";
|
||||
break;
|
||||
case 2:
|
||||
errMsg = "gpghomedir.notwritable";
|
||||
break;
|
||||
case 3:
|
||||
errMsg = "gpghomedir.notdirectory";
|
||||
break;
|
||||
}
|
||||
|
||||
if (errMsg.length > 0) {
|
||||
if (!domWindow) {
|
||||
domWindow = EnigmailWindows.getBestParentWin();
|
||||
}
|
||||
getDialog().alert(domWindow, EnigmailLocale.getString(errMsg, homeDir) + "\n\n" + EnigmailLocale.getString("gpghomedir.notusable"));
|
||||
throw Components.results.NS_ERROR_FAILURE;
|
||||
}
|
||||
},
|
||||
|
||||
finalize: function() {
|
||||
console.debug("reaching disabled EnigmailGpgAgent.finalize()");
|
||||
return;
|
||||
|
||||
if (EnigmailGpgAgent.gpgAgentProcess) {
|
||||
EnigmailLog.DEBUG("gpgAgent.jsm: EnigmailGpgAgent.finalize: stopping gpg-agent\n");
|
||||
try {
|
||||
const proc = {
|
||||
command: EnigmailGpgAgent.connGpgAgentPath,
|
||||
arguments: ['killagent', '/bye'],
|
||||
environment: EnigmailCore.getEnvList()
|
||||
};
|
||||
|
||||
subprocess.call(proc).wait();
|
||||
}
|
||||
catch (ex) {
|
||||
EnigmailLog.ERROR("gpgAgent.jsm: EnigmailGpgAgent.finalize ERROR: " + ex + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -10,7 +10,6 @@ var EXPORTED_SYMBOLS = ["EnigmailKeyRing"];
|
|||
|
||||
const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
|
||||
const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
|
||||
const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
|
||||
const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
|
||||
const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
|
||||
const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
|
||||
|
@ -18,7 +17,6 @@ const EnigmailTrust = ChromeUtils.import("chrome://openpgp/content/modules/trust
|
|||
const EnigmailArmor = ChromeUtils.import("chrome://openpgp/content/modules/armor.jsm").EnigmailArmor;
|
||||
const EnigmailTime = ChromeUtils.import("chrome://openpgp/content/modules/time.jsm").EnigmailTime;
|
||||
const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
|
||||
//const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
|
||||
const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
|
||||
const newEnigmailKeyObj = ChromeUtils.import("chrome://openpgp/content/modules/keyObj.jsm").newEnigmailKeyObj;
|
||||
const EnigmailTimer = ChromeUtils.import("chrome://openpgp/content/modules/timer.jsm").EnigmailTimer;
|
||||
|
@ -33,7 +31,6 @@ const getKeyUsability = EnigmailLazy.loader("enigmail/keyUsability.jsm", "Enigma
|
|||
|
||||
const DEFAULT_FILE_PERMS = 0o600;
|
||||
|
||||
let gKeygenProcess = null;
|
||||
let gKeyListObj = null;
|
||||
let gKeyIndex = [];
|
||||
let gSubkeyIndex = [];
|
||||
|
@ -421,61 +418,6 @@ var EnigmailKeyRing = {
|
|||
return "";
|
||||
}
|
||||
|
||||
/*
|
||||
let args = EnigmailGpg.getStandardArgs(true).concat(["-a", "--export"]);
|
||||
|
||||
if (userId) {
|
||||
args = args.concat(userId.split(/[ ,\t]+/));
|
||||
}
|
||||
|
||||
const cmdErrorMsgObj = {};
|
||||
let keyBlock = EnigmailExecution.execCmd(EnigmailGpg.agentPath, args, "", exitCodeObj, {}, {}, cmdErrorMsgObj);
|
||||
|
||||
if ((exitCodeObj.value === 0) && !keyBlock) {
|
||||
exitCodeObj.value = -1;
|
||||
}
|
||||
|
||||
if (exitCodeObj.value !== 0) {
|
||||
errorMsgObj.value = EnigmailLocale.getString("failKeyExtract");
|
||||
|
||||
if (cmdErrorMsgObj.value) {
|
||||
errorMsgObj.value += "\n" + EnigmailFiles.formatCmdLine(EnigmailGpg.agentPath, args);
|
||||
errorMsgObj.value += "\n" + cmdErrorMsgObj.value;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
if (includeSecretKey) {
|
||||
|
||||
let keyList;
|
||||
if (userId) {
|
||||
keyList = userId.split(/[ ,\t]+/);
|
||||
}
|
||||
else {
|
||||
keyList = this.getAllSecretKeys().map(keyObj => {
|
||||
return keyObj.keyId;
|
||||
});
|
||||
}
|
||||
let secKeyBlock = keyList.map(uid => {
|
||||
let keyObj = EnigmailKeyRing.getKeyById(uid);
|
||||
if (keyObj) return keyObj.getSecretKey(false).keyData;
|
||||
|
||||
let k = EnigmailKeyRing.getKeysByUserId(uid);
|
||||
if (k && k.length > 0) {
|
||||
return k.map(keyObj => {
|
||||
return keyObj.getSecretKey(false).keyData;
|
||||
}).join("\n");
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
}).join("\n");
|
||||
|
||||
keyBlock += "\n" + secKeyBlock;
|
||||
}
|
||||
*/
|
||||
|
||||
exitCodeObj.value = 0;
|
||||
if (outputFile) {
|
||||
if (!EnigmailFiles.writeFileContents(outputFile, keyBlock, DEFAULT_FILE_PERMS)) {
|
||||
|
@ -487,50 +429,6 @@ var EnigmailKeyRing = {
|
|||
return keyBlock;
|
||||
},
|
||||
|
||||
/**
|
||||
* Export the ownertrust database from GnuPG
|
||||
* @param outputFile String or nsIFile - output file name or Object - or NULL
|
||||
* @param exitCodeObj Object - o.value will contain exit code
|
||||
* @param errorMsgObj Object - o.value will contain error message from GnuPG
|
||||
*
|
||||
* @return String - if outputFile is NULL, the key block data; "" if a file is written
|
||||
*/
|
||||
extractOwnerTrust: function(outputFile, exitCodeObj, errorMsgObj) {
|
||||
let args = EnigmailGpg.getStandardArgs(true).concat(["--export-ownertrust"]);
|
||||
|
||||
let trustData = EnigmailExecution.execCmd(EnigmailGpg.agentPath, args, "", exitCodeObj, {}, {}, errorMsgObj);
|
||||
|
||||
if (outputFile) {
|
||||
if (!EnigmailFiles.writeFileContents(outputFile, trustData, DEFAULT_FILE_PERMS)) {
|
||||
exitCodeObj.value = -1;
|
||||
errorMsgObj.value = EnigmailLocale.getString("fileWriteFailed", [outputFile]);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
return trustData;
|
||||
},
|
||||
|
||||
/**
|
||||
* Import the ownertrust database into GnuPG
|
||||
* @param inputFile String or nsIFile - input file name or Object - or NULL
|
||||
* @param errorMsgObj Object - o.value will contain error message from GnuPG
|
||||
*
|
||||
* @return exit code
|
||||
*/
|
||||
importOwnerTrust: function(inputFile, errorMsgObj) {
|
||||
let args = EnigmailGpg.getStandardArgs(true).concat(["--import-ownertrust"]);
|
||||
|
||||
let exitCodeObj = {};
|
||||
try {
|
||||
let trustData = EnigmailFiles.readFile(inputFile);
|
||||
EnigmailExecution.execCmd(EnigmailGpg.agentPath, args, trustData, exitCodeObj, {}, {}, errorMsgObj);
|
||||
}
|
||||
catch (ex) {}
|
||||
|
||||
return exitCodeObj.value;
|
||||
},
|
||||
|
||||
/**
|
||||
* import key from provided key data (synchronous)
|
||||
*
|
||||
|
@ -604,27 +502,6 @@ var EnigmailKeyRing = {
|
|||
throw "importKeyAsync with minimizeKey: not implemented";
|
||||
}
|
||||
|
||||
/*
|
||||
let args = EnigmailGpg.getStandardArgs(false).concat(["--no-verbose", "--status-fd", "2"]);
|
||||
if (minimizeKey) {
|
||||
args = args.concat(["--import-options", "import-minimal"]);
|
||||
}
|
||||
|
||||
if (limitedUids.length > 0 && EnigmailGpg.getGpgFeature("export-specific-uid")) {
|
||||
let filter = limitedUids.map(i => {
|
||||
return `mbox =~ ${i}`;
|
||||
}).join(" || ");
|
||||
|
||||
args.push("--import-filter");
|
||||
args.push(`keep-uid=${filter}`);
|
||||
}
|
||||
args = args.concat(["--no-auto-check-trustdb", "--import"]);
|
||||
|
||||
const res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, pgpBlock);
|
||||
|
||||
const statusMsg = res.statusMsg;
|
||||
*/
|
||||
|
||||
const cApi = EnigmailCryptoAPI();
|
||||
let result = cApi.sync(cApi.importKeyBlock(pgpBlock));
|
||||
|
||||
|
@ -637,49 +514,9 @@ var EnigmailKeyRing = {
|
|||
|
||||
EnigmailKeyRing.clearCache();
|
||||
|
||||
/*
|
||||
let exitCode = 1;
|
||||
if (statusMsg && (statusMsg.search(/^IMPORT_RES /m) > -1)) {
|
||||
let importRes = statusMsg.match(/^IMPORT_RES ([0-9]+) ([0-9]+) ([0-9]+) 0 ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)/m);
|
||||
|
||||
if (importRes !== null) {
|
||||
let secCount = parseInt(importRes[9], 10); // number of secret keys found
|
||||
let secImported = parseInt(importRes[10], 10); // number of secret keys imported
|
||||
let secDups = parseInt(importRes[11], 10); // number of secret keys already on the keyring
|
||||
|
||||
if (secCount !== secImported + secDups) {
|
||||
EnigmailKeyRing.clearCache();
|
||||
errorMsgObj.value = EnigmailLocale.getString("import.secretKeyImportError");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
exitCode = 0;
|
||||
// Normal return
|
||||
if (statusMsg.search(/^IMPORT_OK /m) > -1) {
|
||||
let l = statusMsg.split(/\r|\n/);
|
||||
for (let i = 0; i < l.length; i++) {
|
||||
const matches = l[i].match(/^(IMPORT_OK [0-9]+ )(([0-9a-fA-F]{8}){2,5})/);
|
||||
if (matches && (matches.length > 2)) {
|
||||
EnigmailLog.DEBUG("enigmail.js: Enigmail.importKey: IMPORTED 0x" + matches[2] + "\n");
|
||||
importedKeysObj.value.push(matches[2]);
|
||||
}
|
||||
}
|
||||
|
||||
if (importedKeysObj.value.length > 0) {
|
||||
EnigmailKeyRing.updateKeys(importedKeysObj.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return exitCode;
|
||||
},
|
||||
|
||||
isGeneratingKey: function() {
|
||||
return gKeygenProcess !== null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a new key pair with GnuPG
|
||||
*
|
||||
|
@ -700,102 +537,7 @@ var EnigmailKeyRing = {
|
|||
generateKey: function(name, comment, email, expiryDate, keyLength, keyType,
|
||||
passphrase, listener) {
|
||||
EnigmailLog.WRITE("keyRing.jsm: generateKey:\n");
|
||||
|
||||
if (EnigmailKeyRing.isGeneratingKey()) {
|
||||
// key generation already ongoing
|
||||
throw Components.results.NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
const args = EnigmailGpg.getStandardArgs(true).concat(["--gen-key"]);
|
||||
|
||||
EnigmailLog.CONSOLE(EnigmailFiles.formatCmdLine(EnigmailGpg.agentPath, args));
|
||||
|
||||
let inputData = "%echo Generating key\nKey-Type: ";
|
||||
|
||||
switch (keyType) {
|
||||
case "RSA":
|
||||
inputData += "RSA\nKey-Usage: sign,auth\nKey-Length: " + keyLength;
|
||||
inputData += "\nSubkey-Type: RSA\nSubkey-Usage: encrypt\nSubkey-Length: " + keyLength + "\n";
|
||||
break;
|
||||
case "ECC":
|
||||
inputData += "EDDSA\nKey-Curve: Ed25519\nKey-Usage: sign\n";
|
||||
inputData += "Subkey-Type: ECDH\nSubkey-Curve: Curve25519\nSubkey-Usage: encrypt\n";
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name.replace(/ /g, "").length) {
|
||||
inputData += "Name-Real: " + name + "\n";
|
||||
}
|
||||
if (comment && comment.replace(/ /g, "").length) {
|
||||
inputData += "Name-Comment: " + comment + "\n";
|
||||
}
|
||||
inputData += "Name-Email: " + email + "\n";
|
||||
inputData += "Expire-Date: " + String(expiryDate) + "\n";
|
||||
|
||||
EnigmailLog.CONSOLE(inputData + " \n");
|
||||
|
||||
if (passphrase.length) {
|
||||
inputData += "Passphrase: " + passphrase + "\n";
|
||||
}
|
||||
else {
|
||||
if (EnigmailGpg.getGpgFeature("genkey-no-protection")) {
|
||||
inputData += "%echo no-protection\n";
|
||||
inputData += "%no-protection\n";
|
||||
}
|
||||
}
|
||||
|
||||
inputData += "%commit\n%echo done\n";
|
||||
|
||||
let proc = null;
|
||||
|
||||
console.debug("reaching disabled keyRing.generateKey");
|
||||
/*
|
||||
try {
|
||||
proc = subprocess.call({
|
||||
command: EnigmailGpg.agentPath,
|
||||
arguments: args,
|
||||
environment: EnigmailCore.getEnvList(),
|
||||
charset: null,
|
||||
stdin: function(pipe) {
|
||||
pipe.write(inputData);
|
||||
pipe.close();
|
||||
},
|
||||
stderr: function(data) {
|
||||
// extract key ID
|
||||
if (data.search(/^\[GNUPG:\] KEY_CREATED/m)) {
|
||||
let m = data.match(/^(\[GNUPG:\] KEY_CREATED [BPS] )([^ \r\n\t]+)$/m);
|
||||
if (m && m.length > 2) {
|
||||
listener.keyId = "0x" + m[2];
|
||||
}
|
||||
}
|
||||
listener.onDataAvailable(data);
|
||||
},
|
||||
done: function(result) {
|
||||
gKeygenProcess = null;
|
||||
try {
|
||||
if (result.exitCode === 0) {
|
||||
EnigmailKeyRing.clearCache();
|
||||
}
|
||||
listener.onStopRequest(result.exitCode);
|
||||
}
|
||||
catch (ex) {}
|
||||
},
|
||||
mergeStderr: false
|
||||
});
|
||||
}
|
||||
catch (ex) {
|
||||
EnigmailLog.ERROR("keyRing.jsm: generateKey: subprocess.call failed with '" + ex.toString() + "'\n");
|
||||
throw ex;
|
||||
}
|
||||
*/
|
||||
|
||||
gKeygenProcess = proc;
|
||||
|
||||
EnigmailLog.DEBUG("keyRing.jsm: generateKey: subprocess = " + proc + "\n");
|
||||
|
||||
return proc;
|
||||
throw new Error("Not implemented");
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,7 +16,6 @@ const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/key
|
|||
const EnigmailKeyserverURIs = ChromeUtils.import("chrome://openpgp/content/modules/keyserverUris.jsm").EnigmailKeyserverURIs;
|
||||
const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
|
||||
const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
|
||||
const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
|
||||
const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
|
||||
const EnigmailHttpProxy = ChromeUtils.import("chrome://openpgp/content/modules/httpProxy.jsm").EnigmailHttpProxy;
|
||||
const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
|
||||
|
@ -836,58 +835,24 @@ const accessGnuPG = {
|
|||
*/
|
||||
accessKeyServer: function(actionFlag, keyserver, keyId, listener) {
|
||||
EnigmailLog.DEBUG(`keyserver.jsm: accessGnuPG: accessKeyServer(${keyserver})\n`);
|
||||
throw new Error("Not implemented");
|
||||
|
||||
let processHandle = {
|
||||
value: null
|
||||
};
|
||||
|
||||
if (listener) {
|
||||
listener._isCancelled = 0;
|
||||
listener.onCancel = function() {
|
||||
EnigmailLog.DEBUG(`keyserver.jsm: accessGnuPG: accessKeyServer: onCancel\n`);
|
||||
|
||||
if (processHandle.value) {
|
||||
processHandle.value.killProcess();
|
||||
}
|
||||
this._isCancelled = 1;
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
if (keyserver === null) {
|
||||
keyserver = EnigmailKeyserverURIs.getDefaultKeyServer();
|
||||
}
|
||||
|
||||
let args = EnigmailGpg.getStandardArgs(true);
|
||||
args.push("--log-file");
|
||||
args.push(EnigmailOS.isWin32 ? "NUL" : "/dev/null");
|
||||
args.push("--with-colons");
|
||||
|
||||
let cmd = "";
|
||||
|
||||
let proxyHost = EnigmailHttpProxy.getHttpProxy();
|
||||
if (proxyHost) {
|
||||
args = args.concat(["--keyserver-options", "http-proxy=" + proxyHost]);
|
||||
}
|
||||
|
||||
args.push("--keyserver");
|
||||
args.push(keyserver);
|
||||
|
||||
switch (actionFlag) {
|
||||
case EnigmailConstants.SEARCH_KEY:
|
||||
cmd = "--search-keys";
|
||||
break;
|
||||
case EnigmailConstants.DOWNLOAD_KEY:
|
||||
cmd = "--recv-keys";
|
||||
break;
|
||||
case EnigmailConstants.UPLOAD_KEY:
|
||||
cmd = "--send-keys";
|
||||
break;
|
||||
}
|
||||
|
||||
args.push(cmd);
|
||||
args = args.concat(keyId.split(/ +/));
|
||||
|
||||
return EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, "", processHandle);
|
||||
*/
|
||||
},
|
||||
|
||||
parseStatusMsg: function(execResult) {
|
||||
|
|
|
@ -12,7 +12,6 @@ const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/loca
|
|||
const EnigmailBuildDate = ChromeUtils.import("chrome://openpgp/content/modules/buildDate.jsm").EnigmailBuildDate;
|
||||
const EnigmailApp = ChromeUtils.import("chrome://openpgp/content/modules/app.jsm").EnigmailApp;
|
||||
const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
|
||||
const EnigmailGpgAgent = ChromeUtils.import("chrome://openpgp/content/modules/gpgAgent.jsm").EnigmailGpgAgent;
|
||||
const Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
|
||||
|
||||
function getEnigmailVersion() {
|
||||
|
@ -20,22 +19,6 @@ function getEnigmailVersion() {
|
|||
return EnigmailLocale.getString("usingVersion", versionStr);
|
||||
}
|
||||
|
||||
function getGpgWorking() {
|
||||
var enigmailSvc = EnigmailCore.getService();
|
||||
|
||||
var agentStr;
|
||||
if (enigmailSvc) {
|
||||
agentStr = EnigmailLocale.getString("usingAgent", [EnigmailGpgAgent.agentType, EnigmailGpgAgent.agentPath.path]);
|
||||
} else {
|
||||
agentStr = EnigmailLocale.getString("agentError");
|
||||
|
||||
if (enigmailSvc && enigmailSvc.initializationError)
|
||||
agentStr += "\n" + enigmailSvc.initializationError;
|
||||
}
|
||||
|
||||
return agentStr;
|
||||
}
|
||||
|
||||
var EnigmailLocalizeHtml = {
|
||||
getAllElementsWithAttribute: function(doc, attribute) {
|
||||
let matchingElements = [];
|
||||
|
@ -48,7 +31,6 @@ var EnigmailLocalizeHtml = {
|
|||
return matchingElements;
|
||||
},
|
||||
|
||||
|
||||
onPageLoad: function(doc) {
|
||||
let elem = this.getAllElementsWithAttribute(doc, "txtId");
|
||||
|
||||
|
@ -59,10 +41,10 @@ var EnigmailLocalizeHtml = {
|
|||
|
||||
switch (txtId) {
|
||||
case "FNC_enigmailVersion":
|
||||
node.innerHTML = getEnigmailVersion();
|
||||
node.innerHTML = "no version";
|
||||
break;
|
||||
case "FNC_isGpgWorking":
|
||||
node.innerHTML = getGpgWorking();
|
||||
node.innerHTML = "no agent";
|
||||
break;
|
||||
default:
|
||||
node.innerHTML = EnigmailLocale.getString(txtId, param);
|
||||
|
|
|
@ -301,7 +301,7 @@ MimeDecryptHandler.prototype = {
|
|||
return ret;
|
||||
},
|
||||
|
||||
// cache encrypted data for writing to subprocess
|
||||
// cache encrypted data
|
||||
cacheData: function(str) {
|
||||
if (gDebugLogLevel > 4)
|
||||
LOCAL_DEBUG("mimeDecrypt.jsm: cacheData: " + str.length + "\n");
|
||||
|
|
|
@ -700,7 +700,6 @@ PgpMimeEncrypt.prototype = {
|
|||
|
||||
if (this.exitCode !== 0)
|
||||
EnigmailDialog.alert(this.win, retStatusObj.errorMsg);
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ var EXPORTED_SYMBOLS = ["EnigmailMsgRead"];
|
|||
|
||||
const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
|
||||
const EnigmailApp = ChromeUtils.import("chrome://openpgp/content/modules/app.jsm").EnigmailApp;
|
||||
const EnigmailVersioning = ChromeUtils.import("chrome://openpgp/content/modules/versioning.jsm").EnigmailVersioning;
|
||||
const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
|
||||
const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
|
||||
const EnigmailAutocrypt = ChromeUtils.import("chrome://openpgp/content/modules/autocrypt.jsm").EnigmailAutocrypt;
|
||||
|
|
|
@ -10,8 +10,6 @@ const EXPORTED_SYMBOLS = ["EnigmailOS"];
|
|||
|
||||
const XPCOM_APPINFO = "@mozilla.org/xre/app-info;1";
|
||||
|
||||
// const getExecution = EnigmailLazy.loader("enigmail/execution.jsm", "EnigmailExecution");
|
||||
|
||||
let operatingSystem = null;
|
||||
|
||||
function getOS() {
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["EnigmailPassword"];
|
||||
|
||||
|
||||
|
||||
const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
|
||||
const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
|
||||
const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
|
||||
const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
|
||||
|
||||
|
||||
const gpgAgent = EnigmailLazy.loader("enigmail/gpgAgent.jsm", "EnigmailGpgAgent");
|
||||
const getDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog");
|
||||
const getLocale = EnigmailLazy.loader("enigmail/locale.jsm", "EnigmailLocale");
|
||||
|
||||
var EnigmailPassword = {
|
||||
/*
|
||||
* Get GnuPG command line options for receiving the password depending
|
||||
* on the various user and system settings (gpg-agent/no passphrase)
|
||||
*
|
||||
* @return: Array the GnuPG command line options
|
||||
*/
|
||||
command: function() {
|
||||
return ["--use-agent"];
|
||||
},
|
||||
|
||||
getMaxIdleMinutes: function() {
|
||||
try {
|
||||
return EnigmailPrefs.getPref("maxIdleMinutes");
|
||||
}
|
||||
catch (ex) {}
|
||||
|
||||
return 5;
|
||||
},
|
||||
|
||||
clearPassphrase: function(win) {
|
||||
// clear all passphrases from gpg-agent by reloading the config
|
||||
if (!EnigmailCore.getService()) return;
|
||||
|
||||
let exitCode = -1;
|
||||
let isError = 0;
|
||||
|
||||
const proc = {
|
||||
command: gpgAgent().connGpgAgentPath,
|
||||
arguments: [],
|
||||
charset: null,
|
||||
environment: EnigmailCore.getEnvList(),
|
||||
stdin: function(pipe) {
|
||||
pipe.write("RELOADAGENT\n");
|
||||
pipe.write("/bye\n");
|
||||
pipe.close();
|
||||
},
|
||||
stdout: function(data) {
|
||||
if (data.search(/^ERR/m) >= 0) {
|
||||
++isError;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
exitCode = subprocess.call(proc).wait();
|
||||
}
|
||||
catch (ex) {}
|
||||
|
||||
if (isError === 0) {
|
||||
getDialog().alert(win, getLocale().getString("passphraseCleared"));
|
||||
}
|
||||
else {
|
||||
getDialog().alert(win, getLocale().getString("cannotClearPassphrase"));
|
||||
}
|
||||
}
|
||||
};
|
|
@ -13,7 +13,6 @@ const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.j
|
|||
const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
|
||||
const EnigmailArmor = ChromeUtils.import("chrome://openpgp/content/modules/armor.jsm").EnigmailArmor;
|
||||
const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
|
||||
const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
|
||||
const GlodaUtils = ChromeUtils.import("chrome://openpgp/content/modules/glodaUtils.jsm").GlodaUtils;
|
||||
const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
|
||||
const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
|
||||
|
@ -30,7 +29,6 @@ const EnigmailEncryption = ChromeUtils.import("chrome://openpgp/content/modules/
|
|||
const getFixExchangeMsg = EnigmailLazy.loader("enigmail/fixExchangeMsg.jsm", "EnigmailFixExchangeMsg");
|
||||
const getDecryption = EnigmailLazy.loader("enigmail/decryption.jsm", "EnigmailDecryption");
|
||||
const getDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog");
|
||||
const getGpgAgent = EnigmailLazy.loader("enigmail/gpgAgent.jsm", "EnigmailGpgAgent");
|
||||
|
||||
const STATUS_OK = 0;
|
||||
const STATUS_FAILURE = 1;
|
||||
|
@ -433,8 +431,10 @@ CryptMessageIntoFolder.prototype = {
|
|||
},
|
||||
|
||||
decryptAttachment: function(mimePart) {
|
||||
|
||||
EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment()\n");
|
||||
throw new Error("Not implemented");
|
||||
|
||||
/*
|
||||
let attachmentHead = mimePart.body.substr(0, 30);
|
||||
if (attachmentHead.search(/-----BEGIN PGP \w{5,10} KEY BLOCK-----/) >= 0) {
|
||||
// attachment appears to be a PGP key file, we just go-a-head
|
||||
|
@ -444,62 +444,6 @@ CryptMessageIntoFolder.prototype = {
|
|||
let attachmentName = getAttachmentName(mimePart);
|
||||
attachmentName = attachmentName ? attachmentName.replace(/\.(pgp|asc|gpg)$/, "") : "";
|
||||
|
||||
let args = EnigmailGpg.getStandardArgs(true);
|
||||
args.push("-d");
|
||||
|
||||
let statusMsgObj = {};
|
||||
let cmdLineObj = {};
|
||||
let exitCode = -1;
|
||||
let statusFlagsObj = {
|
||||
value: 0
|
||||
};
|
||||
let errorMsgObj = {};
|
||||
|
||||
let listener = EnigmailExecution.newSimpleListener((pipe) => {
|
||||
pipe.write(mimePart.body);
|
||||
pipe.close();
|
||||
});
|
||||
|
||||
do {
|
||||
// loop to allow for multiple tries of the passphrase
|
||||
let proc = EnigmailExecution.execStart(getGpgAgent().agentPath, args, false, null, listener, statusFlagsObj);
|
||||
if (!proc) {
|
||||
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 & EnigmailConstants.DECRYPTION_OKAY)) {
|
||||
EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: decryption OK\n");
|
||||
exitCode = 0;
|
||||
} else if (statusFlagsObj.value & (EnigmailConstants.DECRYPTION_FAILED | EnigmailConstants.MISSING_MDC)) {
|
||||
EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: decryption without MDC protection\n");
|
||||
exitCode = 0;
|
||||
} else if (statusFlagsObj.value & EnigmailConstants.DECRYPTION_FAILED) {
|
||||
EnigmailLog.DEBUG("persistentCrypto.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", [attachmentName, this.subject]);
|
||||
|
||||
if (!getDialog().confirmDlg(null, msg,
|
||||
EnigmailLocale.getString("dlg.button.retry"), EnigmailLocale.getString("dlg.button.skip"))) {
|
||||
return;
|
||||
}
|
||||
} else if (statusFlagsObj.value & EnigmailConstants.DECRYPTION_INCOMPLETE) {
|
||||
// failure; message not complete
|
||||
EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: decryption incomplete\n");
|
||||
return;
|
||||
} else {
|
||||
// there is nothing to be decrypted
|
||||
EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: no decryption required\n");
|
||||
return;
|
||||
}
|
||||
|
||||
} while (exitCode !== 0);
|
||||
|
||||
|
||||
EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: decrypted to " + listener.stdoutData.length + " bytes\n");
|
||||
if (statusFlagsObj.encryptedFileName && statusFlagsObj.encryptedFileName.length > 0) {
|
||||
attachmentName = statusFlagsObj.encryptedFileName;
|
||||
|
@ -521,6 +465,7 @@ CryptMessageIntoFolder.prototype = {
|
|||
}
|
||||
|
||||
mimePart.headers._rawHeaders.set("content-type", [ct]);
|
||||
*/
|
||||
},
|
||||
|
||||
|
||||
|
|
|
@ -1,390 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* Import into a JS component using
|
||||
* 'ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm");'
|
||||
*
|
||||
* This object allows to start a process, and read/write data to/from it
|
||||
* using stdin/stdout/stderr streams.
|
||||
* Usage example:
|
||||
*
|
||||
* var p = subprocess.call({
|
||||
* command: '/bin/foo',
|
||||
* arguments: ['-v', 'foo'],
|
||||
* environment: [ "XYZ=abc", "MYVAR=def" ],
|
||||
* //stdin: "some value to write to stdin\nfoobar",
|
||||
* stdin: function(stdin) {
|
||||
* stdin.write("some value to write to stdin\nfoobar");
|
||||
* stdin.close();
|
||||
* },
|
||||
* stdout: function(data) {
|
||||
* dump("got data on stdout:" + data + "\n");
|
||||
* },
|
||||
* stderr: function(data) {
|
||||
* dump("got data on stderr:" + data + "\n");
|
||||
* },
|
||||
* done: function(result) {
|
||||
* dump("process terminated with " + result.exitCode + "\n");
|
||||
* },
|
||||
* mergeStderr: false
|
||||
* });
|
||||
* p.wait(); // wait for the subprocess to terminate
|
||||
* // this will block the main thread,
|
||||
* // only do if you can wait that long
|
||||
*
|
||||
*
|
||||
* Description of parameters:
|
||||
* --------------------------
|
||||
* Apart from <command>, all arguments are optional.
|
||||
*
|
||||
* command: either a |nsIFile| object pointing to an executable file or a
|
||||
* String containing the platform-dependent path to an executable
|
||||
* file.
|
||||
*
|
||||
* arguments: optional string array containing the arguments to the command.
|
||||
*
|
||||
* environment: optional string array containing environment variables to pass
|
||||
* to the command. The array elements must have the form
|
||||
* "VAR=data". Please note that if environment is defined, it
|
||||
* replaces any existing environment variables for the subprocess.
|
||||
*
|
||||
* stdin: optional input data for the process to be passed on standard
|
||||
* input. stdin can either be a string or a function.
|
||||
* A |string| gets written to stdin and stdin gets closed;
|
||||
* A |function| gets passed an object with write and close function.
|
||||
* Please note that the write() function will return almost immediately;
|
||||
* data is always written asynchronously on a separate thread.
|
||||
*
|
||||
* stdout: an optional function that can receive output data from the
|
||||
* process. The stdout-function is called asynchronously; it can be
|
||||
* called mutliple times during the execution of a process.
|
||||
* At a minimum at each occurance of \n or \r.
|
||||
* Please note that null-characters might need to be escaped
|
||||
* with something like 'data.replace(/\0/g, "\\0");'.
|
||||
*
|
||||
* stderr: an optional function that can receive stderr data from the
|
||||
* process. The stderr-function is called asynchronously; it can be
|
||||
* called mutliple times during the execution of a process. Please
|
||||
* note that null-characters might need to be escaped with
|
||||
* something like 'data.replace(/\0/g, "\\0");'.
|
||||
* (on windows it only gets called once right now)
|
||||
*
|
||||
*
|
||||
* done: optional function that is called when the process has terminated.
|
||||
* The exit code from the process available via result.exitCode. If
|
||||
* stdout is not defined, then the output from stdout is available
|
||||
* via result.stdout. stderr data is in result.stderr
|
||||
* done() is guaranteed to be called before .wait() finishes
|
||||
*
|
||||
* mergeStderr: optional boolean value. If true, stderr is merged with stdout;
|
||||
* no data will be provided to stderr. Default is false.
|
||||
*
|
||||
*
|
||||
* Description of object returned by subprocess.call(...)
|
||||
* ------------------------------------------------------
|
||||
* The object returned by subprocess.call offers a few methods that can be
|
||||
* executed:
|
||||
*
|
||||
* wait(): waits for the subprocess to terminate. It is not required to use
|
||||
* wait; done will be called in any case when the subprocess terminated.
|
||||
*
|
||||
* kill(hardKill): kill the subprocess. Any open pipes will be closed and
|
||||
* done will be called.
|
||||
* hardKill [ignored on Windows]:
|
||||
* - false: signal the process terminate (SIGTERM)
|
||||
* - true: kill the process (SIGKILL)
|
||||
*
|
||||
*
|
||||
* Other methods in subprocess
|
||||
* ---------------------------
|
||||
*
|
||||
* registerDebugHandler(functionRef): register a handler that is called to get
|
||||
* debugging information
|
||||
* registerLogHandler(functionRef): register a handler that is called to get error
|
||||
* messages
|
||||
*
|
||||
* example:
|
||||
* subprocess.registerLogHandler( function(s) { dump(s); } );
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const SubprocessMain = ChromeUtils.import("chrome://openpgp/content/modules/enigmailprocess_main.jsm").SubprocessMain;
|
||||
const Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
|
||||
|
||||
var EXPORTED_SYMBOLS = ["subprocess"];
|
||||
|
||||
const DEFAULT_ENVIRONMENT = [];
|
||||
|
||||
var gDebugFunction = null;
|
||||
var gErrorFunction = null;
|
||||
var gRunningProcesses = []; // Array with all running subprocesses
|
||||
|
||||
function write(pipe, data) {
|
||||
let buffer = new Uint8Array(Array.from(data, c => c.charCodeAt(0)));
|
||||
return pipe.write(buffer);
|
||||
}
|
||||
|
||||
function arrayBufferToString(buffer) {
|
||||
const MAXLEN = 102400;
|
||||
|
||||
let uArr = new Uint8Array(buffer);
|
||||
let ret = "";
|
||||
let len = buffer.byteLength;
|
||||
|
||||
for (let j = 0; j < Math.floor(len / MAXLEN) + 1; j++) {
|
||||
ret += String.fromCharCode.apply(null, uArr.subarray(j * MAXLEN, ((j + 1) * MAXLEN)));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
async function read(pipe) {
|
||||
let buffer = await pipe.read();
|
||||
|
||||
try {
|
||||
if (buffer.byteLength > 0) {
|
||||
return arrayBufferToString(buffer);
|
||||
}
|
||||
} catch (ex) {
|
||||
DEBUG_LOG("err: " + ex.toString());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
var readAllData = async function(pipe, read, callback) {
|
||||
/* eslint no-cond-assign: 0 */
|
||||
let string;
|
||||
while (string = await read(pipe)) {
|
||||
callback(string);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function removeProcRef(proc) {
|
||||
if (proc) {
|
||||
let i = gRunningProcesses.indexOf(proc);
|
||||
if (i >= 0) {
|
||||
gRunningProcesses.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var subprocess = {
|
||||
registerLogHandler: function(func) {
|
||||
gErrorFunction = func;
|
||||
},
|
||||
|
||||
registerDebugHandler: function(func) {
|
||||
gDebugFunction = func;
|
||||
},
|
||||
|
||||
call: function(options) {
|
||||
|
||||
let resolved = null;
|
||||
let promises = [];
|
||||
let inputPromises = [];
|
||||
let stdinClosed = false;
|
||||
let stdoutData = "";
|
||||
let stderrData = "";
|
||||
|
||||
let formattedStack = Components.stack.formattedStack;
|
||||
|
||||
function writePipe(pipe, value) {
|
||||
let p = write(pipe, value);
|
||||
promises.push(p);
|
||||
inputPromises.push(p);
|
||||
}
|
||||
|
||||
function subProcessThen(proc) {
|
||||
gRunningProcesses.push(proc);
|
||||
|
||||
if (typeof options.stdin === "function") {
|
||||
// Some callers (e.g. child_process.js) depend on this
|
||||
// being called synchronously.
|
||||
options.stdin({
|
||||
write(val) {
|
||||
writePipe(proc.stdin, val);
|
||||
},
|
||||
|
||||
close() {
|
||||
Promise.all(inputPromises).then(() => {
|
||||
if (!stdinClosed) {
|
||||
stdinClosed = true;
|
||||
proc.stdin.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
if (typeof options.stdin === "string") {
|
||||
DEBUG_LOG("write Stdin");
|
||||
writePipe(proc.stdin, options.stdin);
|
||||
}
|
||||
|
||||
Promise.all(inputPromises).then(() => {
|
||||
proc.stdin.close();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
promises.push(
|
||||
readAllData(proc.stdout, read, data => {
|
||||
DEBUG_LOG("Got Stdout: " + data.length + "\n");
|
||||
if (typeof options.stdout === "function") {
|
||||
try {
|
||||
options.stdout(data);
|
||||
} catch (ex) {}
|
||||
} else
|
||||
stdoutData += data;
|
||||
}));
|
||||
|
||||
if (!options.mergeStderr) {
|
||||
promises.push(
|
||||
readAllData(proc.stderr, read, data => {
|
||||
DEBUG_LOG("Got Stderr: " + data.length + "\n");
|
||||
if (typeof options.stderr === "function") {
|
||||
try {
|
||||
options.stderr(data);
|
||||
} catch (ex) {}
|
||||
} else
|
||||
stderrData += data;
|
||||
|
||||
}));
|
||||
}
|
||||
|
||||
Promise.all(promises)
|
||||
.then(() => proc.wait())
|
||||
.then(result => {
|
||||
DEBUG_LOG("Complete: " + result.exitCode + "\n");
|
||||
removeProcRef(proc);
|
||||
if (gRunningProcesses.indexOf(proc) >= 0) {
|
||||
|
||||
}
|
||||
if (result.exitCode === null) result.exitCode = -1;
|
||||
resolved = result.exitCode;
|
||||
if (typeof options.done === "function") {
|
||||
try {
|
||||
options.done({
|
||||
exitCode: result.exitCode,
|
||||
stdout: stdoutData,
|
||||
stderr: stderrData
|
||||
});
|
||||
} catch (ex) {}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
resolved = -1;
|
||||
let errStr = "";
|
||||
if (typeof error === "string") {
|
||||
errStr = error;
|
||||
} else if (error) {
|
||||
for (let i in error) {
|
||||
errStr += "\n" + i + ": " + error[i];
|
||||
}
|
||||
}
|
||||
|
||||
ERROR_LOG(errStr);
|
||||
throw ("subprocess.jsm: caught error: " + errStr);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
let opts = {};
|
||||
if (options.mergeStderr) {
|
||||
opts.stderr = "stdout";
|
||||
} else {
|
||||
opts.stderr = "pipe";
|
||||
}
|
||||
|
||||
if (options.command instanceof Ci.nsIFile) {
|
||||
opts.command = options.command.path;
|
||||
} else {
|
||||
opts.command = options.command;
|
||||
}
|
||||
|
||||
if (options.workdir) {
|
||||
opts.workdir = options.workdir;
|
||||
}
|
||||
|
||||
opts.arguments = options.arguments || [];
|
||||
|
||||
|
||||
// Set up environment
|
||||
|
||||
let envVars = options.environment || DEFAULT_ENVIRONMENT;
|
||||
if (envVars.length) {
|
||||
let environment = {};
|
||||
for (let val of envVars) {
|
||||
let idx = val.indexOf("=");
|
||||
if (idx >= 0)
|
||||
environment[val.slice(0, idx)] = val.slice(idx + 1);
|
||||
}
|
||||
|
||||
opts.environment = environment;
|
||||
}
|
||||
|
||||
let subproc = SubprocessMain.call(opts).then(subProcessThen).catch(
|
||||
error => {
|
||||
resolved = -1;
|
||||
let errStr = formattedStack;
|
||||
throw ("subprocess.jsm: launch error: " + errStr + 'error: ' +
|
||||
error + "\n" + JSON.stringify(error));
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
wait: function() {
|
||||
let mainThread = Services.tm.mainThread;
|
||||
try {
|
||||
while (resolved === null) {
|
||||
mainThread.processNextEvent(true);
|
||||
}
|
||||
} catch(ex) {
|
||||
console.log(ex);
|
||||
}
|
||||
|
||||
return resolved;
|
||||
},
|
||||
|
||||
kill: function(hard = false) {
|
||||
subproc.then(proc => {
|
||||
proc.kill(hard ? 0 : undefined);
|
||||
removeProcRef();
|
||||
});
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* on shutdown kill all still running child processes
|
||||
*/
|
||||
onShutdown: function() {
|
||||
// create a copy of the array because gRunningProcesses will
|
||||
// get altered during kill()
|
||||
let procs = gRunningProcesses.map(x => x);
|
||||
|
||||
for (let i = 0; i < procs.length; i++) {
|
||||
if (procs[i] && ("kill" in procs[i])) {
|
||||
procs[i].kill(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function DEBUG_LOG(str) {
|
||||
if (gDebugFunction) {
|
||||
gDebugFunction("subprocess.jsm: " + str + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
function ERROR_LOG(str) {
|
||||
if (gErrorFunction) {
|
||||
gErrorFunction("subprocess.jsm: " + str + "\n");
|
||||
}
|
||||
}
|
|
@ -1,288 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["EnigmailSystem"];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const ctypes = ChromeUtils.import("resource://gre/modules/ctypes.jsm").ctypes;
|
||||
const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
|
||||
const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
|
||||
const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
|
||||
const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
|
||||
const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
|
||||
|
||||
var gKernel32Dll = null;
|
||||
var gSystemCharset = null;
|
||||
|
||||
const CODEPAGE_MAPPING = {
|
||||
"437": "ISO-8859-1",
|
||||
"855": "IBM855",
|
||||
"866": "IBM866",
|
||||
"874": "ISO-8859-11",
|
||||
"932": "Shift_JIS",
|
||||
"936": "GB2312",
|
||||
"950": "BIG5",
|
||||
"1200": "UTF-16LE",
|
||||
"1201": "UTF-16BE",
|
||||
"1250": "windows-1250",
|
||||
"1251": "windows-1251",
|
||||
"1252": "windows-1252",
|
||||
"1253": "windows-1253",
|
||||
"1254": "windows-1254",
|
||||
"1255": "windows-1255",
|
||||
"1256": "windows-1256",
|
||||
"1257": "windows-1257",
|
||||
"1258": "windows-1258",
|
||||
"20866": "KOI8-R",
|
||||
"20932": "EUC-JP",
|
||||
"28591": "ISO-8859-1",
|
||||
"28592": "ISO-8859-2",
|
||||
"28593": "ISO-8859-3",
|
||||
"28594": "ISO-8859-4",
|
||||
"28595": "ISO-8859-5",
|
||||
"28596": "ISO-8859-6",
|
||||
"28597": "ISO-8859-7",
|
||||
"28598": "ISO-8859-8",
|
||||
"28599": "ISO-8859-9",
|
||||
"28603": "ISO-8859-13",
|
||||
"28605": "ISO-8859-15",
|
||||
"38598": "ISO-8859-8",
|
||||
"50220": "ISO-2022-JP",
|
||||
"50221": "ISO-2022-JP",
|
||||
"50222": "ISO-2022-JP",
|
||||
"50225": "ISO-2022-KR",
|
||||
"50227": "ISO-2022-CN",
|
||||
"50229": "ISO-2022-CN",
|
||||
"51932": "EUC-JP",
|
||||
"51949": "EUC-KR",
|
||||
"52936": "HZ-GB2312",
|
||||
"65000": "UTF-7",
|
||||
"65001": "UTF-8"
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the default codepage that is set on Windows (which equals to the chatset of the console output of gpg)
|
||||
*/
|
||||
function getWindowsCopdepage() {
|
||||
EnigmailLog.DEBUG("system.jsm: getWindowsCopdepage\n");
|
||||
|
||||
if (EnigmailPrefs.getPref("gpgLocaleEn")) {
|
||||
return "437";
|
||||
}
|
||||
|
||||
let output = "";
|
||||
let env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
|
||||
let sysRoot = env.get("SystemRoot");
|
||||
|
||||
if (!sysRoot || sysRoot.length === 0) {
|
||||
sysRoot = "C:\\windows";
|
||||
}
|
||||
|
||||
try {
|
||||
let p = subprocess.call({
|
||||
command: sysRoot + "\\system32\\chcp.com",
|
||||
arguments: [],
|
||||
environment: [],
|
||||
charset: null,
|
||||
mergeStderr: false,
|
||||
stdout: function(data) {
|
||||
output += data;
|
||||
}
|
||||
});
|
||||
p.wait();
|
||||
|
||||
output = output.replace(/[\r\n]/g, "");
|
||||
output = output.replace(/^(.*[: ])([0-9]+)([^0-9].*)?$/, "$2");
|
||||
}
|
||||
catch (ex) {
|
||||
output = "437";
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the charset defined with LC_ALL or locale. That's the charset used by gpg console output
|
||||
*/
|
||||
function getUnixCharset() {
|
||||
EnigmailLog.DEBUG("system.jsm: getUnixCharset\n");
|
||||
let env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
|
||||
let lc = env.get("LC_ALL");
|
||||
|
||||
|
||||
if (lc.length === 0) {
|
||||
let places = [
|
||||
"/usr/bin/locale",
|
||||
"/usr/local/bin/locale",
|
||||
"/opt/bin/locale"
|
||||
];
|
||||
var localeFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
|
||||
for (let i = 0; i < places.length; i++) {
|
||||
localeFile.initWithPath(places[i]);
|
||||
if (localeFile.exists()) break;
|
||||
}
|
||||
|
||||
if (!localeFile.exists()) return "utf-8";
|
||||
|
||||
let output = "";
|
||||
|
||||
let p = subprocess.call({
|
||||
command: localeFile,
|
||||
arguments: [],
|
||||
environment: [],
|
||||
charset: null,
|
||||
mergeStderr: false,
|
||||
stdout: function(data) {
|
||||
output += data;
|
||||
}
|
||||
});
|
||||
p.wait();
|
||||
|
||||
let m = output.match(/^(LC_ALL=)(.*)$/m);
|
||||
if (m && m.length > 2) {
|
||||
lc = m[2].replace(/"/g, "");
|
||||
}
|
||||
else return "utf-8";
|
||||
}
|
||||
|
||||
let i = lc.search(/[.@]/);
|
||||
|
||||
if (i < 0) return "utf-8";
|
||||
|
||||
lc = lc.substr(i + 1);
|
||||
|
||||
return lc;
|
||||
|
||||
}
|
||||
|
||||
function getKernel32Dll() {
|
||||
if (!gKernel32Dll) {
|
||||
if (EnigmailOS.isWin32) {
|
||||
gKernel32Dll = ctypes.open("kernel32.dll");
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return gKernel32Dll;
|
||||
}
|
||||
|
||||
|
||||
var EnigmailSystem = {
|
||||
|
||||
determineSystemCharset: function() {
|
||||
EnigmailLog.DEBUG("system.jsm: determineSystemCharset\n");
|
||||
|
||||
if (!gSystemCharset) {
|
||||
if (EnigmailOS.isWin32) {
|
||||
gSystemCharset = getWindowsCopdepage();
|
||||
}
|
||||
else {
|
||||
gSystemCharset = getUnixCharset();
|
||||
}
|
||||
}
|
||||
|
||||
EnigmailLog.DEBUG("system.jsm: determineSystemCharset: charset='" + gSystemCharset + "'\n");
|
||||
return gSystemCharset;
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert system output coming in a native charset into Unicode (Gecko-platfrom)
|
||||
* applying an appropriate charset conversion
|
||||
*
|
||||
* @param str String - input string in native charset
|
||||
* @param cs String - [Optional] character set (Unix), or codepage (Windows).
|
||||
* If not specified, determine the system default.
|
||||
*
|
||||
* @param String - output in Unicode format. If something failed, the unmodified
|
||||
* input isreturned.
|
||||
*/
|
||||
|
||||
convertNativeToUnicode: function(str, cs) {
|
||||
try {
|
||||
if (!cs) cs = this.determineSystemCharset();
|
||||
|
||||
if (EnigmailOS.isWin32) {
|
||||
if (cs in CODEPAGE_MAPPING) {
|
||||
return EnigmailData.convertToUnicode(str, CODEPAGE_MAPPING[cs]);
|
||||
}
|
||||
else {
|
||||
let charSetNum = Number(cs);
|
||||
if (Number.isNaN(charSetNum)) {
|
||||
return EnigmailData.convertToUnicode(str, cs);
|
||||
}
|
||||
else
|
||||
return EnigmailData.convertToUnicode(this.winConvertNativeToUnichar(str, Number(cs)), "UTF-8");
|
||||
}
|
||||
}
|
||||
else {
|
||||
return EnigmailData.convertToUnicode(str, cs);
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
EnigmailLog.DEBUG("system.jsm: convertNativeToUnicode: exception +" + ex.toString() + "\n");
|
||||
|
||||
return str;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert from native Windows output (often Codepage 437) to a Mozilla Unichar string
|
||||
*
|
||||
* @param byteStr: String - the data to convert in the current Windows codepage
|
||||
*
|
||||
* @return String: the Unicode string directly display-able
|
||||
*/
|
||||
winConvertNativeToUnichar: function(byteStr, codePage) {
|
||||
/*
|
||||
int MultiByteToWideChar(
|
||||
_In_ UINT CodePage,
|
||||
_In_ DWORD dwFlags,
|
||||
_In_ LPCSTR lpMultiByteStr,
|
||||
_In_ int cbMultiByte,
|
||||
_Out_opt_ LPWSTR lpWideCharStr,
|
||||
_In_ int cchWideChar
|
||||
);
|
||||
*/
|
||||
|
||||
if (!getKernel32Dll()) {
|
||||
return byteStr;
|
||||
}
|
||||
|
||||
var multiByteToWideChar = gKernel32Dll.declare("MultiByteToWideChar",
|
||||
ctypes.winapi_abi,
|
||||
ctypes.int, // return value
|
||||
ctypes.unsigned_int, // Codepage
|
||||
ctypes.uint32_t, // dwFlags
|
||||
ctypes.char.ptr, // input string
|
||||
ctypes.int, // cbMultiByte
|
||||
ctypes.jschar.ptr, // widechar string
|
||||
ctypes.int // ccWideChar
|
||||
);
|
||||
|
||||
let n = multiByteToWideChar(codePage, 0, byteStr, byteStr.length, null, 0);
|
||||
|
||||
if (n > 0) {
|
||||
let OutStrType = ctypes.jschar.array(n + 1);
|
||||
let outStr = new OutStrType();
|
||||
|
||||
multiByteToWideChar(codePage, 0, byteStr, byteStr.length, outStr.addressOfElement(0), n);
|
||||
|
||||
let r = new RegExp(String.fromCharCode(9516), "g");
|
||||
return outStr.readString().replace(r, "");
|
||||
|
||||
}
|
||||
else
|
||||
return byteStr;
|
||||
}
|
||||
};
|
|
@ -1,280 +0,0 @@
|
|||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
|
||||
|
||||
const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
|
||||
const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
|
||||
const EnigmailRNG = ChromeUtils.import("chrome://openpgp/content/modules/rng.jsm").EnigmailRNG;
|
||||
const EnigmailVersioning = ChromeUtils.import("chrome://openpgp/content/modules/versioning.jsm").EnigmailVersioning;
|
||||
const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
|
||||
const EnigmailSocks5Proxy = ChromeUtils.import("chrome://openpgp/content/modules/socks5Proxy.jsm").EnigmailSocks5Proxy;
|
||||
const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
|
||||
const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
|
||||
const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
|
||||
|
||||
const EXPORTED_SYMBOLS = ["EnigmailTor"];
|
||||
|
||||
// Minimum for using socks5h:// prefix
|
||||
const MINIMUM_CURL_SOCKS5H_VERSION = "7.21.7";
|
||||
|
||||
// Minimum for using socks5 proxies with curl
|
||||
const MINIMUM_CURL_SOCKS5_PROXY_VERSION = "7.18.0";
|
||||
|
||||
const TORSOCKS_VERSION_2 = "2.0.0";
|
||||
|
||||
const TOR_SERVICE_PORT_PREF = "torServicePort";
|
||||
const TOR_BROWSER_BUNDLE_PORT_PREF = "torBrowserBundlePort";
|
||||
const NEW_CURL_PROTOCOL = "socks5h://";
|
||||
const OLD_CURL_PROTOCOL = "socks5-hostname://";
|
||||
|
||||
const TOR_USER_PREFERENCES = {
|
||||
DOWNLOAD: {
|
||||
requires: "downloadKeyRequireTor",
|
||||
uses: "downloadKeyWithTor",
|
||||
constant: EnigmailConstants.DOWNLOAD_KEY
|
||||
},
|
||||
SEARCH: {
|
||||
requires: "searchKeyRequireTor",
|
||||
uses: "searchKeyWithTor",
|
||||
constant: EnigmailConstants.SEARCH_KEY
|
||||
},
|
||||
UPLOAD: {
|
||||
requires: "uploadKeyRequireTor",
|
||||
uses: "uploadKeyWithTor",
|
||||
constant: EnigmailConstants.UPLOAD_KEY
|
||||
},
|
||||
REFRESH: {
|
||||
requires: "refreshAllKeysRequireTor",
|
||||
uses: "refreshAllKeysWithTor",
|
||||
constant: EnigmailConstants.REFRESH_KEY
|
||||
}
|
||||
};
|
||||
|
||||
function getAction(actionFlags) {
|
||||
for (let key in TOR_USER_PREFERENCES) {
|
||||
if (TOR_USER_PREFERENCES[key].constant & actionFlags) {
|
||||
return TOR_USER_PREFERENCES[key];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets user preference about requiring requests only to be made over Tor
|
||||
*
|
||||
* @param actionFlags - long: A Keyserver action flag
|
||||
*
|
||||
* @return true if user has requested gpg requests to be attempted over Tor, false otherwise
|
||||
*/
|
||||
function isPreferred(actionFlags) {
|
||||
const action = getAction(actionFlags);
|
||||
return EnigmailPrefs.getPref(action.requires) || EnigmailPrefs.getPref(action.uses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets user preference about requiring requests only to be made over Tor
|
||||
*
|
||||
* @param actionFlags - long: A Keyserver action flag
|
||||
*
|
||||
* @return true if user has requested gpg requests ONLY to be attempted over Tor, false otherwise
|
||||
*/
|
||||
function isRequired(actionFlags) {
|
||||
return EnigmailPrefs.getPref(getAction(actionFlags).requires);
|
||||
}
|
||||
|
||||
function combineIntoProxyhostURI(protocol, tor) {
|
||||
EnigmailLog.DEBUG("tor.jsm: combineIntoProxyhostURI()\n");
|
||||
return protocol + createRandomCredential() + ":" + createRandomCredential() + "@" + tor.ip + ":" + tor.port;
|
||||
}
|
||||
|
||||
function gpgProxyArgs(tor, versioning) {
|
||||
EnigmailLog.DEBUG("tor.jsm: gpgProxyArgs()\n");
|
||||
if (EnigmailOS.isDosLike || !versioning.versionFoundMeetsMinimumVersionRequired("curl", MINIMUM_CURL_SOCKS5H_VERSION)) {
|
||||
return combineIntoProxyhostURI(OLD_CURL_PROTOCOL, tor);
|
||||
}
|
||||
else {
|
||||
return combineIntoProxyhostURI(NEW_CURL_PROTOCOL, tor);
|
||||
}
|
||||
}
|
||||
|
||||
function createHelperArgs(helper, addAuth) {
|
||||
EnigmailLog.DEBUG("tor.jsm: createHelperArgs()\n");
|
||||
let args = [];
|
||||
if (addAuth) {
|
||||
args = ["--user", createRandomCredential(), "--pass", createRandomCredential()];
|
||||
}
|
||||
args.push(EnigmailGpg.agentPath);
|
||||
return args;
|
||||
}
|
||||
|
||||
function buildEnvVars() {
|
||||
return [
|
||||
"TORSOCKS_USERNAME=" + createRandomCredential(),
|
||||
"TORSOCKS_PASSWORD=" + createRandomCredential()
|
||||
];
|
||||
}
|
||||
|
||||
function createRandomCredential() {
|
||||
return EnigmailRNG.generateRandomUint32().toString();
|
||||
}
|
||||
|
||||
function torOn(portPref) {
|
||||
EnigmailLog.DEBUG("tor.jsm: torOn()\n");
|
||||
if (EnigmailSocks5Proxy.checkTorExists(portPref)) {
|
||||
const port = EnigmailPrefs.getPref(portPref);
|
||||
|
||||
EnigmailLog.CONSOLE("Tor found on IP: " + EnigmailSocks5Proxy.torIpAddr() + ", port: " + port + "\n\n");
|
||||
|
||||
return {
|
||||
ip: EnigmailSocks5Proxy.torIpAddr(),
|
||||
port: port
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function meetsOSConstraints() {
|
||||
if (EnigmailOS.isDosLike) {
|
||||
return EnigmailGpg.getGpgFeature("socks-on-windows");
|
||||
}
|
||||
else {
|
||||
return EnigmailVersioning.versionFoundMeetsMinimumVersionRequired("curl", MINIMUM_CURL_SOCKS5_PROXY_VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
function useAuthOverArgs(helper, versioning) {
|
||||
if (helper === "torsocks2") {
|
||||
return versioning.versionFoundMeetsMinimumVersionRequired("torsocks2", TORSOCKS_VERSION_2);
|
||||
}
|
||||
return versioning.versionFoundMeetsMinimumVersionRequired("torsocks", TORSOCKS_VERSION_2);
|
||||
}
|
||||
|
||||
function usesDirmngr() {
|
||||
return EnigmailGpg.getGpgFeature("supports-dirmngr");
|
||||
}
|
||||
|
||||
function findTorExecutableHelper(versioning) {
|
||||
EnigmailLog.DEBUG("tor.jsm: findTorExecutableHelper()\n");
|
||||
const helper = EnigmailFiles.resolvePathWithEnv("torsocks2") || EnigmailFiles.resolvePathWithEnv("torsocks");
|
||||
if (helper !== null) {
|
||||
const authOverArgs = useAuthOverArgs(helper, versioning);
|
||||
return {
|
||||
envVars: (authOverArgs ? [] : buildEnvVars()),
|
||||
command: helper,
|
||||
args: createHelperArgs(helper, authOverArgs)
|
||||
};
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if Tor is running on specified ports in preferences for Tor browser bundle and Tor service
|
||||
*
|
||||
* @return true if Tor is running on either port, false if Tor is not running on either
|
||||
*/
|
||||
function findTor() {
|
||||
EnigmailLog.DEBUG("tor.jsm: findTor()\n");
|
||||
const torOnBrowser = torOn(TOR_BROWSER_BUNDLE_PORT_PREF);
|
||||
if (torOnBrowser !== null) {
|
||||
return torOnBrowser;
|
||||
}
|
||||
return torOn(TOR_SERVICE_PORT_PREF);
|
||||
}
|
||||
|
||||
const systemCaller = {
|
||||
findTor: findTor,
|
||||
findTorExecutableHelper: findTorExecutableHelper
|
||||
};
|
||||
|
||||
function buildSocksProperties(tor) {
|
||||
EnigmailLog.DEBUG("tor.jsm: buildSocksProperties()\n");
|
||||
return {
|
||||
command: "gpg",
|
||||
args: gpgProxyArgs(tor, EnigmailVersioning),
|
||||
envVars: []
|
||||
};
|
||||
}
|
||||
|
||||
function torNotAvailableProperties() {
|
||||
EnigmailLog.DEBUG("tor.jsm: torNotAvailableProperties()\n");
|
||||
return {
|
||||
isAvailable: false,
|
||||
useTorMode: false,
|
||||
socks: null,
|
||||
helper: null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs object with properites about how we will use tor for key refreshes
|
||||
*
|
||||
* @param system - object with functions to locate Tor and Tor helpers
|
||||
*
|
||||
* @return object with
|
||||
* isAvailable - boolean, true if Tor is available, false otherwise
|
||||
* useTorMode - boolean, true if dirManager is available and configured to use Tor, false otherwise
|
||||
* socks - object with
|
||||
* command - the name of the gpg executable
|
||||
* args - proxy host URI
|
||||
* envVars - an empty array
|
||||
|
||||
null if Tor is not available
|
||||
|
||||
* helper - object with
|
||||
* envVars - environment variables, if we need them for the helper
|
||||
* command - the path to the helper executable
|
||||
* args - flags used with the helper, if we do not use environment variables
|
||||
|
||||
If no helper is found, return null
|
||||
*/
|
||||
|
||||
function torProperties(system) {
|
||||
EnigmailLog.DEBUG("tor.jsm: torProperties()\n");
|
||||
|
||||
const tor = system.findTor();
|
||||
|
||||
if (!meetsOSConstraints()) {
|
||||
EnigmailLog.DEBUG("tor.jsm: this version of curl does not support socks5 proxies \n");
|
||||
return torNotAvailableProperties();
|
||||
}
|
||||
|
||||
if (tor === null) {
|
||||
return torNotAvailableProperties();
|
||||
}
|
||||
|
||||
const helper = system.findTorExecutableHelper(EnigmailVersioning);
|
||||
let socks = null;
|
||||
let useTorMode = false;
|
||||
|
||||
if (usesDirmngr()) {
|
||||
useTorMode = EnigmailGpg.dirmngrConfiguredWithTor();
|
||||
}
|
||||
else {
|
||||
socks = buildSocksProperties(tor);
|
||||
}
|
||||
|
||||
return {
|
||||
isAvailable: true,
|
||||
useTorMode: useTorMode,
|
||||
socks: socks,
|
||||
helper: helper
|
||||
};
|
||||
}
|
||||
|
||||
var EnigmailTor = {
|
||||
torProperties: function() {
|
||||
return torProperties(systemCaller);
|
||||
},
|
||||
getTorNotAvailableProperties: torNotAvailableProperties,
|
||||
isPreferred: isPreferred,
|
||||
isRequired: isRequired
|
||||
};
|
|
@ -9,12 +9,7 @@
|
|||
|
||||
const EXPORTED_SYMBOLS = ["EnigmailVersioning"];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
|
||||
const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
|
||||
|
||||
let vc = null;
|
||||
|
||||
|
@ -47,29 +42,6 @@ function getVersion(output, executable) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the version number of any application (not gpg)
|
||||
*/
|
||||
function versionFoundMeetsMinimumVersionRequired(executable, minimumVersion) {
|
||||
const args = ["--version"];
|
||||
const exitCodeObj = {
|
||||
value: null
|
||||
};
|
||||
const output = EnigmailExecution.resolveAndSimpleExec(executable, args, exitCodeObj, {});
|
||||
if (!output || exitCodeObj.value < 0) {
|
||||
EnigmailLog.DEBUG("executable not found: " + executable + "\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const version = getVersion(output, executable);
|
||||
if (!version) {
|
||||
EnigmailLog.DEBUG("couldn't find a version in the output from " + executable + " - total output: " + output + "\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return greaterThanOrEqual(version, minimumVersion);
|
||||
}
|
||||
|
||||
function greaterThanOrEqual(versionWeHave, versionWeAreComparingWith) {
|
||||
return getVersionComparator().compare(versionWeHave, versionWeAreComparingWith) >= 0;
|
||||
}
|
||||
|
@ -113,16 +85,4 @@ var EnigmailVersioning = {
|
|||
* @return Boolean - The result of versionWeHave < versionWeAreComparingWith
|
||||
*/
|
||||
lessThan: lessThan,
|
||||
/**
|
||||
* Uses Mozilla's Version Comparator Component to identify whether an executable version
|
||||
* meets the required version specified
|
||||
*
|
||||
* @param String executable - version of the executable
|
||||
* @param String minimumVersion - version we want to compare with
|
||||
*
|
||||
* @return Boolean - True if the executable version meets the minimum version required,
|
||||
* false if it does not or it does not exist, or if a version was not
|
||||
* parseable from its output
|
||||
*/
|
||||
versionFoundMeetsMinimumVersionRequired: versionFoundMeetsMinimumVersionRequired
|
||||
};
|
||||
|
|
|
@ -15,16 +15,10 @@
|
|||
var EXPORTED_SYMBOLS = ["EnigmailWks"];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
|
||||
const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
|
||||
const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
|
||||
const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
|
||||
const EnigmailGpgAgent = ChromeUtils.import("chrome://openpgp/content/modules/gpgAgent.jsm").EnigmailGpgAgent;
|
||||
const EnigmailStdlib = ChromeUtils.import("chrome://openpgp/content/modules/stdlib.jsm").EnigmailStdlib;
|
||||
const EnigmailSend = ChromeUtils.import("chrome://openpgp/content/modules/send.jsm").EnigmailSend;
|
||||
const EnigmailMimeEncrypt = ChromeUtils.import("chrome://openpgp/content/modules/mimeEncrypt.jsm").EnigmailMimeEncrypt;
|
||||
const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
|
||||
const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
|
||||
|
@ -45,59 +39,7 @@ var EnigmailWks = {
|
|||
*/
|
||||
getWksClientPathAsync: function(window, cb) {
|
||||
EnigmailLog.DEBUG("webKey.jsm: getWksClientPathAsync\n");
|
||||
|
||||
if (EnigmailWks.wksClientPath === null) {
|
||||
let listener = EnigmailExecution.newSimpleListener(null, function(ret) {
|
||||
if (ret === 0) {
|
||||
try {
|
||||
let stdout = listener.stdoutData;
|
||||
|
||||
let libexecdir = /^libexecdir:(.+?)$/m.exec(stdout)[1];
|
||||
if (libexecdir) {
|
||||
libexecdir = libexecdir.replace(/%3a/gi, ":");
|
||||
}
|
||||
else {
|
||||
libexecdir = "";
|
||||
}
|
||||
|
||||
let wks_client = checkIfExists(libexecdir, GPG_WKS_CLIENT);
|
||||
if (!wks_client) {
|
||||
let bindir = /^bindir:(.+?)$/m.exec(stdout)[1];
|
||||
if (bindir) {
|
||||
bindir = bindir.replace(/%3a/gi, ":");
|
||||
}
|
||||
else {
|
||||
bindir = "";
|
||||
}
|
||||
wks_client = checkIfExists(bindir, GPG_WKS_CLIENT);
|
||||
|
||||
if (!wks_client) {
|
||||
cb(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
EnigmailWks.wksClientPath = wks_client;
|
||||
cb(wks_client);
|
||||
}
|
||||
catch (e) {
|
||||
EnigmailLog.DEBUG("webKey.jsm: getWksClientPathAsync: " + e.toString() + "\n");
|
||||
cb(null);
|
||||
}
|
||||
}
|
||||
else {
|
||||
cb(null);
|
||||
}
|
||||
});
|
||||
|
||||
return EnigmailExecution.execStart(EnigmailGpgAgent.gpgconfPath, ["--list-dirs"], false, window, listener, {
|
||||
value: null
|
||||
});
|
||||
}
|
||||
else {
|
||||
cb(EnigmailWks.wksClientPath);
|
||||
return null;
|
||||
}
|
||||
throw new Error("Not implemented");
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -111,20 +53,7 @@ var EnigmailWks = {
|
|||
*/
|
||||
isWksSupportedAsync: function(email, window, cb) {
|
||||
EnigmailLog.DEBUG("webKey.jsm: isWksSupportedAsync: email = " + email + "\n");
|
||||
return EnigmailWks.getWksClientPathAsync(window, function(wks_client) {
|
||||
if (wks_client === null) {
|
||||
cb(false);
|
||||
}
|
||||
let listener = EnigmailExecution.newSimpleListener(null, function(ret) {
|
||||
cb(ret === 0);
|
||||
});
|
||||
let proc = EnigmailExecution.execStart(wks_client, ["--supported", email], false, window, listener, {
|
||||
value: null
|
||||
});
|
||||
if (proc === null) {
|
||||
cb(false);
|
||||
}
|
||||
});
|
||||
throw new Error("Not implemented");
|
||||
},
|
||||
|
||||
|
||||
|
@ -179,45 +108,7 @@ var EnigmailWks = {
|
|||
|
||||
submitKey: function(ident, key, window, cb) {
|
||||
EnigmailLog.DEBUG("webKey.jsm: submitKey(): email = " + ident.email + "\n");
|
||||
return EnigmailWks.getWksClientPathAsync(window, function(wks_client) {
|
||||
if (wks_client === null) {
|
||||
cb(false);
|
||||
return null;
|
||||
}
|
||||
let listener = EnigmailExecution.newSimpleListener(null, function(ret) {
|
||||
if (ret !== 0) {
|
||||
cb(false);
|
||||
return;
|
||||
}
|
||||
EnigmailLog.DEBUG("webKey.jsm: submitKey: send " + listener.stdoutData + "\n");
|
||||
let si = EnigmailMimeEncrypt.createMimeEncrypt(null);
|
||||
let subject = listener.stdoutData.match(/^Subject:[ \t]*(.+)$/im);
|
||||
let to = listener.stdoutData.match(/^To:[ \t]*(.+)$/im);
|
||||
|
||||
if (subject !== null && to !== null) {
|
||||
si.sendFlags = EnigmailConstants.SEND_VERBATIM;
|
||||
|
||||
if (!EnigmailSend.simpleSendMessage({
|
||||
urls: [],
|
||||
identity: ident,
|
||||
to: to[1],
|
||||
subject: subject[1],
|
||||
composeSecure: si
|
||||
},
|
||||
listener.stdoutData,
|
||||
cb
|
||||
)) {
|
||||
cb(false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
cb(false);
|
||||
}
|
||||
});
|
||||
return EnigmailExecution.execStart(wks_client, ["--create", key.fpr, ident.email], false, window, listener, {
|
||||
value: null
|
||||
});
|
||||
});
|
||||
throw new Error("Not implemented");
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -234,60 +125,7 @@ var EnigmailWks = {
|
|||
|
||||
confirmKey: function(ident, body, window, cb) {
|
||||
EnigmailLog.DEBUG("webKey.jsm: confirmKey: ident=" + ident.email + "\n");
|
||||
|
||||
var sanitized = body.replace(/\r?\n/g, "\r\n");
|
||||
return EnigmailWks.getWksClientPathAsync(window, function(wks_client) {
|
||||
if (wks_client === null) {
|
||||
if (cb) {
|
||||
cb(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let listener = EnigmailExecution.newSimpleListener(function(pipe) {
|
||||
try {
|
||||
pipe.write(sanitized);
|
||||
pipe.close();
|
||||
}
|
||||
catch (e) {
|
||||
if (cb) {
|
||||
cb(false);
|
||||
}
|
||||
EnigmailLog.DEBUG(e + "\n");
|
||||
}
|
||||
}, function(ret) {
|
||||
try {
|
||||
let si = EnigmailMimeEncrypt.createMimeEncrypt(null);
|
||||
let subject = listener.stdoutData.match(/^Subject:[ \t]*(.+)$/im);
|
||||
let to = listener.stdoutData.match(/^To:[ \t]*(.+)$/im);
|
||||
|
||||
if (subject !== null && to !== null) {
|
||||
si.sendFlags = EnigmailConstants.SEND_VERBATIM;
|
||||
|
||||
if (!EnigmailSend.simpleSendMessage({
|
||||
urls: [],
|
||||
identity: ident,
|
||||
to: to[1],
|
||||
subject: subject[1],
|
||||
composeSecure: si
|
||||
},
|
||||
listener.stdoutData,
|
||||
cb
|
||||
)) {
|
||||
cb(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (cb) {
|
||||
cb(false);
|
||||
}
|
||||
EnigmailLog.DEBUG(e + "\n");
|
||||
}
|
||||
});
|
||||
EnigmailExecution.execStart(wks_client, ["--receive"], false, window, listener, {
|
||||
value: null
|
||||
});
|
||||
});
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/key
|
|||
const EnigmailZBase32 = ChromeUtils.import("chrome://openpgp/content/modules/zbase32.jsm").EnigmailZBase32;
|
||||
const EnigmailOpenPGP = ChromeUtils.import("chrome://openpgp/content/modules/openpgp.jsm").EnigmailOpenPGP;
|
||||
const EnigmailKey = ChromeUtils.import("chrome://openpgp/content/modules/key.jsm").EnigmailKey;
|
||||
const EnigmailDns = ChromeUtils.import("chrome://openpgp/content/modules/dns.jsm").EnigmailDns;
|
||||
const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
|
||||
const EnigmailSqliteDb = ChromeUtils.import("chrome://openpgp/content/modules/sqliteDb.jsm").EnigmailSqliteDb;
|
||||
|
||||
|
@ -403,6 +402,8 @@ async function getSiteSpecificUrl(emailAddr) {
|
|||
}
|
||||
|
||||
if (!url) {
|
||||
throw new Error("EnigmailWkdLookup getSiteSpecificUrl with DNS not implemented");
|
||||
/*
|
||||
try {
|
||||
let mxHosts = await EnigmailDns.lookup("MX", domain);
|
||||
if (mxHosts & mxHosts.indexOf("mail.protonmail.ch") >= 0 ||
|
||||
|
@ -411,6 +412,7 @@ async function getSiteSpecificUrl(emailAddr) {
|
|||
}
|
||||
}
|
||||
catch (ex) {}
|
||||
*/
|
||||
}
|
||||
|
||||
return url;
|
||||
|
|
|
@ -35,8 +35,6 @@ noRepeat=\n\nThis alert will not repeat until you upgrade Gine-Liam.
|
|||
pgpNotSupported=You seem to be using Gine-Liam together with PGP 6.x\n\nUnfortunately, PGP 6.x has a number of issues that prevent Gine-Liam from working correctly. Therefore, Gine-Liam 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 Gine-Liam homepage.
|
||||
initErr.howToFixIt=In order to use Gine-Liam, 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 Gine-Liam.
|
||||
noPhotoAvailable=No Photo available
|
||||
debugLog.title=Gine-Liam Debug Log
|
||||
error.photoPathNotReadable=Photo path '%S' is not readable
|
||||
|
@ -340,7 +338,6 @@ keyError.resolutionAction=Please select a valid key in the OpenPGP section of yo
|
|||
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.pinentryCursesError=Your GnuPG installation is configured to use the console for pinentry. However, when using Gine-Liam you need a graphical version of pinentry.
|
||||
errorHandling.readFaq=This is a system setup or configuration error that prevents Gine-Liam from working properly and cannot be fixed automatically.\n\nWe strongly recommend that you consult our support web site at https://doesnotexist-openpgp-integration.thunderbird/faq.
|
||||
|
|
|
@ -23,7 +23,6 @@ var Ci = Components.interfaces;
|
|||
// Many of these components are not used in this file, but are instead used in other files that are loaded together with EnigmailCommon
|
||||
var EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
|
||||
var EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
|
||||
var EnigmailKeyEditor = ChromeUtils.import("chrome://openpgp/content/modules/keyEditor.jsm").EnigmailKeyEditor;
|
||||
var EnigmailKey = ChromeUtils.import("chrome://openpgp/content/modules/key.jsm").EnigmailKey;
|
||||
var EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
|
||||
var EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
|
||||
|
@ -43,7 +42,6 @@ var EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules
|
|||
var EnigmailKeyServer = ChromeUtils.import("chrome://openpgp/content/modules/keyserver.jsm").EnigmailKeyServer;
|
||||
var EnigmailEvents = ChromeUtils.import("chrome://openpgp/content/modules/events.jsm").EnigmailEvents;
|
||||
var EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
|
||||
var EnigmailGpgAgent = ChromeUtils.import("chrome://openpgp/content/modules/gpgAgent.jsm").EnigmailGpgAgent;
|
||||
var EnigmailStreams = ChromeUtils.import("chrome://openpgp/content/modules/streams.jsm").EnigmailStreams;
|
||||
var EnigmailCryptoAPI = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI.jsm").EnigmailCryptoAPI;
|
||||
|
||||
|
@ -472,17 +470,14 @@ function EnigSignKey(userId, keyId) {
|
|||
|
||||
|
||||
function EnigChangeKeyPwd(keyId, userId) {
|
||||
// gpg-agent used: gpg-agent will handle everything
|
||||
EnigmailKeyEditor.changePassphrase(window, "0x" + keyId, "", "",
|
||||
function _changePwdCb(exitCode, errorMsg) {
|
||||
if (exitCode !== 0) {
|
||||
EnigAlert(EnigGetString("changePassFailed") + "\n\n" + errorMsg);
|
||||
}
|
||||
});
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
|
||||
function EnigRevokeKey(keyId, userId, callbackFunc) {
|
||||
throw new Error("Not implemented");
|
||||
|
||||
/*
|
||||
var enigmailSvc = GetEnigmailSvc();
|
||||
if (!enigmailSvc)
|
||||
return false;
|
||||
|
@ -503,26 +498,8 @@ function EnigRevokeKey(keyId, userId, callbackFunc) {
|
|||
} catch (ex) {}
|
||||
revFile.append("revkey.asc");
|
||||
|
||||
EnigmailKeyEditor.genRevokeCert(window, "0x" + keyId, revFile, "0", "",
|
||||
function _revokeCertCb(exitCode, errorMsg) {
|
||||
if (exitCode !== 0) {
|
||||
revFile.remove(false);
|
||||
EnigAlert(EnigGetString("revokeKeyFailed") + "\n\n" + errorMsg);
|
||||
return;
|
||||
}
|
||||
var errorMsgObj = {};
|
||||
var r = EnigmailKeyRing.importKeyFromFile(revFile, errorMsgObj);
|
||||
revFile.remove(false);
|
||||
if (r !== 0) {
|
||||
EnigAlert(EnigGetString("revokeKeyFailed") + "\n\n" + EnigConvertGpgToUnicode(errorMsgObj.value));
|
||||
} else {
|
||||
EnigAlert(EnigGetString("revokeKeyOk"));
|
||||
}
|
||||
if (callbackFunc) {
|
||||
callbackFunc(r === 0);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
*/
|
||||
}
|
||||
|
||||
function EnigGetLocalFileApi() {
|
||||
|
@ -538,6 +515,9 @@ function EnigGetFilePath(nsFileObj) {
|
|||
}
|
||||
|
||||
function EnigCreateRevokeCert(keyId, userId, callbackFunc) {
|
||||
throw new Error("Not implemented");
|
||||
|
||||
/*
|
||||
var defaultFileName = userId.replace(/[<>]/g, "");
|
||||
defaultFileName += " (0x" + keyId + ") rev.asc";
|
||||
var outFile = EnigFilePicker(EnigGetString("saveRevokeCertAs"),
|
||||
|
@ -549,17 +529,8 @@ function EnigCreateRevokeCert(keyId, userId, callbackFunc) {
|
|||
if (!enigmailSvc)
|
||||
return -1;
|
||||
|
||||
EnigmailKeyEditor.genRevokeCert(window, "0x" + keyId, outFile, "1", "",
|
||||
function _revokeCertCb(exitCode, errorMsg) {
|
||||
if (exitCode !== 0) {
|
||||
EnigAlert(EnigGetString("revokeCertFailed") + "\n\n" + errorMsg);
|
||||
} else {
|
||||
EnigAlert(EnigGetString("revokeCertOK"));
|
||||
}
|
||||
|
||||
if (callbackFunc) callbackFunc(exitCode === 0);
|
||||
});
|
||||
return 0;
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -337,55 +337,11 @@ function enigmailDeleteKey() {
|
|||
RNP.deleteKey(gKeyList[keyList[j]].fpr, deleteSecret);
|
||||
}
|
||||
clearKeyCache();
|
||||
|
||||
|
||||
/*
|
||||
let fprArr = [];
|
||||
for (let j in keyList) {
|
||||
fprArr.push("0x" + gKeyList[keyList[j]].fpr);
|
||||
}
|
||||
|
||||
EnigmailKeyEditor.deleteKey(window, fprArr.join(" "), deleteSecret,
|
||||
function(exitCode, errorMsg) {
|
||||
if (exitCode !== 0) {
|
||||
EnigAlert(EnigGetString("deleteKeyFailed") + "\n\n" + errorMsg);
|
||||
return;
|
||||
}
|
||||
refreshKeys();
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
function enigmailEnableKey() {
|
||||
var enigmailSvc = GetEnigmailSvc();
|
||||
if (!enigmailSvc)
|
||||
return;
|
||||
|
||||
var keyList = getSelectedKeys();
|
||||
var disableKey = (gKeyList[keyList[0]].keyUseFor.indexOf("D") < 0 &&
|
||||
gKeyList[keyList[0]].keyTrust.indexOf(ENIG_KEY_DISABLED) < 0);
|
||||
|
||||
var keyIndex = 0;
|
||||
|
||||
function processNextKey() {
|
||||
EnigmailKeyEditor.enableDisableKey(window, "0x" + gKeyList[keyList[keyIndex]].keyId, disableKey, function _enDisCb(exitCode, errorMsg) {
|
||||
if (exitCode === 0) {
|
||||
++keyIndex;
|
||||
if (keyIndex < keyList.length) {
|
||||
processNextKey();
|
||||
return;
|
||||
} else {
|
||||
refreshKeys();
|
||||
}
|
||||
} else {
|
||||
EnigAlert(EnigGetString("enableKeyFailed") + "\n\n" + errorMsg);
|
||||
if (keyIndex > 0) refreshKeys();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
processNextKey();
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
function enigShowPhoto() {
|
||||
|
@ -425,56 +381,7 @@ function enigmailAddPhoto() {
|
|||
}
|
||||
|
||||
function keyMgrAddPhoto(userId, keyId) {
|
||||
var enigmailSvc = GetEnigmailSvc();
|
||||
if (!enigmailSvc)
|
||||
return;
|
||||
var inFile;
|
||||
var validFile = false;
|
||||
while (!validFile) {
|
||||
inFile = EnigFilePicker(EnigGetString("keyMan.addphoto.filepicker.title"),
|
||||
"", false, "*.jpg",
|
||||
null, ["JPG", "*.jpg", "JPEG", "*.jpeg"]);
|
||||
if (!inFile) return;
|
||||
|
||||
var jpgHeader = EnigReadFileContents(inFile, 10);
|
||||
|
||||
validFile = (jpgHeader.charCodeAt(0) == 0xFF &&
|
||||
jpgHeader.charCodeAt(1) == 0xD8 &&
|
||||
jpgHeader.substr(6, 4) == "JFIF");
|
||||
|
||||
if (!validFile) {
|
||||
EnigAlert(EnigGetString("keyMan.addphoto.noJpegFile"));
|
||||
}
|
||||
}
|
||||
|
||||
if (inFile.fileSize > 25600) {
|
||||
// warn if file size > 25 kB
|
||||
if (!EnigConfirm(EnigGetString("keyMan.addphoto.warnLargeFile"), EnigGetString("dlg.button.continue"), EnigGetString("dlg.button.cancel")))
|
||||
return;
|
||||
}
|
||||
|
||||
var ioServ = enigGetService(IOSERVICE_CONTRACTID, "nsIIOService");
|
||||
var photoUri = ioServ.newFileURI(inFile).spec;
|
||||
var argsObj = {
|
||||
photoUri: photoUri,
|
||||
userId: userId,
|
||||
keyId: keyId,
|
||||
okPressed: false
|
||||
};
|
||||
|
||||
window.openDialog("chrome://openpgp/content/ui/enigmailImportPhoto.xhtml", inFile, "chrome,modal=1,resizable=1,dialog=1,centerscreen", argsObj);
|
||||
|
||||
if (!argsObj.okPressed) return;
|
||||
|
||||
EnigmailKeyEditor.addPhoto(window, "0x" + keyId, inFile,
|
||||
function(exitCode, errorMsg) {
|
||||
if (exitCode !== 0) {
|
||||
EnigAlert(EnigGetString("keyMan.addphoto.failed") + "\n\n" + errorMsg);
|
||||
return;
|
||||
}
|
||||
refreshKeys();
|
||||
});
|
||||
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
function enigCreateKeyMsg() {
|
||||
|
|
|
@ -127,14 +127,6 @@ function getKeyList(secretOnly, refresh) {
|
|||
}
|
||||
|
||||
keyList = EnigmailFuncs.cloneObj(userList.keyList);
|
||||
|
||||
let grpList = cApi.getGroups().map(k => {
|
||||
return newEnigmailKeyObj(k);
|
||||
});
|
||||
|
||||
for (let i in grpList) {
|
||||
keyList.push(grpList[i]);
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
EnigmailLog.writeException("enigmailKeySelection.js: getKeyList", ex);
|
||||
|
|
|
@ -14,7 +14,7 @@ var Ci = Components.interfaces;
|
|||
|
||||
// modules
|
||||
/* global EnigmailData: false, EnigmailLog: false, EnigmailLocale: false, EnigmailGpg: false, EnigmailKeyEditor: false */
|
||||
/* global EnigmailOS: false, EnigmailPrefs: false, EnigmailGpgAgent: false, EnigmailApp: false, EnigmailKeyRing: false */
|
||||
/* global EnigmailOS: false, EnigmailPrefs: false, EnigmailApp: false, EnigmailKeyRing: false */
|
||||
/* global EnigmailDialog: false, EnigmailFuncs: false */
|
||||
|
||||
// from enigmailCommon.js:
|
||||
|
@ -67,13 +67,6 @@ function enigmailKeygenLoad() {
|
|||
if (!enigmailSvc) {
|
||||
EnigAlert(EnigGetString("accessError"));
|
||||
}
|
||||
|
||||
/*
|
||||
if (EnigmailGpgAgent.agentType != "gpg") {
|
||||
EnigAlert(EnigGetString("onlyGPG"));
|
||||
return;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
function updateKeySizeSel(selectedObj) {
|
||||
|
@ -159,24 +152,7 @@ function enigmailKeygenTerminate(exitCode) {
|
|||
|
||||
function genAndSaveRevCert(keyId, uid) {
|
||||
EnigmailLog.DEBUG("enigmailKeygen.js: genAndSaveRevCert\n");
|
||||
|
||||
return new Promise(
|
||||
function(resolve, reject) {
|
||||
|
||||
let keyFile = EnigmailApp.getProfileDirectory();
|
||||
keyFile.append("0x" + keyId + "_rev.asc");
|
||||
|
||||
// create a revokation cert in the TB profile directoy
|
||||
EnigmailKeyEditor.genRevokeCert(window, "0x" + keyId, keyFile, "1", "",
|
||||
function _revokeCertCb(exitCode, errorMsg) {
|
||||
if (exitCode !== 0) {
|
||||
EnigAlert(EnigGetString("revokeCertFailed") + "\n\n" + errorMsg);
|
||||
reject(1);
|
||||
}
|
||||
saveRevCert(keyFile, keyId, uid, resolve, reject);
|
||||
});
|
||||
}
|
||||
);
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -38,7 +38,6 @@ var EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRi
|
|||
var EnigmailDecryption = ChromeUtils.import("chrome://openpgp/content/modules/decryption.jsm").EnigmailDecryption;
|
||||
var EnigmailAttachment = ChromeUtils.import("chrome://openpgp/content/modules/attachment.jsm").EnigmailAttachment;
|
||||
var EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
|
||||
var EnigmailPassword = ChromeUtils.import("chrome://openpgp/content/modules/passwords.jsm").EnigmailPassword;
|
||||
var EnigmailKeyUsability = ChromeUtils.import("chrome://openpgp/content/modules/keyUsability.jsm").EnigmailKeyUsability;
|
||||
var EnigmailURIs = ChromeUtils.import("chrome://openpgp/content/modules/uris.jsm").EnigmailURIs;
|
||||
var EnigmailProtocolHandler = ChromeUtils.import("chrome://openpgp/content/modules/protocolHandler.jsm").EnigmailProtocolHandler;
|
||||
|
|
|
@ -73,25 +73,6 @@ Enigmail.hlp = {
|
|||
addresses = EnigmailFuncs.stripEmail(emailsOrKeys).split(',');
|
||||
} catch (ex) {}
|
||||
|
||||
/*
|
||||
// resolve GnuPG groups
|
||||
let gpgGroups = EnigmailGpg.getGpgGroups();
|
||||
for (let i = 0; i < addresses.length; i++) {
|
||||
let addr = addresses[i].toLowerCase();
|
||||
for (let j = 0; j < gpgGroups.length; j++) {
|
||||
if (addr == gpgGroups[j].alias.toLowerCase() ||
|
||||
"<" + addr + ">" == gpgGroups[j].alias.toLowerCase()) {
|
||||
// replace address with keylist
|
||||
let grpList = gpgGroups[j].keylist.split(/;/);
|
||||
addresses[i] = grpList[0];
|
||||
for (let k = 1; k < grpList.length; k++) {
|
||||
addresses.push(grpList[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// resolve all the email addresses if possible:
|
||||
keyMissing = EnigmailKeyRing.getValidKeysForAllRecipients(addresses, minTrustLevel, details, resultingArray);
|
||||
} catch (ex) {
|
||||
|
|
|
@ -32,7 +32,6 @@ var EnigmailWindows = ChromeUtils.import("chrome://openpgp/content/modules/windo
|
|||
var EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
|
||||
var EnigmailURIs = ChromeUtils.import("chrome://openpgp/content/modules/uris.jsm").EnigmailURIs;
|
||||
var EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
|
||||
var EnigmailPassword = ChromeUtils.import("chrome://openpgp/content/modules/passwords.jsm").EnigmailPassword;
|
||||
var EnigmailDecryption = ChromeUtils.import("chrome://openpgp/content/modules/decryption.jsm").EnigmailDecryption;
|
||||
var EnigmailEncryption = ChromeUtils.import("chrome://openpgp/content/modules/encryption.jsm").EnigmailEncryption;
|
||||
var EnigmailClipboard = ChromeUtils.import("chrome://openpgp/content/modules/clipboard.jsm").EnigmailClipboard;
|
||||
|
@ -3212,144 +3211,6 @@ Enigmail.msg = {
|
|||
this.sendProcess = false;
|
||||
},
|
||||
|
||||
|
||||
// encrypt attachments when sending inline PGP mails
|
||||
// It's quite a hack: the attachments are stored locally
|
||||
// and the attachments list is modified to pick up the
|
||||
// encrypted file(s) instead of the original ones.
|
||||
encryptAttachments: function(bucketList, newAttachments, window, uiFlags,
|
||||
fromAddr, toAddr, bccAddr, sendFlags,
|
||||
errorMsgObj) {
|
||||
EnigmailLog.DEBUG("enigmailMsgComposeOverlay.js: Enigmail.msg.encryptAttachments\n");
|
||||
|
||||
const SIGN = EnigmailConstants.SEND_SIGNED;
|
||||
const ENCRYPT = EnigmailConstants.SEND_ENCRYPTED;
|
||||
|
||||
var ioServ;
|
||||
var fileTemplate;
|
||||
errorMsgObj.value = "";
|
||||
|
||||
try {
|
||||
ioServ = Components.classes[IOSERVICE_CONTRACTID].getService(Components.interfaces.nsIIOService);
|
||||
if (!ioServ)
|
||||
return -1;
|
||||
}
|
||||
catch (ex) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
var tmpDir = EnigmailFiles.getTempDir();
|
||||
var extAppLauncher = Components.classes["@mozilla.org/mime;1"].getService(Components.interfaces.nsPIExternalAppLauncher);
|
||||
|
||||
try {
|
||||
fileTemplate = Components.classes[LOCAL_FILE_CONTRACTID].createInstance(Components.interfaces.nsIFile);
|
||||
fileTemplate.initWithPath(tmpDir);
|
||||
if (!(fileTemplate.isDirectory() && fileTemplate.isWritable())) {
|
||||
errorMsgObj.value = EnigmailLocale.getString("noTempDir");
|
||||
return -1;
|
||||
}
|
||||
fileTemplate.append("encfile");
|
||||
}
|
||||
catch (ex) {
|
||||
errorMsgObj.value = EnigmailLocale.getString("noTempDir");
|
||||
return -1;
|
||||
}
|
||||
EnigmailLog.DEBUG("enigmailMsgComposeOverlay.js: Enigmail.msg.encryptAttachments tmpDir=" + tmpDir + "\n");
|
||||
var enigmailSvc = EnigmailCore.getService(window);
|
||||
if (!enigmailSvc)
|
||||
return null;
|
||||
|
||||
var exitCodeObj = {};
|
||||
var statusFlagsObj = {};
|
||||
|
||||
var node = bucketList.firstChild;
|
||||
while (node) {
|
||||
var origUrl = node.attachment.url;
|
||||
if (origUrl.substring(0, 7) != "file://") {
|
||||
// this should actually never happen since it is pre-checked!
|
||||
errorMsgObj.value = "The attachment '" + node.attachment.name + "' is not a local file";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// transform attachment URL to platform-specific file name
|
||||
var origUri = ioServ.newURI(origUrl, null, null);
|
||||
var origFile = origUri.QueryInterface(Components.interfaces.nsIFileURL);
|
||||
if (node.attachment.temporary) {
|
||||
try {
|
||||
var origLocalFile = Components.classes[LOCAL_FILE_CONTRACTID].createInstance(Components.interfaces.nsIFile);
|
||||
origLocalFile.initWithPath(origFile.file.path);
|
||||
extAppLauncher.deleteTemporaryFileOnExit(origLocalFile);
|
||||
}
|
||||
catch (ex) {}
|
||||
}
|
||||
|
||||
var newFile = fileTemplate.clone();
|
||||
var txtMessage;
|
||||
try {
|
||||
newFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0x180);
|
||||
txtMessage = EnigmailEncryption.encryptAttachment(window, fromAddr, toAddr, bccAddr, sendFlags,
|
||||
origFile.file, newFile,
|
||||
exitCodeObj, statusFlagsObj,
|
||||
errorMsgObj);
|
||||
}
|
||||
catch (ex) {}
|
||||
|
||||
if (exitCodeObj.value !== 0) {
|
||||
return exitCodeObj.value;
|
||||
}
|
||||
|
||||
var fileInfo = {
|
||||
origFile: origFile,
|
||||
origUrl: node.attachment.url,
|
||||
origName: node.attachment.name,
|
||||
origTemp: node.attachment.temporary,
|
||||
origCType: node.attachment.contentType
|
||||
};
|
||||
|
||||
// transform platform specific new file name to file:// URL
|
||||
var newUri = ioServ.newFileURI(newFile);
|
||||
fileInfo.newUrl = newUri.asciiSpec;
|
||||
fileInfo.newFile = newFile;
|
||||
fileInfo.encrypted = (sendFlags & ENCRYPT);
|
||||
|
||||
newAttachments.push(fileInfo);
|
||||
node = node.nextSibling;
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
if (sendFlags & ENCRYPT) {
|
||||
// if we got here, all attachments were encrpted successfully,
|
||||
// so we replace their names & urls
|
||||
node = bucketList.firstChild;
|
||||
|
||||
while (node) {
|
||||
node.attachment.url = newAttachments[i].newUrl;
|
||||
node.attachment.name += EnigmailPrefs.getPref("inlineAttachExt");
|
||||
node.attachment.contentType = "application/octet-stream";
|
||||
node.attachment.temporary = true;
|
||||
|
||||
++i;
|
||||
node = node.nextSibling;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// for inline signing we need to add new attachments for every
|
||||
// signed file
|
||||
for (i = 0; i < newAttachments.length; i++) {
|
||||
// create new attachment
|
||||
var fileAttachment = Components.classes["@mozilla.org/messengercompose/attachment;1"].createInstance(Components.interfaces.nsIMsgAttachment);
|
||||
fileAttachment.temporary = true;
|
||||
fileAttachment.url = newAttachments[i].newUrl;
|
||||
fileAttachment.name = newAttachments[i].origName + EnigmailPrefs.getPref("inlineSigAttachExt");
|
||||
|
||||
// add attachment to msg
|
||||
this.addAttachment(fileAttachment);
|
||||
}
|
||||
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
|
||||
toggleAttribute: function(attrName) {
|
||||
EnigmailLog.DEBUG("enigmailMsgComposeOverlay.js: Enigmail.msg.toggleAttribute('" + attrName + "')\n");
|
||||
|
||||
|
|
|
@ -5,12 +5,6 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// 0: disable openpgp
|
||||
// 1: use internal default engine
|
||||
// 2: use gnupg engine
|
||||
pref("temp.openpgp.engine", 0);
|
||||
|
||||
|
||||
/**
|
||||
* Default pref values for Enigmail
|
||||
*/
|
||||
|
@ -22,12 +16,6 @@ pref("temp.openpgp.configuredVersion", "");
|
|||
// Hide prefs and menu entries from non-advanced users
|
||||
pref("temp.openpgp.advancedUser", false);
|
||||
|
||||
// additional parameter(s) to pass to GnuPG
|
||||
pref("temp.openpgp.agentAdditionalParam", "");
|
||||
|
||||
// path to gpg executable
|
||||
pref("temp.openpgp.agentPath", "");
|
||||
|
||||
// ** enigmail keySel preferences:
|
||||
// use rules to assign keys
|
||||
pref("temp.openpgp.assignKeysByRules", true);
|
||||
|
@ -62,9 +50,6 @@ pref("temp.openpgp.displaySecondaryUid", true);
|
|||
// treat '-- ' as signature separator
|
||||
pref("temp.openpgp.doubleDashSeparator", true);
|
||||
|
||||
// last state of dialog to choose encryption method if there are attachments
|
||||
pref("temp.openpgp.encryptAttachments", 1);
|
||||
|
||||
// skip the attachments dialog
|
||||
pref("temp.openpgp.encryptAttachmentsSkipDlg", 0);
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче