Loading browser/installer/package-manifest.in +1 −0 Original line number Diff line number Diff line Loading @@ -225,6 +225,7 @@ @RESPATH@/browser/chrome/browser.manifest @RESPATH@/chrome/pdfjs.manifest @RESPATH@/chrome/pdfjs/* @RESPATH@/components/tor-launcher.manifest @RESPATH@/chrome/toolkit@JAREXT@ @RESPATH@/chrome/toolkit.manifest #ifdef MOZ_GTK Loading toolkit/components/moz.build +1 −0 Original line number Diff line number Diff line Loading @@ -76,6 +76,7 @@ DIRS += [ "thumbnails", "timermanager", "tooltiptext", "tor-launcher", "typeaheadfind", "utils", "url-classifier", Loading toolkit/components/tor-launcher/TorBootstrapRequest.jsm 0 → 100644 +129 −0 Original line number Diff line number Diff line "use strict"; var EXPORTED_SYMBOLS = ["TorBootstrapRequest", "TorTopics"]; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { setTimeout, clearTimeout } = ChromeUtils.import( "resource://gre/modules/Timer.jsm" ); const { TorProtocolService } = ChromeUtils.import( "resource://gre/modules/TorProtocolService.jsm" ); const { TorLauncherUtil } = ChromeUtils.import( "resource://gre/modules/TorLauncherUtil.jsm" ); /* tor-launcher observer topics */ const TorTopics = Object.freeze({ BootstrapStatus: "TorBootstrapStatus", BootstrapError: "TorBootstrapError", LogHasWarnOrErr: "TorLogHasWarnOrErr", }); // modeled after XMLHttpRequest // nicely encapsulates the observer register/unregister logic class TorBootstrapRequest { constructor() { // number of ms to wait before we abandon the bootstrap attempt // a value of 0 implies we never wait this.timeout = 0; // callbacks for bootstrap process status updates this.onbootstrapstatus = (progress, status) => {}; this.onbootstrapcomplete = () => {}; this.onbootstraperror = (message, details) => {}; // internal resolve() method for bootstrap this._bootstrapPromiseResolve = null; this._bootstrapPromise = null; this._timeoutID = null; } observe(subject, topic, data) { const obj = subject?.wrappedJSObject; switch (topic) { case TorTopics.BootstrapStatus: { const progress = obj.PROGRESS; const status = TorLauncherUtil.getLocalizedBootstrapStatus(obj, "TAG"); if (this.onbootstrapstatus) { this.onbootstrapstatus(progress, status); } if (progress === 100) { if (this.onbootstrapcomplete) { this.onbootstrapcomplete(); } this._bootstrapPromiseResolve(true); clearTimeout(this._timeoutID); } break; } case TorTopics.BootstrapError: { console.info("TorBootstrapRequest: observerd TorBootstrapError", obj); this._stop(obj?.message, obj?.details); break; } } } // resolves 'true' if bootstrap succeeds, false otherwise bootstrap() { if (this._bootstrapPromise) { return this._bootstrapPromise; } this._bootstrapPromise = new Promise((resolve, reject) => { this._bootstrapPromiseResolve = resolve; // register ourselves to listen for bootstrap events Services.obs.addObserver(this, TorTopics.BootstrapStatus); Services.obs.addObserver(this, TorTopics.BootstrapError); // optionally cancel bootstrap after a given timeout if (this.timeout > 0) { this._timeoutID = setTimeout(async () => { this._timeoutID = null; // TODO: Translate, if really used await this._stop( "Tor Bootstrap process timed out", `Bootstrap attempt abandoned after waiting ${this.timeout} ms` ); }, this.timeout); } // wait for bootstrapping to begin and maybe handle error TorProtocolService.connect().catch(err => { this._stop(err.message, ""); }); }).finally(() => { // and remove ourselves once bootstrap is resolved Services.obs.removeObserver(this, TorTopics.BootstrapStatus); Services.obs.removeObserver(this, TorTopics.BootstrapError); this._bootstrapPromise = null; }); return this._bootstrapPromise; } async cancel() { await this._stop(); } // Internal implementation. Do not use directly, but call cancel, instead. async _stop(message, details) { // first stop our bootstrap timeout before handling the error if (this._timeoutID !== null) { clearTimeout(this._timeoutID); this._timeoutID = null; } // stopBootstrap never throws await TorProtocolService.stopBootstrap(); if (this.onbootstraperror && message) { this.onbootstraperror(message, details); } this._bootstrapPromiseResolve(false); } } toolkit/components/tor-launcher/TorLauncherUtil.jsm 0 → 100644 +554 −0 Original line number Diff line number Diff line // Copyright (c) 2022, The Tor Project, Inc. // See LICENSE for licensing information. "use strict"; /************************************************************************* * Tor Launcher Util JS Module *************************************************************************/ var EXPORTED_SYMBOLS = ["TorLauncherUtil"]; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const kPropBundleURI = "chrome://torbutton/locale/torlauncher.properties"; const kPropNamePrefix = "torlauncher."; const kIPCDirPrefName = "extensions.torlauncher.tmp_ipc_dir"; let gStringBundle = null; class TorFile { // The nsIFile to be returned file = null; isIPC = false; ipcFileName = ""; checkIPCPathLen = true; static _isFirstIPCPathRequest = true; static _dataDir = null; static _appDir = null; static _torDir = null; constructor(aTorFileType, aCreate) { this.fileType = aTorFileType; this.getFromPref(); this.getIPC(); // No preference and no pre-determined IPC path: use a default path. if (!this.file) { this.getDefault(); } // At this point, this.file must not be null, or previous functions must // have thrown and interrupted this constructor. if (!this.file.exists() && !this.isIPC && aCreate) { this.createFile(); } this.normalize(); } getFile() { return this.file; } getFromPref() { const prefName = `extensions.torlauncher.${this.fileType}_path`; const path = Services.prefs.getCharPref(prefName, ""); if (path) { const isUserData = this.fileType !== "tor" && this.fileType !== "pt-startup-dir" && this.fileType !== "torrc-defaults"; // always try to use path if provided in pref this.checkIPCPathLen = false; this.setFileFromPath(path, isUserData); } } getIPC() { const isControlIPC = this.fileType === "control_ipc"; const isSOCKSIPC = this.fileType === "socks_ipc"; this.isIPC = isControlIPC || isSOCKSIPC; if (!this.isIPC) { return; } const kControlIPCFileName = "control.socket"; const kSOCKSIPCFileName = "socks.socket"; this.ipcFileName = isControlIPC ? kControlIPCFileName : kSOCKSIPCFileName; this.extraIPCPathLen = this.isSOCKSIPC ? 2 : 0; // Do not do anything else if this.file has already been populated with the // _path preference for this file type (or if we are not looking for an IPC // file). if (this.file) { return; } // If this is the first request for an IPC path during this browser // session, remove the old temporary directory. This helps to keep /tmp // clean if the browser crashes or is killed. if (TorFile._isFirstIPCPathRequest) { TorLauncherUtil.cleanupTempDirectories(); TorFile._isFirstIPCPathRequest = false; } else { // FIXME: Do we really need a preference? Or can we save it in a static // member? // Retrieve path for IPC objects (it may have already been determined). const ipcDirPath = Services.prefs.getCharPref(kIPCDirPrefName, ""); if (ipcDirPath) { // We have already determined where IPC objects will be placed. this.file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); this.file.initWithPath(ipcDirPath); this.file.append(this.ipcFileName); this.checkIPCPathLen = false; // already checked. return; } } // If XDG_RUNTIME_DIR is set, use it as the base directory for IPC // objects (e.g., Unix domain sockets) -- assuming it is not too long. const env = Cc["@mozilla.org/process/environment;1"].getService( Ci.nsIEnvironment ); if (!env.exists("XDG_RUNTIME_DIR")) { return; } const ipcDir = this.createUniqueIPCDir(env.get("XDG_RUNTIME_DIR")); if (ipcDir) { const f = ipcDir.clone(); f.append(this.ipcFileName); if (this.isIPCPathLengthOK(f.path, this.extraIPCPathLen)) { this.file = f; this.checkIPCPathLen = false; // no need to check again. // Store directory path so it can be reused for other IPC objects // and so it can be removed during exit. Services.prefs.setCharPref(kIPCDirPrefName, ipcDir.path); } else { // too long; remove the directory that we just created. ipcDir.remove(false); } } } getDefault() { switch (this.fileType) { case "tor": this.file = TorFile.torDir; this.file.append(TorLauncherUtil.isWindows ? "tor.exe" : "tor"); break; case "torrc-defaults": if (TorLauncherUtil.isMac) { this.file = TorFile.appDir; this.file.appendRelativePath( "Contents/Resources/TorBrowser/Tor/torrc-defaults" ); } else { // FIXME: Should we move this file to the tor directory, in the other // platforms, since it is not user data? this.file = TorFile.torDataDir; this.file.append("torrc-defaults"); } break; case "torrc": this.file = TorFile.torDataDir; this.file.append("torrc"); break; case "tordatadir": this.file = TorFile.torDataDir; break; case "toronionauthdir": this.file = TorFile.torDataDir; this.file.append("onion-auth"); break; case "pt-startup-dir": // On macOS we specify different relative paths than on Linux and // Windows this.file = TorLauncherUtil.isMac ? TorFile.torDir : TorFile.appDir; break; default: if (!TorLauncherUtil.isWindows && this.isIPC) { this.setFileFromPath(`Tor/${this.ipcFileName}`, true); break; } throw new Error("Unknown file type"); } } // This function is used to set this.file from a string that contains a path. // As a matter of fact, it is used only when setting a path from preferences, // or to set the default IPC paths. setFileFromPath(path, isUserData) { if (TorLauncherUtil.isWindows) { path = path.replaceAll("/", "\\"); } // Turn 'path' into an absolute path when needed. if (TorLauncherUtil.isPathRelative(path)) { if (TorLauncherUtil.isMac) { // On macOS, files are correctly separated because it was needed for the // gatekeeper signing. this.file = isUserData ? TorFile.dataDir : TorFile.appDir; } else { // Windows and Linux still use the legacy behavior. // To avoid breaking old installations, let's just keep it. this.file = TorFile.appDir; this.file.append("TorBrowser"); } this.file.appendRelativePath(path); } else { this.file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); this.file.initWithPath(path); } } createFile() { if ( "tordatadir" == this.fileType || "toronionauthdir" == this.fileType || "pt-profiles-dir" == this.fileType ) { this.file.create(this.file.DIRECTORY_TYPE, 0o700); } else { this.file.create(this.file.NORMAL_FILE_TYPE, 0o600); } } // If the file exists or an IPC object was requested, normalize the path // and return a file object. The control and SOCKS IPC objects will be // created by tor. normalize() { if (!this.file.exists() && !this.isIPC) { throw new Error(`${this.fileType} file not found: ${this.file.path}`); } try { this.file.normalize(); } catch (e) { console.warn("Normalization of the path failed", e); } // Ensure that the IPC path length is short enough for use by the // operating system. If not, create and use a unique directory under // /tmp for all IPC objects. The created directory path is stored in // a preference so it can be reused for other IPC objects and so it // can be removed during exit. if ( this.isIPC && this.checkIPCPathLen && !this.isIPCPathLengthOK(this.file.path, this.extraIPCPathLen) ) { this.file = this.createUniqueIPCDir("/tmp"); if (!this.file) { throw new Error("failed to create unique directory under /tmp"); } Services.prefs.setCharPref(kIPCDirPrefName, this.file.path); this.file.append(this.ipcFileName); } } // Return true if aPath is short enough to be used as an IPC object path, // e.g., for a Unix domain socket path. aExtraLen is the "delta" necessary // to accommodate other IPC objects that have longer names; it is used to // account for "control.socket" vs. "socks.socket" (we want to ensure that // all IPC objects are placed in the same parent directory unless the user // has set prefs or env vars to explicitly specify the path for an object). // We enforce a maximum length of 100 because all operating systems allow // at least 100 characters for Unix domain socket paths. isIPCPathLengthOK(aPath, aExtraLen) { const kMaxIPCPathLen = 100; return aPath && aPath.length + aExtraLen <= kMaxIPCPathLen; } // Returns an nsIFile or null if a unique directory could not be created. createUniqueIPCDir(aBasePath) { try { const d = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); d.initWithPath(aBasePath); d.append("Tor"); d.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o700); return d; } catch (e) { console.error(`createUniqueIPCDir failed for ${aBasePath}: `, e); return null; } } // Returns an nsIFile that points to the binary directory (on Linux and // Windows), and to the root of the application bundle on macOS. static get appDir() { if (!this._appDir) { // .../Browser on Windows and Linux, .../TorBrowser.app/Contents/MacOS/ on // macOS. this._appDir = Services.dirsvc.get("XREExeF", Ci.nsIFile).parent; if (TorLauncherUtil.isMac) { this._appDir = this._appDir.parent.parent; } } return this._appDir.clone(); } // Returns an nsIFile that points to the data directory. This is usually // TorBrowser/Data/ on Linux and Windows, and TorBrowser-Data/ on macOS. // The parent directory of the default profile directory is taken. static get dataDir() { if (!this._dataDir) { // Notice that we use `DefProfRt`, because users could create their // profile in a completely unexpected directory: the profiles.ini contains // a IsRelative entry, which I expect could influence ProfD, but not this. this._dataDir = Services.dirsvc.get("DefProfRt", Ci.nsIFile).parent; } return this._dataDir.clone(); } // Returns an nsIFile that points to the directory that contains the tor // executable. static get torDir() { if (!this._torDir) { // The directory that contains firefox const torDir = Services.dirsvc.get("XREExeF", Ci.nsIFile).parent; if (!TorLauncherUtil.isMac) { torDir.append("TorBrowser"); } torDir.append("Tor"); // Save the value only if the XPCOM methods do not throw. this._torDir = torDir; } return this._torDir.clone(); } // Returns an nsIFile that points to the directory that contains the tor // data. Currently it is ${dataDir}/Tor. static get torDataDir() { const dir = this.dataDir; dir.append("Tor"); return dir; } } const TorLauncherUtil = Object.freeze({ get isMac() { return Services.appinfo.OS === "Darwin"; }, get isWindows() { return Services.appinfo.OS === "WINNT"; }, isPathRelative(path) { const re = this.isWindows ? /^([A-Za-z]:|\\)\\/ : /^\//; return !re.test(path); }, // Returns true if user confirms; false if not. showConfirm(aParentWindow, aMsg, aDefaultButtonLabel, aCancelButtonLabel) { if (!aParentWindow) { aParentWindow = Services.wm.getMostRecentWindow("navigator:browser"); } const ps = Services.prompt; const title = this.getLocalizedString("error_title"); const btnFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING + ps.BUTTON_POS_0_DEFAULT + ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING; const notUsed = { value: false }; const btnIndex = ps.confirmEx( aParentWindow, title, aMsg, btnFlags, aDefaultButtonLabel, aCancelButtonLabel, null, null, notUsed ); return btnIndex === 0; }, // Localized Strings // TODO: Switch to fluent also these ones. // "torlauncher." is prepended to aStringName. getLocalizedString(aStringName) { if (!aStringName) { return aStringName; } try { const key = kPropNamePrefix + aStringName; return this._stringBundle.GetStringFromName(key); } catch (e) {} return aStringName; }, // "torlauncher." is prepended to aStringName. getFormattedLocalizedString(aStringName, aArray, aLen) { if (!aStringName || !aArray) { return aStringName; } try { const key = kPropNamePrefix + aStringName; return this._stringBundle.formatStringFromName(key, aArray, aLen); } catch (e) {} return aStringName; }, getLocalizedStringForError(aNSResult) { for (let prop in Cr) { if (Cr[prop] === aNSResult) { const key = "nsresult." + prop; const rv = this.getLocalizedString(key); if (rv !== key) { return rv; } return prop; // As a fallback, return the NS_ERROR... name. } } return undefined; }, getLocalizedBootstrapStatus(aStatusObj, aKeyword) { if (!aStatusObj || !aKeyword) { return ""; } let result; let fallbackStr; if (aStatusObj[aKeyword]) { let val = aStatusObj[aKeyword].toLowerCase(); let key; if (aKeyword === "TAG") { // The bootstrap status tags in tagMap below are used by Tor // versions prior to 0.4.0.x. We map each one to the tag that will // produce the localized string that is the best fit. const tagMap = { conn_dir: "conn", handshake_dir: "onehop_create", conn_or: "enough_dirinfo", handshake_or: "ap_conn", }; if (val in tagMap) { val = tagMap[val]; } key = "bootstrapStatus." + val; fallbackStr = aStatusObj.SUMMARY; } else if (aKeyword === "REASON") { if (val === "connectreset") { val = "connectrefused"; } key = "bootstrapWarning." + val; fallbackStr = aStatusObj.WARNING; } result = TorLauncherUtil.getLocalizedString(key); if (result === key) { result = undefined; } } if (!result) { result = fallbackStr; } if (aKeyword === "REASON" && aStatusObj.HOSTADDR) { result += " - " + aStatusObj.HOSTADDR; } return result ? result : ""; }, get shouldStartAndOwnTor() { const kPrefStartTor = "extensions.torlauncher.start_tor"; try { const kBrowserToolboxPort = "MOZ_BROWSER_TOOLBOX_PORT"; const kEnvSkipLaunch = "TOR_SKIP_LAUNCH"; const env = Cc["@mozilla.org/process/environment;1"].getService( Ci.nsIEnvironment ); if (env.exists(kBrowserToolboxPort)) { return false; } if (env.exists(kEnvSkipLaunch)) { const value = parseInt(env.get(kEnvSkipLaunch)); return isNaN(value) || !value; } } catch (e) {} return Services.prefs.getBoolPref(kPrefStartTor, true); }, get shouldShowNetworkSettings() { try { const kEnvForceShowNetConfig = "TOR_FORCE_NET_CONFIG"; const env = Cc["@mozilla.org/process/environment;1"].getService( Ci.nsIEnvironment ); if (env.exists(kEnvForceShowNetConfig)) { const value = parseInt(env.get(kEnvForceShowNetConfig)); return !isNaN(value) && value; } } catch (e) {} return true; }, get shouldOnlyConfigureTor() { const kPrefOnlyConfigureTor = "extensions.torlauncher.only_configure_tor"; try { const kEnvOnlyConfigureTor = "TOR_CONFIGURE_ONLY"; const env = Cc["@mozilla.org/process/environment;1"].getService( Ci.nsIEnvironment ); if (env.exists(kEnvOnlyConfigureTor)) { const value = parseInt(env.get(kEnvOnlyConfigureTor)); return !isNaN(value) && value; } } catch (e) {} return Services.prefs.getBoolPref(kPrefOnlyConfigureTor, false); }, // Returns an nsIFile. // If aTorFileType is "control_ipc" or "socks_ipc", aCreate is ignored // and there is no requirement that the IPC object exists. // For all other file types, null is returned if the file does not exist // and it cannot be created (it will be created if aCreate is true). getTorFile(aTorFileType, aCreate) { if (!aTorFileType) { return null; } try { const torFile = new TorFile(aTorFileType, aCreate); return torFile.getFile(); } catch (e) { console.error(`getTorFile: cannot get ${aTorFileType}`, e); } return null; // File not found or error (logged above). }, cleanupTempDirectories() { const dirPath = Services.prefs.getCharPref(kIPCDirPrefName, ""); try { Services.prefs.clearUserPref(kIPCDirPrefName); } catch (e) {} try { if (dirPath) { const f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); f.initWithPath(dirPath); if (f.exists()) { f.remove(false); } } } catch (e) { console.warn("Could not remove the IPC directory", e); } }, get _stringBundle() { if (!gStringBundle) { gStringBundle = Services.strings.createBundle(kPropBundleURI); } return gStringBundle; }, }); toolkit/components/tor-launcher/TorMonitorService.jsm 0 → 100644 +491 −0 File added.Preview size limit exceeded, changes collapsed. Show changes Loading
browser/installer/package-manifest.in +1 −0 Original line number Diff line number Diff line Loading @@ -225,6 +225,7 @@ @RESPATH@/browser/chrome/browser.manifest @RESPATH@/chrome/pdfjs.manifest @RESPATH@/chrome/pdfjs/* @RESPATH@/components/tor-launcher.manifest @RESPATH@/chrome/toolkit@JAREXT@ @RESPATH@/chrome/toolkit.manifest #ifdef MOZ_GTK Loading
toolkit/components/moz.build +1 −0 Original line number Diff line number Diff line Loading @@ -76,6 +76,7 @@ DIRS += [ "thumbnails", "timermanager", "tooltiptext", "tor-launcher", "typeaheadfind", "utils", "url-classifier", Loading
toolkit/components/tor-launcher/TorBootstrapRequest.jsm 0 → 100644 +129 −0 Original line number Diff line number Diff line "use strict"; var EXPORTED_SYMBOLS = ["TorBootstrapRequest", "TorTopics"]; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { setTimeout, clearTimeout } = ChromeUtils.import( "resource://gre/modules/Timer.jsm" ); const { TorProtocolService } = ChromeUtils.import( "resource://gre/modules/TorProtocolService.jsm" ); const { TorLauncherUtil } = ChromeUtils.import( "resource://gre/modules/TorLauncherUtil.jsm" ); /* tor-launcher observer topics */ const TorTopics = Object.freeze({ BootstrapStatus: "TorBootstrapStatus", BootstrapError: "TorBootstrapError", LogHasWarnOrErr: "TorLogHasWarnOrErr", }); // modeled after XMLHttpRequest // nicely encapsulates the observer register/unregister logic class TorBootstrapRequest { constructor() { // number of ms to wait before we abandon the bootstrap attempt // a value of 0 implies we never wait this.timeout = 0; // callbacks for bootstrap process status updates this.onbootstrapstatus = (progress, status) => {}; this.onbootstrapcomplete = () => {}; this.onbootstraperror = (message, details) => {}; // internal resolve() method for bootstrap this._bootstrapPromiseResolve = null; this._bootstrapPromise = null; this._timeoutID = null; } observe(subject, topic, data) { const obj = subject?.wrappedJSObject; switch (topic) { case TorTopics.BootstrapStatus: { const progress = obj.PROGRESS; const status = TorLauncherUtil.getLocalizedBootstrapStatus(obj, "TAG"); if (this.onbootstrapstatus) { this.onbootstrapstatus(progress, status); } if (progress === 100) { if (this.onbootstrapcomplete) { this.onbootstrapcomplete(); } this._bootstrapPromiseResolve(true); clearTimeout(this._timeoutID); } break; } case TorTopics.BootstrapError: { console.info("TorBootstrapRequest: observerd TorBootstrapError", obj); this._stop(obj?.message, obj?.details); break; } } } // resolves 'true' if bootstrap succeeds, false otherwise bootstrap() { if (this._bootstrapPromise) { return this._bootstrapPromise; } this._bootstrapPromise = new Promise((resolve, reject) => { this._bootstrapPromiseResolve = resolve; // register ourselves to listen for bootstrap events Services.obs.addObserver(this, TorTopics.BootstrapStatus); Services.obs.addObserver(this, TorTopics.BootstrapError); // optionally cancel bootstrap after a given timeout if (this.timeout > 0) { this._timeoutID = setTimeout(async () => { this._timeoutID = null; // TODO: Translate, if really used await this._stop( "Tor Bootstrap process timed out", `Bootstrap attempt abandoned after waiting ${this.timeout} ms` ); }, this.timeout); } // wait for bootstrapping to begin and maybe handle error TorProtocolService.connect().catch(err => { this._stop(err.message, ""); }); }).finally(() => { // and remove ourselves once bootstrap is resolved Services.obs.removeObserver(this, TorTopics.BootstrapStatus); Services.obs.removeObserver(this, TorTopics.BootstrapError); this._bootstrapPromise = null; }); return this._bootstrapPromise; } async cancel() { await this._stop(); } // Internal implementation. Do not use directly, but call cancel, instead. async _stop(message, details) { // first stop our bootstrap timeout before handling the error if (this._timeoutID !== null) { clearTimeout(this._timeoutID); this._timeoutID = null; } // stopBootstrap never throws await TorProtocolService.stopBootstrap(); if (this.onbootstraperror && message) { this.onbootstraperror(message, details); } this._bootstrapPromiseResolve(false); } }
toolkit/components/tor-launcher/TorLauncherUtil.jsm 0 → 100644 +554 −0 Original line number Diff line number Diff line // Copyright (c) 2022, The Tor Project, Inc. // See LICENSE for licensing information. "use strict"; /************************************************************************* * Tor Launcher Util JS Module *************************************************************************/ var EXPORTED_SYMBOLS = ["TorLauncherUtil"]; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const kPropBundleURI = "chrome://torbutton/locale/torlauncher.properties"; const kPropNamePrefix = "torlauncher."; const kIPCDirPrefName = "extensions.torlauncher.tmp_ipc_dir"; let gStringBundle = null; class TorFile { // The nsIFile to be returned file = null; isIPC = false; ipcFileName = ""; checkIPCPathLen = true; static _isFirstIPCPathRequest = true; static _dataDir = null; static _appDir = null; static _torDir = null; constructor(aTorFileType, aCreate) { this.fileType = aTorFileType; this.getFromPref(); this.getIPC(); // No preference and no pre-determined IPC path: use a default path. if (!this.file) { this.getDefault(); } // At this point, this.file must not be null, or previous functions must // have thrown and interrupted this constructor. if (!this.file.exists() && !this.isIPC && aCreate) { this.createFile(); } this.normalize(); } getFile() { return this.file; } getFromPref() { const prefName = `extensions.torlauncher.${this.fileType}_path`; const path = Services.prefs.getCharPref(prefName, ""); if (path) { const isUserData = this.fileType !== "tor" && this.fileType !== "pt-startup-dir" && this.fileType !== "torrc-defaults"; // always try to use path if provided in pref this.checkIPCPathLen = false; this.setFileFromPath(path, isUserData); } } getIPC() { const isControlIPC = this.fileType === "control_ipc"; const isSOCKSIPC = this.fileType === "socks_ipc"; this.isIPC = isControlIPC || isSOCKSIPC; if (!this.isIPC) { return; } const kControlIPCFileName = "control.socket"; const kSOCKSIPCFileName = "socks.socket"; this.ipcFileName = isControlIPC ? kControlIPCFileName : kSOCKSIPCFileName; this.extraIPCPathLen = this.isSOCKSIPC ? 2 : 0; // Do not do anything else if this.file has already been populated with the // _path preference for this file type (or if we are not looking for an IPC // file). if (this.file) { return; } // If this is the first request for an IPC path during this browser // session, remove the old temporary directory. This helps to keep /tmp // clean if the browser crashes or is killed. if (TorFile._isFirstIPCPathRequest) { TorLauncherUtil.cleanupTempDirectories(); TorFile._isFirstIPCPathRequest = false; } else { // FIXME: Do we really need a preference? Or can we save it in a static // member? // Retrieve path for IPC objects (it may have already been determined). const ipcDirPath = Services.prefs.getCharPref(kIPCDirPrefName, ""); if (ipcDirPath) { // We have already determined where IPC objects will be placed. this.file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); this.file.initWithPath(ipcDirPath); this.file.append(this.ipcFileName); this.checkIPCPathLen = false; // already checked. return; } } // If XDG_RUNTIME_DIR is set, use it as the base directory for IPC // objects (e.g., Unix domain sockets) -- assuming it is not too long. const env = Cc["@mozilla.org/process/environment;1"].getService( Ci.nsIEnvironment ); if (!env.exists("XDG_RUNTIME_DIR")) { return; } const ipcDir = this.createUniqueIPCDir(env.get("XDG_RUNTIME_DIR")); if (ipcDir) { const f = ipcDir.clone(); f.append(this.ipcFileName); if (this.isIPCPathLengthOK(f.path, this.extraIPCPathLen)) { this.file = f; this.checkIPCPathLen = false; // no need to check again. // Store directory path so it can be reused for other IPC objects // and so it can be removed during exit. Services.prefs.setCharPref(kIPCDirPrefName, ipcDir.path); } else { // too long; remove the directory that we just created. ipcDir.remove(false); } } } getDefault() { switch (this.fileType) { case "tor": this.file = TorFile.torDir; this.file.append(TorLauncherUtil.isWindows ? "tor.exe" : "tor"); break; case "torrc-defaults": if (TorLauncherUtil.isMac) { this.file = TorFile.appDir; this.file.appendRelativePath( "Contents/Resources/TorBrowser/Tor/torrc-defaults" ); } else { // FIXME: Should we move this file to the tor directory, in the other // platforms, since it is not user data? this.file = TorFile.torDataDir; this.file.append("torrc-defaults"); } break; case "torrc": this.file = TorFile.torDataDir; this.file.append("torrc"); break; case "tordatadir": this.file = TorFile.torDataDir; break; case "toronionauthdir": this.file = TorFile.torDataDir; this.file.append("onion-auth"); break; case "pt-startup-dir": // On macOS we specify different relative paths than on Linux and // Windows this.file = TorLauncherUtil.isMac ? TorFile.torDir : TorFile.appDir; break; default: if (!TorLauncherUtil.isWindows && this.isIPC) { this.setFileFromPath(`Tor/${this.ipcFileName}`, true); break; } throw new Error("Unknown file type"); } } // This function is used to set this.file from a string that contains a path. // As a matter of fact, it is used only when setting a path from preferences, // or to set the default IPC paths. setFileFromPath(path, isUserData) { if (TorLauncherUtil.isWindows) { path = path.replaceAll("/", "\\"); } // Turn 'path' into an absolute path when needed. if (TorLauncherUtil.isPathRelative(path)) { if (TorLauncherUtil.isMac) { // On macOS, files are correctly separated because it was needed for the // gatekeeper signing. this.file = isUserData ? TorFile.dataDir : TorFile.appDir; } else { // Windows and Linux still use the legacy behavior. // To avoid breaking old installations, let's just keep it. this.file = TorFile.appDir; this.file.append("TorBrowser"); } this.file.appendRelativePath(path); } else { this.file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); this.file.initWithPath(path); } } createFile() { if ( "tordatadir" == this.fileType || "toronionauthdir" == this.fileType || "pt-profiles-dir" == this.fileType ) { this.file.create(this.file.DIRECTORY_TYPE, 0o700); } else { this.file.create(this.file.NORMAL_FILE_TYPE, 0o600); } } // If the file exists or an IPC object was requested, normalize the path // and return a file object. The control and SOCKS IPC objects will be // created by tor. normalize() { if (!this.file.exists() && !this.isIPC) { throw new Error(`${this.fileType} file not found: ${this.file.path}`); } try { this.file.normalize(); } catch (e) { console.warn("Normalization of the path failed", e); } // Ensure that the IPC path length is short enough for use by the // operating system. If not, create and use a unique directory under // /tmp for all IPC objects. The created directory path is stored in // a preference so it can be reused for other IPC objects and so it // can be removed during exit. if ( this.isIPC && this.checkIPCPathLen && !this.isIPCPathLengthOK(this.file.path, this.extraIPCPathLen) ) { this.file = this.createUniqueIPCDir("/tmp"); if (!this.file) { throw new Error("failed to create unique directory under /tmp"); } Services.prefs.setCharPref(kIPCDirPrefName, this.file.path); this.file.append(this.ipcFileName); } } // Return true if aPath is short enough to be used as an IPC object path, // e.g., for a Unix domain socket path. aExtraLen is the "delta" necessary // to accommodate other IPC objects that have longer names; it is used to // account for "control.socket" vs. "socks.socket" (we want to ensure that // all IPC objects are placed in the same parent directory unless the user // has set prefs or env vars to explicitly specify the path for an object). // We enforce a maximum length of 100 because all operating systems allow // at least 100 characters for Unix domain socket paths. isIPCPathLengthOK(aPath, aExtraLen) { const kMaxIPCPathLen = 100; return aPath && aPath.length + aExtraLen <= kMaxIPCPathLen; } // Returns an nsIFile or null if a unique directory could not be created. createUniqueIPCDir(aBasePath) { try { const d = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); d.initWithPath(aBasePath); d.append("Tor"); d.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o700); return d; } catch (e) { console.error(`createUniqueIPCDir failed for ${aBasePath}: `, e); return null; } } // Returns an nsIFile that points to the binary directory (on Linux and // Windows), and to the root of the application bundle on macOS. static get appDir() { if (!this._appDir) { // .../Browser on Windows and Linux, .../TorBrowser.app/Contents/MacOS/ on // macOS. this._appDir = Services.dirsvc.get("XREExeF", Ci.nsIFile).parent; if (TorLauncherUtil.isMac) { this._appDir = this._appDir.parent.parent; } } return this._appDir.clone(); } // Returns an nsIFile that points to the data directory. This is usually // TorBrowser/Data/ on Linux and Windows, and TorBrowser-Data/ on macOS. // The parent directory of the default profile directory is taken. static get dataDir() { if (!this._dataDir) { // Notice that we use `DefProfRt`, because users could create their // profile in a completely unexpected directory: the profiles.ini contains // a IsRelative entry, which I expect could influence ProfD, but not this. this._dataDir = Services.dirsvc.get("DefProfRt", Ci.nsIFile).parent; } return this._dataDir.clone(); } // Returns an nsIFile that points to the directory that contains the tor // executable. static get torDir() { if (!this._torDir) { // The directory that contains firefox const torDir = Services.dirsvc.get("XREExeF", Ci.nsIFile).parent; if (!TorLauncherUtil.isMac) { torDir.append("TorBrowser"); } torDir.append("Tor"); // Save the value only if the XPCOM methods do not throw. this._torDir = torDir; } return this._torDir.clone(); } // Returns an nsIFile that points to the directory that contains the tor // data. Currently it is ${dataDir}/Tor. static get torDataDir() { const dir = this.dataDir; dir.append("Tor"); return dir; } } const TorLauncherUtil = Object.freeze({ get isMac() { return Services.appinfo.OS === "Darwin"; }, get isWindows() { return Services.appinfo.OS === "WINNT"; }, isPathRelative(path) { const re = this.isWindows ? /^([A-Za-z]:|\\)\\/ : /^\//; return !re.test(path); }, // Returns true if user confirms; false if not. showConfirm(aParentWindow, aMsg, aDefaultButtonLabel, aCancelButtonLabel) { if (!aParentWindow) { aParentWindow = Services.wm.getMostRecentWindow("navigator:browser"); } const ps = Services.prompt; const title = this.getLocalizedString("error_title"); const btnFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING + ps.BUTTON_POS_0_DEFAULT + ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING; const notUsed = { value: false }; const btnIndex = ps.confirmEx( aParentWindow, title, aMsg, btnFlags, aDefaultButtonLabel, aCancelButtonLabel, null, null, notUsed ); return btnIndex === 0; }, // Localized Strings // TODO: Switch to fluent also these ones. // "torlauncher." is prepended to aStringName. getLocalizedString(aStringName) { if (!aStringName) { return aStringName; } try { const key = kPropNamePrefix + aStringName; return this._stringBundle.GetStringFromName(key); } catch (e) {} return aStringName; }, // "torlauncher." is prepended to aStringName. getFormattedLocalizedString(aStringName, aArray, aLen) { if (!aStringName || !aArray) { return aStringName; } try { const key = kPropNamePrefix + aStringName; return this._stringBundle.formatStringFromName(key, aArray, aLen); } catch (e) {} return aStringName; }, getLocalizedStringForError(aNSResult) { for (let prop in Cr) { if (Cr[prop] === aNSResult) { const key = "nsresult." + prop; const rv = this.getLocalizedString(key); if (rv !== key) { return rv; } return prop; // As a fallback, return the NS_ERROR... name. } } return undefined; }, getLocalizedBootstrapStatus(aStatusObj, aKeyword) { if (!aStatusObj || !aKeyword) { return ""; } let result; let fallbackStr; if (aStatusObj[aKeyword]) { let val = aStatusObj[aKeyword].toLowerCase(); let key; if (aKeyword === "TAG") { // The bootstrap status tags in tagMap below are used by Tor // versions prior to 0.4.0.x. We map each one to the tag that will // produce the localized string that is the best fit. const tagMap = { conn_dir: "conn", handshake_dir: "onehop_create", conn_or: "enough_dirinfo", handshake_or: "ap_conn", }; if (val in tagMap) { val = tagMap[val]; } key = "bootstrapStatus." + val; fallbackStr = aStatusObj.SUMMARY; } else if (aKeyword === "REASON") { if (val === "connectreset") { val = "connectrefused"; } key = "bootstrapWarning." + val; fallbackStr = aStatusObj.WARNING; } result = TorLauncherUtil.getLocalizedString(key); if (result === key) { result = undefined; } } if (!result) { result = fallbackStr; } if (aKeyword === "REASON" && aStatusObj.HOSTADDR) { result += " - " + aStatusObj.HOSTADDR; } return result ? result : ""; }, get shouldStartAndOwnTor() { const kPrefStartTor = "extensions.torlauncher.start_tor"; try { const kBrowserToolboxPort = "MOZ_BROWSER_TOOLBOX_PORT"; const kEnvSkipLaunch = "TOR_SKIP_LAUNCH"; const env = Cc["@mozilla.org/process/environment;1"].getService( Ci.nsIEnvironment ); if (env.exists(kBrowserToolboxPort)) { return false; } if (env.exists(kEnvSkipLaunch)) { const value = parseInt(env.get(kEnvSkipLaunch)); return isNaN(value) || !value; } } catch (e) {} return Services.prefs.getBoolPref(kPrefStartTor, true); }, get shouldShowNetworkSettings() { try { const kEnvForceShowNetConfig = "TOR_FORCE_NET_CONFIG"; const env = Cc["@mozilla.org/process/environment;1"].getService( Ci.nsIEnvironment ); if (env.exists(kEnvForceShowNetConfig)) { const value = parseInt(env.get(kEnvForceShowNetConfig)); return !isNaN(value) && value; } } catch (e) {} return true; }, get shouldOnlyConfigureTor() { const kPrefOnlyConfigureTor = "extensions.torlauncher.only_configure_tor"; try { const kEnvOnlyConfigureTor = "TOR_CONFIGURE_ONLY"; const env = Cc["@mozilla.org/process/environment;1"].getService( Ci.nsIEnvironment ); if (env.exists(kEnvOnlyConfigureTor)) { const value = parseInt(env.get(kEnvOnlyConfigureTor)); return !isNaN(value) && value; } } catch (e) {} return Services.prefs.getBoolPref(kPrefOnlyConfigureTor, false); }, // Returns an nsIFile. // If aTorFileType is "control_ipc" or "socks_ipc", aCreate is ignored // and there is no requirement that the IPC object exists. // For all other file types, null is returned if the file does not exist // and it cannot be created (it will be created if aCreate is true). getTorFile(aTorFileType, aCreate) { if (!aTorFileType) { return null; } try { const torFile = new TorFile(aTorFileType, aCreate); return torFile.getFile(); } catch (e) { console.error(`getTorFile: cannot get ${aTorFileType}`, e); } return null; // File not found or error (logged above). }, cleanupTempDirectories() { const dirPath = Services.prefs.getCharPref(kIPCDirPrefName, ""); try { Services.prefs.clearUserPref(kIPCDirPrefName); } catch (e) {} try { if (dirPath) { const f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); f.initWithPath(dirPath); if (f.exists()) { f.remove(false); } } } catch (e) { console.warn("Could not remove the IPC directory", e); } }, get _stringBundle() { if (!gStringBundle) { gStringBundle = Services.strings.createBundle(kPropBundleURI); } return gStringBundle; }, });
toolkit/components/tor-launcher/TorMonitorService.jsm 0 → 100644 +491 −0 File added.Preview size limit exceeded, changes collapsed. Show changes