Loading browser/installer/package-manifest.in +1 −0 Original line number Diff line number Diff line Loading @@ -234,6 +234,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 @@ -75,6 +75,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 +569 −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; // A relative or absolute path that will determine file path = null; pathIsRelative = false; // If true, path is ignored useAppDir = false; isIPC = false; checkIPCPathLen = true; static _isFirstIPCPathRequest = true; static _isUserDataOutsideOfAppDir = undefined; static _dataDir = null; static _appDir = 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.path) { this.getDefault(); } if (!this.file && this.path) { this.pathToFile(); } if (this.file && !this.file.exists() && !this.isIPC && aCreate) { this.createFile(); } this.normalize(); } getFile() { return this.file; } getFromPref() { const prefName = `extensions.torlauncher.${this.fileType}_path`; this.path = Services.prefs.getCharPref(prefName, ""); if (this.path) { const re = TorLauncherUtil.isWindows ? /^[A-Za-z]:\\/ : /^\//; this.isRelativePath = !re.test(this.path); // always try to use path if provided in pref this.checkIPCPathLen = false; } } getIPC() { const isControlIPC = this.fileType === "control_ipc"; const isSOCKSIPC = this.fileType === "socks_ipc"; this.isIPC = isControlIPC || isSOCKSIPC; 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.path has already been populated with the // _path preference for this file type (or if we are not looking for an IPC // file). if (this.path || !this.isIPC) { 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); } } } // This block is used for the TorBrowser-Data/ case. getDefault() { let torPath = ""; let dataDir = ""; // FIXME: TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used only on macOS at the // moment. In Linux and Windows it might not work anymore. // We might simplify the code here, if we get rid of this macro. // Also, we allow specifying directly a relative path, for a portable mode. // Anyway, that macro is also available in AppConstants. if (TorFile.isUserDataOutsideOfAppDir) { if (TorLauncherUtil.isMac) { torPath = "Contents/Resources/"; } torPath += "TorBrowser/Tor"; } else { torPath = "Tor"; dataDir = "Data/"; } switch (this.fileType) { case "tor": if (TorLauncherUtil.isMac) { this.path = `${torPath}/tor`; } else { this.path = torPath + "/tor" + (TorLauncherUtil.isWindows ? ".exe" : ""); } break; case "torrc-defaults": this.path = TorFile.isUserDataOutsideOfAppDir ? `${torPath}/torrc-defaults` : `${dataDir}Tor/torrc-defaults`; break; case "torrc": this.path = `${dataDir}Tor/torrc`; break; case "tordatadir": this.path = `${dataDir}Tor`; break; case "toronionauthdir": this.path = `${dataDir}Tor/onion-auth`; break; case "pt-profiles-dir": this.path = TorFile.isUserDataOutsideOfAppDir ? "Tor/PluggableTransports" : `${dataDir}Browser`; break; case "pt-startup-dir": if (TorLauncherUtil.isMac && TorFile.isUserDataOutsideOfAppDir) { this.path = "Contents/MacOS/Tor"; } else { this.file = TorFile.appDir.clone(); return; } break; default: if (!TorLauncherUtil.isWindows && this.isIPC) { this.path = "Tor/" + this.ipcFileName; break; } throw new Error("Unknown file type"); } if (TorLauncherUtil.isWindows) { this.path = this.path.replaceAll("/", "\\"); } this.isRelativePath = true; } pathToFile() { if (TorLauncherUtil.isWindows) { this.path = this.path.replaceAll("/", "\\"); } // Turn 'path' into an absolute path when needed. if (this.isRelativePath) { const isUserData = this.fileType !== "tor" && this.fileType !== "pt-startup-dir" && this.fileType !== "torrc-defaults"; if (TorFile.isUserDataOutsideOfAppDir) { let baseDir = isUserData ? TorFile.dataDir : TorFile.appDir; this.file = baseDir.clone(); } else { this.file = TorFile.appDir.clone(); this.file.append("TorBrowser"); } this.file.appendRelativePath(this.path); } else { this.file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); this.file.initWithPath(this.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; } } static get isUserDataOutsideOfAppDir() { if (this._isUserDataOutsideOfAppDir === undefined) { // Determine if we are using a "side-by-side" data model by checking // whether the user profile is outside of the app directory. try { const profDir = Services.dirsvc.get("ProfD", Ci.nsIFile); this._isUserDataOutsideOfAppDir = !this.appDir.contains(profDir); } catch (e) { this._isUserDataOutsideOfAppDir = false; } } return this._isUserDataOutsideOfAppDir; } // Returns an nsIFile that points to the application directory. static get appDir() { if (!this._appDir) { let topDir = Services.dirsvc.get("CurProcD", Ci.nsIFile); // On Linux and Windows, we want to return the Browser/ directory. // Because topDir ("CurProcD") points to Browser/browser on those // platforms, we need to go up one level. // On Mac OS, we want to return the TorBrowser.app/ directory. // Because topDir points to Contents/Resources/browser on Mac OS, // we need to go up 3 levels. let tbbBrowserDepth = TorLauncherUtil.isMac ? 3 : 1; while (tbbBrowserDepth > 0) { let didRemove = topDir.leafName != "."; topDir = topDir.parent; if (didRemove) { tbbBrowserDepth--; } } this._appDir = topDir; } return this._appDir; } // Returns an nsIFile that points to the TorBrowser-Data/ directory. // This function is only used when isUserDataOutsideOfAppDir === true. // May throw. static get dataDir() { if (!this._dataDir) { const profDir = Services.dirsvc.get("ProfD", Ci.nsIFile); this._dataDir = profDir.parent.parent; } return this._dataDir; } } const TorLauncherUtil = Object.freeze({ get isMac() { return Services.appinfo.OS === "Darwin"; }, get isWindows() { return Services.appinfo.OS === "WINNT"; }, // 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 +506 −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 @@ -234,6 +234,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 @@ -75,6 +75,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 +569 −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; // A relative or absolute path that will determine file path = null; pathIsRelative = false; // If true, path is ignored useAppDir = false; isIPC = false; checkIPCPathLen = true; static _isFirstIPCPathRequest = true; static _isUserDataOutsideOfAppDir = undefined; static _dataDir = null; static _appDir = 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.path) { this.getDefault(); } if (!this.file && this.path) { this.pathToFile(); } if (this.file && !this.file.exists() && !this.isIPC && aCreate) { this.createFile(); } this.normalize(); } getFile() { return this.file; } getFromPref() { const prefName = `extensions.torlauncher.${this.fileType}_path`; this.path = Services.prefs.getCharPref(prefName, ""); if (this.path) { const re = TorLauncherUtil.isWindows ? /^[A-Za-z]:\\/ : /^\//; this.isRelativePath = !re.test(this.path); // always try to use path if provided in pref this.checkIPCPathLen = false; } } getIPC() { const isControlIPC = this.fileType === "control_ipc"; const isSOCKSIPC = this.fileType === "socks_ipc"; this.isIPC = isControlIPC || isSOCKSIPC; 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.path has already been populated with the // _path preference for this file type (or if we are not looking for an IPC // file). if (this.path || !this.isIPC) { 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); } } } // This block is used for the TorBrowser-Data/ case. getDefault() { let torPath = ""; let dataDir = ""; // FIXME: TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used only on macOS at the // moment. In Linux and Windows it might not work anymore. // We might simplify the code here, if we get rid of this macro. // Also, we allow specifying directly a relative path, for a portable mode. // Anyway, that macro is also available in AppConstants. if (TorFile.isUserDataOutsideOfAppDir) { if (TorLauncherUtil.isMac) { torPath = "Contents/Resources/"; } torPath += "TorBrowser/Tor"; } else { torPath = "Tor"; dataDir = "Data/"; } switch (this.fileType) { case "tor": if (TorLauncherUtil.isMac) { this.path = `${torPath}/tor`; } else { this.path = torPath + "/tor" + (TorLauncherUtil.isWindows ? ".exe" : ""); } break; case "torrc-defaults": this.path = TorFile.isUserDataOutsideOfAppDir ? `${torPath}/torrc-defaults` : `${dataDir}Tor/torrc-defaults`; break; case "torrc": this.path = `${dataDir}Tor/torrc`; break; case "tordatadir": this.path = `${dataDir}Tor`; break; case "toronionauthdir": this.path = `${dataDir}Tor/onion-auth`; break; case "pt-profiles-dir": this.path = TorFile.isUserDataOutsideOfAppDir ? "Tor/PluggableTransports" : `${dataDir}Browser`; break; case "pt-startup-dir": if (TorLauncherUtil.isMac && TorFile.isUserDataOutsideOfAppDir) { this.path = "Contents/MacOS/Tor"; } else { this.file = TorFile.appDir.clone(); return; } break; default: if (!TorLauncherUtil.isWindows && this.isIPC) { this.path = "Tor/" + this.ipcFileName; break; } throw new Error("Unknown file type"); } if (TorLauncherUtil.isWindows) { this.path = this.path.replaceAll("/", "\\"); } this.isRelativePath = true; } pathToFile() { if (TorLauncherUtil.isWindows) { this.path = this.path.replaceAll("/", "\\"); } // Turn 'path' into an absolute path when needed. if (this.isRelativePath) { const isUserData = this.fileType !== "tor" && this.fileType !== "pt-startup-dir" && this.fileType !== "torrc-defaults"; if (TorFile.isUserDataOutsideOfAppDir) { let baseDir = isUserData ? TorFile.dataDir : TorFile.appDir; this.file = baseDir.clone(); } else { this.file = TorFile.appDir.clone(); this.file.append("TorBrowser"); } this.file.appendRelativePath(this.path); } else { this.file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); this.file.initWithPath(this.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; } } static get isUserDataOutsideOfAppDir() { if (this._isUserDataOutsideOfAppDir === undefined) { // Determine if we are using a "side-by-side" data model by checking // whether the user profile is outside of the app directory. try { const profDir = Services.dirsvc.get("ProfD", Ci.nsIFile); this._isUserDataOutsideOfAppDir = !this.appDir.contains(profDir); } catch (e) { this._isUserDataOutsideOfAppDir = false; } } return this._isUserDataOutsideOfAppDir; } // Returns an nsIFile that points to the application directory. static get appDir() { if (!this._appDir) { let topDir = Services.dirsvc.get("CurProcD", Ci.nsIFile); // On Linux and Windows, we want to return the Browser/ directory. // Because topDir ("CurProcD") points to Browser/browser on those // platforms, we need to go up one level. // On Mac OS, we want to return the TorBrowser.app/ directory. // Because topDir points to Contents/Resources/browser on Mac OS, // we need to go up 3 levels. let tbbBrowserDepth = TorLauncherUtil.isMac ? 3 : 1; while (tbbBrowserDepth > 0) { let didRemove = topDir.leafName != "."; topDir = topDir.parent; if (didRemove) { tbbBrowserDepth--; } } this._appDir = topDir; } return this._appDir; } // Returns an nsIFile that points to the TorBrowser-Data/ directory. // This function is only used when isUserDataOutsideOfAppDir === true. // May throw. static get dataDir() { if (!this._dataDir) { const profDir = Services.dirsvc.get("ProfD", Ci.nsIFile); this._dataDir = profDir.parent.parent; } return this._dataDir; } } const TorLauncherUtil = Object.freeze({ get isMac() { return Services.appinfo.OS === "Darwin"; }, get isWindows() { return Services.appinfo.OS === "WINNT"; }, // 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 +506 −0 File added.Preview size limit exceeded, changes collapsed. Show changes