commit dd142c5a83dbee21d4e060bad404e8f20a9e30ab Author: Richard Pospesel richard@torproject.org Date: Fri Aug 6 16:39:03 2021 +0200
Bug 40597: Implement TorSettings module
- migrated in-page settings read/write implementation from about:preferences#tor to the TorSettings module - TorSettings initially loads settings from the tor daemon, and saves them to firefox prefs - TorSettings notifies observers when a setting has changed; currently only QuickStart notification is implemented for parity with previous preference notify logic in about:torconnect and about:preferences#tor - about:preferences#tor, and about:torconnect now read and write settings thorugh the TorSettings module - all tor settings live in the torbrowser.settings.* preference branch - removed unused pref modify permission for about:torconnect content page from AsyncPrefs.jsm --- browser/components/torconnect/TorConnectParent.jsm | 25 +- .../torpreferences/content/parseFunctions.jsm | 89 --- .../torpreferences/content/torBridgeSettings.jsm | 325 -------- .../torpreferences/content/torFirewallSettings.jsm | 72 -- .../components/torpreferences/content/torPane.js | 301 ++++---- .../torpreferences/content/torProxySettings.jsm | 245 ------- browser/components/torpreferences/jar.mn | 12 +- browser/modules/TorConnect.jsm | 47 +- browser/modules/TorSettings.jsm | 814 +++++++++++++++++++++ browser/modules/moz.build | 1 + .../processsingleton/MainProcessSingleton.jsm | 5 + toolkit/modules/AsyncPrefs.jsm | 1 - 12 files changed, 989 insertions(+), 948 deletions(-)
diff --git a/browser/components/torconnect/TorConnectParent.jsm b/browser/components/torconnect/TorConnectParent.jsm index 526c588a423e..2fbc2a5c7c7c 100644 --- a/browser/components/torconnect/TorConnectParent.jsm +++ b/browser/components/torconnect/TorConnectParent.jsm @@ -7,10 +7,9 @@ const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); const { TorConnect, TorConnectTopics, TorConnectState } = ChromeUtils.import( "resource:///modules/TorConnect.jsm" ); - -const TorLauncherPrefs = Object.freeze({ - quickstart: "extensions.torlauncher.quickstart", -}); +const { TorSettings, TorSettingsTopics, TorSettingsData } = ChromeUtils.import( + "resource:///modules/TorSettings.jsm" +);
/* This object is basically a marshalling interface between the TorConnect module @@ -31,7 +30,7 @@ class TorConnectParent extends JSWindowActorParent { BootstrapProgress: TorConnect.bootstrapProgress, BootstrapStatus: TorConnect.bootstrapStatus, ShowCopyLog: TorConnect.logHasWarningOrError, - QuickStartEnabled: Services.prefs.getBoolPref(TorLauncherPrefs.quickstart, false), + QuickStartEnabled: TorSettings.quickstart.enabled, };
// JSWindowActiveParent derived objects cannot observe directly, so create a member @@ -78,9 +77,12 @@ class TorConnectParent extends JSWindowActorParent { // TODO: handle break; } - case "nsPref:changed": { - if (aData === TorLauncherPrefs.quickstart) { - self.state.QuickStartEnabled = Services.prefs.getBoolPref(TorLauncherPrefs.quickstart); + case TorSettingsTopics.SettingChanged:{ + if (aData === TorSettingsData.QuickStartEnabled) { + self.state.QuickStartEnabled = obj.value; + } else { + // this isn't a setting torconnect cares about + return; } break; } @@ -98,7 +100,7 @@ class TorConnectParent extends JSWindowActorParent { const topic = TorConnectTopics[key]; Services.obs.addObserver(this.torConnectObserver, topic); } - Services.prefs.addObserver(TorLauncherPrefs.quickstart, this.torConnectObserver); + Services.obs.addObserver(this.torConnectObserver, TorSettingsTopics.SettingChanged); }
willDestroy() { @@ -107,13 +109,14 @@ class TorConnectParent extends JSWindowActorParent { const topic = TorConnectTopics[key]; Services.obs.removeObserver(this.torConnectObserver, topic); } - Services.prefs.removeObserver(TorLauncherPrefs.quickstart, this.torConnectObserver); + Services.obs.removeObserver(this.torConnectObserver, TorSettingsTopics.SettingChanged); }
receiveMessage(message) { switch (message.name) { case "torconnect:set-quickstart": - Services.prefs.setBoolPref(TorLauncherPrefs.quickstart, message.data); + TorSettings.quickstart.enabled = message.data; + TorSettings.saveToPrefs().applySettings(); break; case "torconnect:open-tor-preferences": TorConnect.openTorPreferences(); diff --git a/browser/components/torpreferences/content/parseFunctions.jsm b/browser/components/torpreferences/content/parseFunctions.jsm deleted file mode 100644 index 954759de63a5..000000000000 --- a/browser/components/torpreferences/content/parseFunctions.jsm +++ /dev/null @@ -1,89 +0,0 @@ -"use strict"; - -var EXPORTED_SYMBOLS = [ - "parsePort", - "parseAddrPort", - "parseUsernamePassword", - "parseAddrPortList", - "parseBridgeStrings", - "parsePortList", -]; - -// expects a string representation of an integer from 1 to 65535 -let parsePort = function(aPort) { - // ensure port string is a valid positive integer - const validIntRegex = /^[0-9]+$/; - if (!validIntRegex.test(aPort)) { - throw new Error(`Invalid PORT string : '${aPort}'`); - } - - // ensure port value is on valid range - let port = Number.parseInt(aPort); - if (port < 1 || port > 65535) { - throw new Error( - `Invalid PORT value, needs to be on range [1,65535] : '${port}'` - ); - } - - return port; -}; -// expects a string in the format: "ADDRESS:PORT" -let parseAddrPort = function(aAddrColonPort) { - let tokens = aAddrColonPort.split(":"); - if (tokens.length != 2) { - throw new Error(`Invalid ADDRESS:PORT string : '${aAddrColonPort}'`); - } - let address = tokens[0]; - let port = parsePort(tokens[1]); - return [address, port]; -}; - -// expects a string in the format: "USERNAME:PASSWORD" -// split on the first colon and any subsequent go into password -let parseUsernamePassword = function(aUsernameColonPassword) { - let colonIndex = aUsernameColonPassword.indexOf(":"); - if (colonIndex < 0) { - // we don't log the contents of the potentially password containing string - throw new Error("Invalid USERNAME:PASSWORD string"); - } - - let username = aUsernameColonPassword.substring(0, colonIndex); - let password = aUsernameColonPassword.substring(colonIndex + 1); - - return [username, password]; -}; - -// expects a string in the format: ADDRESS:PORT,ADDRESS:PORT,... -// returns array of ports (as ints) -let parseAddrPortList = function(aAddrPortList) { - let addrPorts = aAddrPortList.split(","); - // parse ADDRESS:PORT string and only keep the port (second element in returned array) - let retval = addrPorts.map(addrPort => parseAddrPort(addrPort)[1]); - return retval; -}; - -// expects a '/n' or '/r/n' delimited bridge string, which we split and trim -// each bridge string can also optionally have 'bridge' at the beginning ie: -// bridge $(type) $(address):$(port) $(certificate) -// we strip out the 'bridge' prefix here -let parseBridgeStrings = function(aBridgeStrings) { - - // replace carriage returns ('\r') with new lines ('\n') - aBridgeStrings = aBridgeStrings.replace(/\r/g, "\n"); - // then replace contiguous new lines ('\n') with a single one - aBridgeStrings = aBridgeStrings.replace(/[\n]+/g, "\n"); - - // split on the newline and for each bridge string: trim, remove starting 'bridge' string - // finally discard entries that are empty strings; empty strings could occur if we receive - // a new line containing only whitespace - let splitStrings = aBridgeStrings.split("\n"); - return splitStrings.map(val => val.trim().replace(/^bridge\s+/i, "")) - .filter(bridgeString => bridgeString != ""); -}; - -// expecting a ',' delimited list of ints with possible white space between -// returns an array of ints -let parsePortList = function(aPortListString) { - let splitStrings = aPortListString.split(","); - return splitStrings.map(val => parsePort(val.trim())); -}; diff --git a/browser/components/torpreferences/content/torBridgeSettings.jsm b/browser/components/torpreferences/content/torBridgeSettings.jsm deleted file mode 100644 index ceb61d3ec972..000000000000 --- a/browser/components/torpreferences/content/torBridgeSettings.jsm +++ /dev/null @@ -1,325 +0,0 @@ -"use strict"; - -var EXPORTED_SYMBOLS = [ - "TorBridgeSource", - "TorBridgeSettings", - "makeTorBridgeSettingsNone", - "makeTorBridgeSettingsBuiltin", - "makeTorBridgeSettingsBridgeDB", - "makeTorBridgeSettingsUserProvided", -]; - -const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); -const { TorProtocolService } = ChromeUtils.import( - "resource:///modules/TorProtocolService.jsm" -); -const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); - -const TorBridgeSource = { - NONE: "NONE", - BUILTIN: "BUILTIN", - BRIDGEDB: "BRIDGEDB", - USERPROVIDED: "USERPROVIDED", -}; - -class TorBridgeSettings { - constructor() { - this._bridgeSource = TorBridgeSource.NONE; - this._selectedDefaultBridgeType = null; - this._bridgeStrings = []; - } - - get selectedDefaultBridgeType() { - if (this._bridgeSource == TorBridgeSource.BUILTIN) { - return this._selectedDefaultBridgeType; - } - return undefined; - } - - get bridgeSource() { - return this._bridgeSource; - } - - // for display - get bridgeStrings() { - return this._bridgeStrings.join("\n"); - } - - // raw - get bridgeStringsArray() { - return this._bridgeStrings; - } - - static get defaultBridgeTypes() { - if (TorBridgeSettings._defaultBridgeTypes) { - return TorBridgeSettings._defaultBridgeTypes; - } - - let bridgeListBranch = Services.prefs.getBranch( - TorStrings.preferenceBranches.defaultBridge - ); - let bridgePrefs = bridgeListBranch.getChildList("", {}); - - // an unordered set for shoving bridge types into - let bridgeTypes = new Set(); - // look for keys ending in ".N" and treat string before that as the bridge type - const pattern = /.[0-9]+$/; - for (const key of bridgePrefs) { - const offset = key.search(pattern); - if (offset != -1) { - const bt = key.substring(0, offset); - bridgeTypes.add(bt); - } - } - - // recommended bridge type goes first in the list - let recommendedBridgeType = Services.prefs.getCharPref( - TorStrings.preferenceKeys.recommendedBridgeType, - null - ); - - let retval = []; - if (recommendedBridgeType && bridgeTypes.has(recommendedBridgeType)) { - retval.push(recommendedBridgeType); - } - - for (const bridgeType of bridgeTypes.values()) { - if (bridgeType != recommendedBridgeType) { - retval.push(bridgeType); - } - } - - // cache off - TorBridgeSettings._defaultBridgeTypes = retval; - return retval; - } - - _readDefaultBridges(aBridgeType) { - let bridgeBranch = Services.prefs.getBranch( - TorStrings.preferenceBranches.defaultBridge - ); - let bridgeBranchPrefs = bridgeBranch.getChildList("", {}); - - let retval = []; - - // regex matches against strings ending in ".N" where N is a positive integer - let pattern = /.[0-9]+$/; - for (const key of bridgeBranchPrefs) { - // verify the location of the match is the correct offset required for aBridgeType - // to fit, and that the string begins with aBridgeType - if ( - key.search(pattern) == aBridgeType.length && - key.startsWith(aBridgeType) - ) { - let bridgeStr = bridgeBranch.getCharPref(key); - retval.push(bridgeStr); - } - } - - // fisher-yates shuffle - // shuffle so that Tor Browser users don't all try the built-in bridges in the same order - for (let i = retval.length - 1; i > 0; --i) { - // number n such that 0.0 <= n < 1.0 - const n = Math.random(); - // integer j such that 0 <= j <= i - const j = Math.floor(n * (i + 1)); - - // swap values at indices i and j - const tmp = retval[i]; - retval[i] = retval[j]; - retval[j] = tmp; - } - - return retval; - } - - _readBridgeDBBridges() { - let bridgeBranch = Services.prefs.getBranch( - `${TorStrings.preferenceBranches.bridgeDBBridges}` - ); - let bridgeBranchPrefs = bridgeBranch.getChildList("", {}); - // the child prefs do not come in any particular order so sort the keys - // so the values can be compared to what we get out off torrc - bridgeBranchPrefs.sort(); - - // just assume all of the prefs under the parent point to valid bridge string - let retval = bridgeBranchPrefs.map(key => - bridgeBranch.getCharPref(key).trim() - ); - - return retval; - } - - _readTorrcBridges() { - let bridgeList = TorProtocolService.readStringArraySetting( - TorStrings.configKeys.bridgeList - ); - - let retval = []; - for (const line of bridgeList) { - let trimmedLine = line.trim(); - if (trimmedLine) { - retval.push(trimmedLine); - } - } - - return retval; - } - - // analagous to initBridgeSettings() - readSettings() { - // restore to defaults - this._bridgeSource = TorBridgeSource.NONE; - this._selectedDefaultBridgeType = null; - this._bridgeStrings = []; - - // So the way tor-launcher determines the origin of the configured bridges is a bit - // weird and depends on inferring our scenario based on some firefox prefs and the - // relationship between the saved list of bridges in about:config vs the list saved in torrc - - // first off, if "extensions.torlauncher.default_bridge_type" is set to one of our - // builtin default types (obfs4, meek-azure, snowflake, etc) then we provide the - // bridges in "extensions.torlauncher.default_bridge.*" (filtered by our default_bridge_type) - - // next, we compare the list of bridges saved in torrc to the bridges stored in the - // "extensions.torlauncher.bridgedb_bridge."" branch. If they match *exactly* then we assume - // the bridges were retrieved from BridgeDB and use those. If the torrc list is empty then we know - // we have no bridge settings - - // finally, if none of the previous conditions are not met, it is assumed the bridges stored in - // torrc are user-provided - - // what we should(?) do once we excise tor-launcher entirely is explicitly store an int/enum in - // about:config that tells us which scenario we are in so we don't have to guess - - let defaultBridgeType = Services.prefs.getCharPref( - TorStrings.preferenceKeys.defaultBridgeType, - null - ); - - // check if source is BUILTIN - if (defaultBridgeType) { - this._bridgeStrings = this._readDefaultBridges(defaultBridgeType); - this._bridgeSource = TorBridgeSource.BUILTIN; - this._selectedDefaultBridgeType = defaultBridgeType; - return; - } - - let torrcBridges = this._readTorrcBridges(); - - // no stored bridges means no bridge is in use - if (torrcBridges.length == 0) { - this._bridgeStrings = []; - this._bridgeSource = TorBridgeSource.NONE; - return; - } - - let bridgedbBridges = this._readBridgeDBBridges(); - - // if these two lists are equal then we got our bridges from bridgedb - // ie: same element in identical order - let arraysEqual = (left, right) => { - if (left.length != right.length) { - return false; - } - const length = left.length; - for (let i = 0; i < length; ++i) { - if (left[i] != right[i]) { - return false; - } - } - return true; - }; - - // agreement between prefs and torrc means bridgedb bridges - if (arraysEqual(torrcBridges, bridgedbBridges)) { - this._bridgeStrings = torrcBridges; - this._bridgeSource = TorBridgeSource.BRIDGEDB; - return; - } - - // otherwise they must be user provided - this._bridgeStrings = torrcBridges; - this._bridgeSource = TorBridgeSource.USERPROVIDED; - } - - writeSettings() { - let settingsObject = new Map(); - - // init tor bridge settings to null - settingsObject.set(TorStrings.configKeys.useBridges, null); - settingsObject.set(TorStrings.configKeys.bridgeList, null); - - // clear bridge related firefox prefs - Services.prefs.setCharPref(TorStrings.preferenceKeys.defaultBridgeType, ""); - let bridgeBranch = Services.prefs.getBranch( - `${TorStrings.preferenceBranches.bridgeDBBridges}` - ); - let bridgeBranchPrefs = bridgeBranch.getChildList("", {}); - for (const pref of bridgeBranchPrefs) { - Services.prefs.clearUserPref( - `${TorStrings.preferenceBranches.bridgeDBBridges}${pref}` - ); - } - - switch (this._bridgeSource) { - case TorBridgeSource.BUILTIN: - // set builtin bridge type to use in prefs - Services.prefs.setCharPref( - TorStrings.preferenceKeys.defaultBridgeType, - this._selectedDefaultBridgeType - ); - break; - case TorBridgeSource.BRIDGEDB: - // save bridges off to prefs - for (let i = 0; i < this.bridgeStringsArray.length; ++i) { - Services.prefs.setCharPref( - `${TorStrings.preferenceBranches.bridgeDBBridges}${i}`, - this.bridgeStringsArray[i] - ); - } - break; - } - - // write over our bridge list if bridges are enabled - if (this._bridgeSource != TorBridgeSource.NONE) { - settingsObject.set(TorStrings.configKeys.useBridges, true); - settingsObject.set( - TorStrings.configKeys.bridgeList, - this.bridgeStringsArray - ); - } - TorProtocolService.writeSettings(settingsObject); - } -} - -function makeTorBridgeSettingsNone() { - return new TorBridgeSettings(); -} - -function makeTorBridgeSettingsBuiltin(aBridgeType) { - let retval = new TorBridgeSettings(); - retval._bridgeSource = TorBridgeSource.BUILTIN; - retval._selectedDefaultBridgeType = aBridgeType; - retval._bridgeStrings = retval._readDefaultBridges(aBridgeType); - - return retval; -} - -function makeTorBridgeSettingsBridgeDB(aBridges) { - let retval = new TorBridgeSettings(); - retval._bridgeSource = TorBridgeSource.BRIDGEDB; - retval._selectedDefaultBridgeType = null; - retval._bridgeStrings = aBridges; - - return retval; -} - -function makeTorBridgeSettingsUserProvided(aBridges) { - let retval = new TorBridgeSettings(); - retval._bridgeSource = TorBridgeSource.USERPROVIDED; - retval._selectedDefaultBridgeType = null; - retval._bridgeStrings = aBridges; - - return retval; -} diff --git a/browser/components/torpreferences/content/torFirewallSettings.jsm b/browser/components/torpreferences/content/torFirewallSettings.jsm deleted file mode 100644 index e77f18ef2fae..000000000000 --- a/browser/components/torpreferences/content/torFirewallSettings.jsm +++ /dev/null @@ -1,72 +0,0 @@ -"use strict"; - -var EXPORTED_SYMBOLS = [ - "TorFirewallSettings", - "makeTorFirewallSettingsNone", - "makeTorFirewallSettingsCustom", -]; - -const { TorProtocolService } = ChromeUtils.import( - "resource:///modules/TorProtocolService.jsm" -); -const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); -const { parseAddrPortList } = ChromeUtils.import( - "chrome://browser/content/torpreferences/parseFunctions.jsm" -); - -class TorFirewallSettings { - constructor() { - this._allowedPorts = []; - } - - get portsConfigurationString() { - let portStrings = this._allowedPorts.map(port => `*:${port}`); - return portStrings.join(","); - } - - get commaSeparatedListString() { - return this._allowedPorts.join(","); - } - - get hasPorts() { - return this._allowedPorts.length > 0; - } - - readSettings() { - let addressPortList = TorProtocolService.readStringSetting( - TorStrings.configKeys.reachableAddresses - ); - - let allowedPorts = []; - if (addressPortList) { - allowedPorts = parseAddrPortList(addressPortList); - } - this._allowedPorts = allowedPorts; - } - - writeSettings() { - let settingsObject = new Map(); - - // init to null so Tor daemon resets if no ports - settingsObject.set(TorStrings.configKeys.reachableAddresses, null); - - if (this._allowedPorts.length > 0) { - settingsObject.set( - TorStrings.configKeys.reachableAddresses, - this.portsConfigurationString - ); - } - - TorProtocolService.writeSettings(settingsObject); - } -} - -function makeTorFirewallSettingsNone() { - return new TorFirewallSettings(); -} - -function makeTorFirewallSettingsCustom(aPortsList) { - let retval = new TorFirewallSettings(); - retval._allowedPorts = aPortsList; - return retval; -} diff --git a/browser/components/torpreferences/content/torPane.js b/browser/components/torpreferences/content/torPane.js index 1f169cbe1a55..6905cbe473ac 100644 --- a/browser/components/torpreferences/content/torPane.js +++ b/browser/components/torpreferences/content/torPane.js @@ -2,6 +2,10 @@
/* global Services */
+const { TorSettings, TorSettingsTopics, TorSettingsData, TorBridgeSource, TorBuiltinBridgeTypes, TorProxyType } = ChromeUtils.import( + "resource:///modules/TorSettings.jsm" +); + const { TorProtocolService } = ChromeUtils.import( "resource:///modules/TorProtocolService.jsm" ); @@ -10,35 +14,6 @@ const { TorConnect, TorConnectTopics, TorConnectState } = ChromeUtils.import( "resource:///modules/TorConnect.jsm" );
-const { - TorBridgeSource, - TorBridgeSettings, - makeTorBridgeSettingsNone, - makeTorBridgeSettingsBuiltin, - makeTorBridgeSettingsBridgeDB, - makeTorBridgeSettingsUserProvided, -} = ChromeUtils.import( - "chrome://browser/content/torpreferences/torBridgeSettings.jsm" -); - -const { - TorProxyType, - TorProxySettings, - makeTorProxySettingsNone, - makeTorProxySettingsSocks4, - makeTorProxySettingsSocks5, - makeTorProxySettingsHTTPS, -} = ChromeUtils.import( - "chrome://browser/content/torpreferences/torProxySettings.jsm" -); -const { - TorFirewallSettings, - makeTorFirewallSettingsNone, - makeTorFirewallSettingsCustom, -} = ChromeUtils.import( - "chrome://browser/content/torpreferences/torFirewallSettings.jsm" -); - const { TorLogDialog } = ChromeUtils.import( "chrome://browser/content/torpreferences/torLogDialog.jsm" ); @@ -53,14 +28,6 @@ ChromeUtils.defineModuleGetter( "resource:///modules/TorStrings.jsm" );
-const { parsePort, parseBridgeStrings, parsePortList } = ChromeUtils.import( - "chrome://browser/content/torpreferences/parseFunctions.jsm" -); - -const TorLauncherPrefs = { - quickstart: "extensions.torlauncher.quickstart", -} - /* Tor Pane
@@ -261,10 +228,11 @@ const gTorPane = (function() { ); this._enableQuickstartCheckbox.addEventListener("command", e => { const checked = this._enableQuickstartCheckbox.checked; - Services.prefs.setBoolPref(TorLauncherPrefs.quickstart, checked); + TorSettings.quickstart.enabled = checked; + TorSettings.saveToPrefs().applySettings(); }); - this._enableQuickstartCheckbox.checked = Services.prefs.getBoolPref(TorLauncherPrefs.quickstart); - Services.prefs.addObserver(TorLauncherPrefs.quickstart, this); + this._enableQuickstartCheckbox.checked = TorSettings.quickstart.enabled; + Services.obs.addObserver(this, TorSettingsTopics.SettingChanged);
// Bridge setup prefpane.querySelector(selectors.bridges.header).innerText = @@ -291,7 +259,7 @@ const gTorPane = (function() { this._bridgeSelectionRadiogroup = prefpane.querySelector( selectors.bridges.bridgeSelectionRadiogroup ); - this._bridgeSelectionRadiogroup.value = TorBridgeSource.BUILTIN; + this._bridgeSelectionRadiogroup.value = TorBridgeSource.BuiltIn; this._bridgeSelectionRadiogroup.addEventListener("command", e => { const value = this._bridgeSelectionRadiogroup.value; gTorPane.onSelectBridgeOption(value).onUpdateBridgeSettings(); @@ -305,7 +273,7 @@ const gTorPane = (function() { "label", TorStrings.settings.selectBridge ); - this._builtinBridgeOption.setAttribute("value", TorBridgeSource.BUILTIN); + this._builtinBridgeOption.setAttribute("value", TorBridgeSource.BuiltIn); this._builtinBridgeMenulist = prefpane.querySelector( selectors.bridges.builtinBridgeList ); @@ -321,7 +289,7 @@ const gTorPane = (function() { "label", TorStrings.settings.requestBridgeFromTorProject ); - this._requestBridgeOption.setAttribute("value", TorBridgeSource.BRIDGEDB); + this._requestBridgeOption.setAttribute("value", TorBridgeSource.BridgeDB); this._requestBridgeButton = prefpane.querySelector( selectors.bridges.requestBridgeButton ); @@ -346,7 +314,7 @@ const gTorPane = (function() { ); this._provideBridgeOption.setAttribute( "value", - TorBridgeSource.USERPROVIDED + TorBridgeSource.UserProvided ); prefpane.querySelector( selectors.bridges.provideBridgeDescription @@ -395,11 +363,11 @@ const gTorPane = (function() {
let mockProxies = [ { - value: TorProxyType.SOCKS4, + value: TorProxyType.Socks4, label: TorStrings.settings.proxyTypeSOCKS4, }, { - value: TorProxyType.SOCKS5, + value: TorProxyType.Socks5, label: TorStrings.settings.proxyTypeSOCKS5, }, { value: TorProxyType.HTTPS, label: TorStrings.settings.proxyTypeHTTP }, @@ -550,12 +518,8 @@ const gTorPane = (function() { true );
- // load bridge settings - let torBridgeSettings = new TorBridgeSettings(); - torBridgeSettings.readSettings(); - - // populate the bridge list - for (let currentBridge of TorBridgeSettings.defaultBridgeTypes) { + // init bridge UI + for (let currentBridge of TorBuiltinBridgeTypes) { let menuEntry = document.createXULElement("menuitem"); menuEntry.setAttribute("value", currentBridge); menuEntry.setAttribute("label", currentBridge); @@ -564,53 +528,41 @@ const gTorPane = (function() { .appendChild(menuEntry); }
- this.onSelectBridgeOption(torBridgeSettings.bridgeSource); - this.onToggleBridge( - torBridgeSettings.bridgeSource != TorBridgeSource.NONE - ); - switch (torBridgeSettings.bridgeSource) { - case TorBridgeSource.NONE: - break; - case TorBridgeSource.BUILTIN: - this._builtinBridgeMenulist.value = - torBridgeSettings.selectedDefaultBridgeType; - break; - case TorBridgeSource.BRIDGEDB: - this._requestBridgeTextarea.value = torBridgeSettings.bridgeStrings; - break; - case TorBridgeSource.USERPROVIDED: - this._provideBridgeTextarea.value = torBridgeSettings.bridgeStrings; - break; + if (TorSettings.bridges.enabled) { + this.onSelectBridgeOption(TorSettings.bridges.source); + this.onToggleBridge( + TorSettings.bridges.source != TorBridgeSource.Invalid + ); + switch (TorSettings.bridges.source) { + case TorBridgeSource.Invalid: + break; + case TorBridgeSource.BuiltIn: + this._builtinBridgeMenulist.value = TorSettings.bridges.builtin_type; + break; + case TorBridgeSource.BridgeDB: + this._requestBridgeTextarea.value = TorSettings.bridges.bridge_strings.join("\n"); + break; + case TorBridgeSource.UserProvided: + this._provideBridgeTextarea.value = TorSettings.bridges.bridge_strings.join("\n"); + break; + } }
- this._bridgeSettings = torBridgeSettings; - - // load proxy settings - let torProxySettings = new TorProxySettings(); - torProxySettings.readSettings(); - - if (torProxySettings.type != TorProxyType.NONE) { + // init proxy UI + if (TorSettings.proxy.enabled) { this.onToggleProxy(true); - this.onSelectProxyType(torProxySettings.type); - this._proxyAddressTextbox.value = torProxySettings.address; - this._proxyPortTextbox.value = torProxySettings.port; - this._proxyUsernameTextbox.value = torProxySettings.username; - this._proxyPasswordTextbox.value = torProxySettings.password; + this.onSelectProxyType(TorSettings.proxy.type); + this._proxyAddressTextbox.value = TorSettings.proxy.address; + this._proxyPortTextbox.value = TorSettings.proxy.port; + this._proxyUsernameTextbox.value = TorSettings.proxy.username; + this._proxyPasswordTextbox.value = TorSettings.proxy.password; }
- this._proxySettings = torProxySettings; - - // load firewall settings - let torFirewallSettings = new TorFirewallSettings(); - torFirewallSettings.readSettings(); - - if (torFirewallSettings.hasPorts) { + // init firewall + if (TorSettings.firewall.enabled) { this.onToggleFirewall(true); - this._allowedPortsTextbox.value = - torFirewallSettings.commaSeparatedListString; + this._allowedPortsTextbox.value = TorSettings.firewall.allowed_ports.join(", "); } - - this._firewallSettings = torFirewallSettings; },
init() { @@ -683,13 +635,17 @@ const gTorPane = (function() { if (enabled) { this.onSelectBridgeOption(this._bridgeSelectionRadiogroup.value); } else { - this.onSelectBridgeOption(TorBridgeSource.NONE); + this.onSelectBridgeOption(TorBridgeSource.Invalid); } return this; },
// callback when a bridge option is selected onSelectBridgeOption(source) { + if (typeof source === "string") { + source = parseInt(source); + } + // disable all of the bridge elements under radio buttons this._setElementsDisabled( [ @@ -701,23 +657,23 @@ const gTorPane = (function() { true );
- if (source != TorBridgeSource.NONE) { + if (source != TorBridgeSource.Invalid) { this._bridgeSelectionRadiogroup.value = source; }
switch (source) { - case TorBridgeSource.BUILTIN: { + case TorBridgeSource.BuiltIn: { this._setElementsDisabled([this._builtinBridgeMenulist], false); break; } - case TorBridgeSource.BRIDGEDB: { + case TorBridgeSource.BridgeDB: { this._setElementsDisabled( [this._requestBridgeButton, this._requestBridgeTextarea], false ); break; } - case TorBridgeSource.USERPROVIDED: { + case TorBridgeSource.UserProvided: { this._setElementsDisabled([this._provideBridgeTextarea], false); break; } @@ -730,14 +686,17 @@ const gTorPane = (function() { let requestBridgeDialog = new RequestBridgeDialog(); requestBridgeDialog.openDialog( gSubDialog, - this._proxySettings.proxyURI, + TorSettings.proxy.uri, aBridges => { if (aBridges.length > 0) { - let bridgeSettings = makeTorBridgeSettingsBridgeDB(aBridges); - bridgeSettings.writeSettings(); - this._bridgeSettings = bridgeSettings; - - this._requestBridgeTextarea.value = bridgeSettings.bridgeStrings; + let bridgeStrings = aBridges.join("\n"); + TorSettings.bridges.enabled = true; + TorSettings.bridges.source = TorBridgeSource.BridgeDB; + TorSettings.bridges.bridge_strings = bridgeStrings; + TorSettings.saveToPrefs(); + TorSettings.applySettings().then((result) => { + this._requestBridgeTextarea.value = bridgeStrings; + }); } } ); @@ -746,53 +705,56 @@ const gTorPane = (function() {
// pushes bridge settings from UI to tor onUpdateBridgeSettings() { - let bridgeSettings = null; - let source = this._useBridgeCheckbox.checked - ? this._bridgeSelectionRadiogroup.value - : TorBridgeSource.NONE; + ? parseInt(this._bridgeSelectionRadiogroup.value) + : TorBridgeSource.Invalid; + switch (source) { - case TorBridgeSource.NONE: { - bridgeSettings = makeTorBridgeSettingsNone(); - break; + case TorBridgeSource.Invalid: { + TorSettings.bridges.enabled = false; } - case TorBridgeSource.BUILTIN: { + break; + case TorBridgeSource.BuiltIn: { // if there is a built-in bridge already selected, use that let bridgeType = this._builtinBridgeMenulist.value; + console.log(`bridge type: ${bridgeType}`); if (bridgeType) { - bridgeSettings = makeTorBridgeSettingsBuiltin(bridgeType); + TorSettings.bridges.enabled = true; + TorSettings.bridges.source = TorBridgeSource.BuiltIn; + TorSettings.bridges.builtin_type = bridgeType; } else { - bridgeSettings = makeTorBridgeSettingsNone(); + TorSettings.bridges.enabled = false; } break; } - case TorBridgeSource.BRIDGEDB: { + case TorBridgeSource.BridgeDB: { // if there are bridgedb bridges saved in the text area, use them let bridgeStrings = this._requestBridgeTextarea.value; if (bridgeStrings) { - let bridgeStringList = parseBridgeStrings(bridgeStrings); - bridgeSettings = makeTorBridgeSettingsBridgeDB(bridgeStringList); + TorSettings.bridges.enabled = true; + TorSettings.bridges.source = TorBridgeSource.BridgeDB; + TorSettings.bridges.bridge_strings = bridgeStrings; } else { - bridgeSettings = makeTorBridgeSettingsNone(); + TorSettings.bridges.enabled = false; } break; } - case TorBridgeSource.USERPROVIDED: { + case TorBridgeSource.UserProvided: { // if bridges already exist in the text area, use them let bridgeStrings = this._provideBridgeTextarea.value; if (bridgeStrings) { - let bridgeStringList = parseBridgeStrings(bridgeStrings); - bridgeSettings = makeTorBridgeSettingsUserProvided( - bridgeStringList - ); + TorSettings.bridges.enabled = true; + TorSettings.bridges.source = TorBridgeSource.UserProvided; + TorSettings.bridges.bridge_strings = bridgeStrings; } else { - bridgeSettings = makeTorBridgeSettingsNone(); + TorSettings.bridges.enabled = false; } break; } } - bridgeSettings.writeSettings(); - this._bridgeSettings = bridgeSettings; + TorSettings.saveToPrefs(); + TorSettings.applySettings(); + return this; },
@@ -822,12 +784,13 @@ const gTorPane = (function() {
// callback when proxy type is changed onSelectProxyType(value) { - if (value == "") { - value = TorProxyType.NONE; + if (typeof value === "string") { + value = parseInt(value); } + this._proxyTypeMenulist.value = value; switch (value) { - case TorProxyType.NONE: { + case TorProxyType.Invalid: { this._setElementsDisabled( [ this._proxyAddressLabel, @@ -848,7 +811,7 @@ const gTorPane = (function() { this._proxyPasswordTextbox.value = ""; break; } - case TorProxyType.SOCKS4: { + case TorProxyType.Socks4: { this._setElementsDisabled( [ this._proxyAddressLabel, @@ -872,7 +835,7 @@ const gTorPane = (function() { this._proxyPasswordTextbox.value = ""; break; } - case TorProxyType.SOCKS5: + case TorProxyType.Socks5: case TorProxyType.HTTPS: { this._setElementsDisabled( [ @@ -895,46 +858,45 @@ const gTorPane = (function() {
// pushes proxy settings from UI to tor onUpdateProxySettings() { - const proxyType = this._useProxyCheckbox.checked - ? this._proxyTypeMenulist.value - : TorProxyType.NONE; - const addressString = this._proxyAddressTextbox.value; - const portString = this._proxyPortTextbox.value; - const usernameString = this._proxyUsernameTextbox.value; - const passwordString = this._proxyPasswordTextbox.value; - - let proxySettings = null; - - switch (proxyType) { - case TorProxyType.NONE: - proxySettings = makeTorProxySettingsNone(); + const type = this._useProxyCheckbox.checked + ? parseInt(this._proxyTypeMenulist.value) + : TorProxyType.Invalid; + const address = this._proxyAddressTextbox.value; + const port = this._proxyPortTextbox.value; + const username = this._proxyUsernameTextbox.value; + const password = this._proxyPasswordTextbox.value; + + switch (type) { + case TorProxyType.Invalid: + TorSettings.proxy.enabled = false; break; - case TorProxyType.SOCKS4: - proxySettings = makeTorProxySettingsSocks4( - addressString, - parsePort(portString) - ); + case TorProxyType.Socks4: + TorSettings.proxy.enabled = true; + TorSettings.proxy.type = type; + TorSettings.proxy.address = address; + TorSettings.proxy.port = port; + break; - case TorProxyType.SOCKS5: - proxySettings = makeTorProxySettingsSocks5( - addressString, - parsePort(portString), - usernameString, - passwordString - ); + case TorProxyType.Socks5: + TorSettings.proxy.enabled = true; + TorSettings.proxy.type = type; + TorSettings.proxy.address = address; + TorSettings.proxy.port = port; + TorSettings.proxy.username = username; + TorSettings.proxy.password = password; break; case TorProxyType.HTTPS: - proxySettings = makeTorProxySettingsHTTPS( - addressString, - parsePort(portString), - usernameString, - passwordString - ); + TorSettings.proxy.enabled = true; + TorSettings.proxy.type = type; + TorSettings.proxy.address = address; + TorSettings.proxy.port = port; + TorSettings.proxy.username = username; + TorSettings.proxy.password = password; break; } + TorSettings.saveToPrefs(); + TorSettings.applySettings();
- proxySettings.writeSettings(); - this._proxySettings = proxySettings; return this; },
@@ -953,21 +915,20 @@ const gTorPane = (function() {
// pushes firewall settings from UI to tor onUpdateFirewallSettings() { + let portListString = this._useFirewallCheckbox.checked ? this._allowedPortsTextbox.value : ""; - let firewallSettings = null;
if (portListString) { - firewallSettings = makeTorFirewallSettingsCustom( - parsePortList(portListString) - ); + TorSettings.firewall.enabled = true; + TorSettings.firewall.allowed_ports = portListString; } else { - firewallSettings = makeTorFirewallSettingsNone(); + TorSettings.firewall.enabled = false; } + TorSettings.saveToPrefs(); + TorSettings.applySettings();
- firewallSettings.writeSettings(); - this._firewallSettings = firewallSettings; return this; },
diff --git a/browser/components/torpreferences/content/torProxySettings.jsm b/browser/components/torpreferences/content/torProxySettings.jsm deleted file mode 100644 index 98bb5e8d5cbf..000000000000 --- a/browser/components/torpreferences/content/torProxySettings.jsm +++ /dev/null @@ -1,245 +0,0 @@ -"use strict"; - -var EXPORTED_SYMBOLS = [ - "TorProxyType", - "TorProxySettings", - "makeTorProxySettingsNone", - "makeTorProxySettingsSocks4", - "makeTorProxySettingsSocks5", - "makeTorProxySettingsHTTPS", -]; - -const { TorProtocolService } = ChromeUtils.import( - "resource:///modules/TorProtocolService.jsm" -); -const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); -const { parseAddrPort, parseUsernamePassword } = ChromeUtils.import( - "chrome://browser/content/torpreferences/parseFunctions.jsm" -); - -const TorProxyType = { - NONE: "NONE", - SOCKS4: "SOCKS4", - SOCKS5: "SOCKS5", - HTTPS: "HTTPS", -}; - -class TorProxySettings { - constructor() { - this._proxyType = TorProxyType.NONE; - this._proxyAddress = undefined; - this._proxyPort = undefined; - this._proxyUsername = undefined; - this._proxyPassword = undefined; - } - - get type() { - return this._proxyType; - } - get address() { - return this._proxyAddress; - } - get port() { - return this._proxyPort; - } - get username() { - return this._proxyUsername; - } - get password() { - return this._proxyPassword; - } - get proxyURI() { - switch (this._proxyType) { - case TorProxyType.SOCKS4: - return `socks4a://${this._proxyAddress}:${this._proxyPort}`; - case TorProxyType.SOCKS5: - if (this._proxyUsername) { - return `socks5://${this._proxyUsername}:${this._proxyPassword}@${ - this._proxyAddress - }:${this._proxyPort}`; - } - return `socks5://${this._proxyAddress}:${this._proxyPort}`; - case TorProxyType.HTTPS: - if (this._proxyUsername) { - return `http://$%7Bthis._proxyUsername%7D:$%7Bthis._proxyPassword%7D@$%7B - this._proxyAddress - }:${this._proxyPort}`; - } - return `http://$%7Bthis._proxyAddress%7D:$%7Bthis._proxyPort%7D%60; - } - return undefined; - } - - // attempts to read proxy settings from Tor daemon - readSettings() { - // SOCKS4 - { - let addressPort = TorProtocolService.readStringSetting( - TorStrings.configKeys.socks4Proxy - ); - if (addressPort) { - // address+port - let [proxyAddress, proxyPort] = parseAddrPort(addressPort); - - this._proxyType = TorProxyType.SOCKS4; - this._proxyAddress = proxyAddress; - this._proxyPort = proxyPort; - this._proxyUsername = ""; - this._proxyPassword = ""; - - return; - } - } - - // SOCKS5 - { - let addressPort = TorProtocolService.readStringSetting( - TorStrings.configKeys.socks5Proxy - ); - - if (addressPort) { - // address+port - let [proxyAddress, proxyPort] = parseAddrPort(addressPort); - // username - let proxyUsername = TorProtocolService.readStringSetting( - TorStrings.configKeys.socks5ProxyUsername - ); - // password - let proxyPassword = TorProtocolService.readStringSetting( - TorStrings.configKeys.socks5ProxyPassword - ); - - this._proxyType = TorProxyType.SOCKS5; - this._proxyAddress = proxyAddress; - this._proxyPort = proxyPort; - this._proxyUsername = proxyUsername; - this._proxyPassword = proxyPassword; - - return; - } - } - - // HTTP - { - let addressPort = TorProtocolService.readStringSetting( - TorStrings.configKeys.httpsProxy - ); - - if (addressPort) { - // address+port - let [proxyAddress, proxyPort] = parseAddrPort(addressPort); - - // username:password - let proxyAuthenticator = TorProtocolService.readStringSetting( - TorStrings.configKeys.httpsProxyAuthenticator - ); - - let [proxyUsername, proxyPassword] = ["", ""]; - if (proxyAuthenticator) { - [proxyUsername, proxyPassword] = parseUsernamePassword( - proxyAuthenticator - ); - } - - this._proxyType = TorProxyType.HTTPS; - this._proxyAddress = proxyAddress; - this._proxyPort = proxyPort; - this._proxyUsername = proxyUsername; - this._proxyPassword = proxyPassword; - } - } - // no proxy settings - } /* TorProxySettings::ReadFromTor() */ - - // attempts to write proxy settings to Tor daemon - // throws on error - writeSettings() { - let settingsObject = new Map(); - - // init proxy related settings to null so Tor daemon resets them - settingsObject.set(TorStrings.configKeys.socks4Proxy, null); - settingsObject.set(TorStrings.configKeys.socks5Proxy, null); - settingsObject.set(TorStrings.configKeys.socks5ProxyUsername, null); - settingsObject.set(TorStrings.configKeys.socks5ProxyPassword, null); - settingsObject.set(TorStrings.configKeys.httpsProxy, null); - settingsObject.set(TorStrings.configKeys.httpsProxyAuthenticator, null); - - switch (this._proxyType) { - case TorProxyType.SOCKS4: - settingsObject.set( - TorStrings.configKeys.socks4Proxy, - `${this._proxyAddress}:${this._proxyPort}` - ); - break; - case TorProxyType.SOCKS5: - settingsObject.set( - TorStrings.configKeys.socks5Proxy, - `${this._proxyAddress}:${this._proxyPort}` - ); - settingsObject.set( - TorStrings.configKeys.socks5ProxyUsername, - this._proxyUsername - ); - settingsObject.set( - TorStrings.configKeys.socks5ProxyPassword, - this._proxyPassword - ); - break; - case TorProxyType.HTTPS: - settingsObject.set( - TorStrings.configKeys.httpsProxy, - `${this._proxyAddress}:${this._proxyPort}` - ); - settingsObject.set( - TorStrings.configKeys.httpsProxyAuthenticator, - `${this._proxyUsername}:${this._proxyPassword}` - ); - break; - } - - TorProtocolService.writeSettings(settingsObject); - } /* TorProxySettings::WriteToTor() */ -} - -// factory methods for our various supported proxies -function makeTorProxySettingsNone() { - return new TorProxySettings(); -} - -function makeTorProxySettingsSocks4(aProxyAddress, aProxyPort) { - let retval = new TorProxySettings(); - retval._proxyType = TorProxyType.SOCKS4; - retval._proxyAddress = aProxyAddress; - retval._proxyPort = aProxyPort; - return retval; -} - -function makeTorProxySettingsSocks5( - aProxyAddress, - aProxyPort, - aProxyUsername, - aProxyPassword -) { - let retval = new TorProxySettings(); - retval._proxyType = TorProxyType.SOCKS5; - retval._proxyAddress = aProxyAddress; - retval._proxyPort = aProxyPort; - retval._proxyUsername = aProxyUsername; - retval._proxyPassword = aProxyPassword; - return retval; -} - -function makeTorProxySettingsHTTPS( - aProxyAddress, - aProxyPort, - aProxyUsername, - aProxyPassword -) { - let retval = new TorProxySettings(); - retval._proxyType = TorProxyType.HTTPS; - retval._proxyAddress = aProxyAddress; - retval._proxyPort = aProxyPort; - retval._proxyUsername = aProxyUsername; - retval._proxyPassword = aProxyPassword; - return retval; -} diff --git a/browser/components/torpreferences/jar.mn b/browser/components/torpreferences/jar.mn index 857bc9ee3eac..552c92b2feff 100644 --- a/browser/components/torpreferences/jar.mn +++ b/browser/components/torpreferences/jar.mn @@ -1,14 +1,10 @@ browser.jar: - content/browser/torpreferences/parseFunctions.jsm (content/parseFunctions.jsm) - content/browser/torpreferences/requestBridgeDialog.xhtml (content/requestBridgeDialog.xhtml) + content/browser/torpreferences/requestBridgeDialog.xhtml (content/requestBridgeDialog.xhtml) content/browser/torpreferences/requestBridgeDialog.jsm (content/requestBridgeDialog.jsm) - content/browser/torpreferences/torBridgeSettings.jsm (content/torBridgeSettings.jsm) - content/browser/torpreferences/torCategory.inc.xhtml (content/torCategory.inc.xhtml) - content/browser/torpreferences/torFirewallSettings.jsm (content/torFirewallSettings.jsm) + content/browser/torpreferences/torCategory.inc.xhtml (content/torCategory.inc.xhtml) content/browser/torpreferences/torLogDialog.jsm (content/torLogDialog.jsm) - content/browser/torpreferences/torLogDialog.xhtml (content/torLogDialog.xhtml) + content/browser/torpreferences/torLogDialog.xhtml (content/torLogDialog.xhtml) content/browser/torpreferences/torPane.js (content/torPane.js) - content/browser/torpreferences/torPane.xhtml (content/torPane.xhtml) + content/browser/torpreferences/torPane.xhtml (content/torPane.xhtml) content/browser/torpreferences/torPreferences.css (content/torPreferences.css) content/browser/torpreferences/torPreferencesIcon.svg (content/torPreferencesIcon.svg) - content/browser/torpreferences/torProxySettings.jsm (content/torProxySettings.jsm) diff --git a/browser/modules/TorConnect.jsm b/browser/modules/TorConnect.jsm index 19a3f595d490..e7b8b364db86 100644 --- a/browser/modules/TorConnect.jsm +++ b/browser/modules/TorConnect.jsm @@ -18,6 +18,10 @@ const { TorLauncherUtil } = ChromeUtils.import( "resource://torlauncher/modules/tl-util.jsm" );
+const { TorSettings, TorSettingsTopics } = ChromeUtils.import( + "resource:///modules/TorSettings.jsm" +); + /* Browser observer topis */ const BrowserTopics = Object.freeze({ ProfileAfterChange: "profile-after-change", @@ -25,7 +29,6 @@ const BrowserTopics = Object.freeze({
/* tor-launcher observer topics */ const TorTopics = Object.freeze({ - ProcessIsReady: "TorProcessIsReady", BootstrapStatus: "TorBootstrapStatus", BootstrapError: "TorBootstrapError", ProcessExited: "TorProcessExited", @@ -34,7 +37,6 @@ const TorTopics = Object.freeze({
/* Relevant prefs used by tor-launcher */ const TorLauncherPrefs = Object.freeze({ - quickstart: "extensions.torlauncher.quickstart", prompt_at_startup: "extensions.torlauncher.prompt_at_startup", });
@@ -250,38 +252,29 @@ const TorConnect = (() => { // Disabled this.legacyOrSystemTor(); } else { - // register the Tor topics we always care about - for (const topicKey in TorTopics) { - const topic = TorTopics[topicKey]; + let observeTopic = (topic) => { Services.obs.addObserver(this, topic); console.log(`TorConnect: observing topic '${topic}'`); - } + };
- if (TorProtocolService.torProcessStatus == TorProcessStatus.Running) { - if (this.shouldQuickStart) { - // Quickstart - this.beginBootstrap(); - } else { - // Configuring - this.beginConfigure(); - } + // register the Tor topics we always care about + for (const topicKey in TorTopics) { + const topic = TorTopics[topicKey]; + observeTopic(topic); } + observeTopic(TorSettingsTopics.Ready); } - Services.obs.removeObserver(this, topic); break; } - /* Transition out of Initial if Tor daemon wasn't running yet in BrowserTopics.ProfileAfterChange */ - case TorTopics.ProcessIsReady: { - if (this.state === TorConnectState.Initial) - { - if (this.shouldQuickStart) { - // Quickstart - this.beginBootstrap(); - } else { - // Configuring - this.beginConfigure(); - } + /* We need to wait until TorSettings have been loaded and applied before we can Quickstart */ + case TorSettingsTopics.Ready: { + if (this.shouldQuickStart) { + // Quickstart + this.beginBootstrap(); + } else { + // Configuring + this.beginConfigure(); } break; } @@ -342,7 +335,7 @@ const TorConnect = (() => {
get shouldQuickStart() { // quickstart must be enabled - return Services.prefs.getBoolPref(TorLauncherPrefs.quickstart, false) && + return TorSettings.quickstart.enabled && // and the previous bootstrap attempt must have succeeded !Services.prefs.getBoolPref(TorLauncherPrefs.prompt_at_startup, true); }, diff --git a/browser/modules/TorSettings.jsm b/browser/modules/TorSettings.jsm new file mode 100644 index 000000000000..6d2a6c4a07cf --- /dev/null +++ b/browser/modules/TorSettings.jsm @@ -0,0 +1,814 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["TorSettings", "TorSettingsTopics", "TorSettingsData", "TorBridgeSource", "TorBuiltinBridgeTypes", "TorProxyType"]; + +const { Services } = ChromeUtils.import( + "resource://gre/modules/Services.jsm" +); + +const { TorProtocolService, TorProcessStatus } = ChromeUtils.import( + "resource:///modules/TorProtocolService.jsm" +); + +/* Browser observer topics */ +const BrowserTopics = Object.freeze({ + ProfileAfterChange: "profile-after-change", +}); + +/* tor-launcher observer topics */ +const TorTopics = Object.freeze({ + ProcessIsReady: "TorProcessIsReady", +}); + +/* TorSettings observer topics */ +const TorSettingsTopics = Object.freeze({ + Ready: "torsettings:ready", + SettingChanged: "torsettings:setting-changed", +}); + +/* TorSettings observer data (for SettingChanged topic) */ +const TorSettingsData = Object.freeze({ + QuickStartEnabled : "torsettings:quickstart_enabled", +}); + +/* Prefs used to store settings in TorBrowser prefs */ +const TorSettingsPrefs = Object.freeze({ + /* bool: are we pulling tor settings from the preferences */ + enabled: 'torbrowser.settings.enabled', + quickstart : { + /* bool: does tor connect automatically on launch */ + enabled: 'torbrowser.settings.quickstart.enabled', + }, + bridges : { + /* bool: does tor use bridges */ + enabled : 'torbrowser.settings.bridges.enabled', + /* int: -1=invalid|0=builtin|1=bridge_db|2=user_provided */ + source : 'torbrowser.settings.bridges.source', + /* string: obfs4|meek_azure|snowflake|etc */ + builtin_type : 'torbrowser.settings.bridges.builtin_type', + /* preference branch: each child branch should be a bridge string */ + bridge_strings : 'torbrowser.settings.bridges.bridge_strings', + }, + proxy : { + /* bool: does tor use a proxy */ + enabled : 'torbrowser.settings.proxy.enabled', + /* -1=invalid|0=socks4,1=socks5,2=https */ + type: 'torbrowser.settings.proxy.type', + /* string: proxy server address */ + address: 'torbrowser.settings.proxy.address', + /* int: [1,65535], proxy port */ + port: 'torbrowser.settings.proxy.port', + /* string: username */ + username: 'torbrowser.settings.proxy.username', + /* string: password */ + password: 'torbrowser.settings.proxy.password', + }, + firewall : { + /* bool: does tor have a port allow list */ + enabled: 'torbrowser.settings.firewall.enabled', + /* string: comma-delimitted list of port numbers */ + allowed_ports: 'torbrowser.settings.firewall.allowed_ports', + }, +}); + +/* Legacy tor-launcher prefs and pref branches*/ +const TorLauncherPrefs = Object.freeze({ + quickstart: "extensions.torlauncher.quickstart", + default_bridge_type: "extensions.torlauncher.default_bridge_type", + default_bridge: "extensions.torlauncher.default_bridge.", + default_bridge_recommended_type: "extensions.torlauncher.default_bridge_recommended_type", + bridgedb_bridge: "extensions.torlauncher.bridgedb_bridge.", +}); + +/* Config Keys used to configure tor daemon */ +const TorConfigKeys = Object.freeze({ + useBridges: "UseBridges", + bridgeList: "Bridge", + socks4Proxy: "Socks4Proxy", + socks5Proxy: "Socks5Proxy", + socks5ProxyUsername: "Socks5ProxyUsername", + socks5ProxyPassword: "Socks5ProxyPassword", + httpsProxy: "HTTPSProxy", + httpsProxyAuthenticator: "HTTPSProxyAuthenticator", + reachableAddresses: "ReachableAddresses", + clientTransportPlugin: "ClientTransportPlugin", +}); + +const TorBridgeSource = Object.freeze({ + Invalid: -1, + BuiltIn: 0, + BridgeDB: 1, + UserProvided: 2, +}); + +const TorProxyType = Object.freeze({ + Invalid: -1, + Socks4: 0, + Socks5: 1, + HTTPS: 2, +}); + + +const TorBuiltinBridgeTypes = Object.freeze( + (() => { + let bridgeListBranch = Services.prefs.getBranch(TorLauncherPrefs.default_bridge); + let bridgePrefs = bridgeListBranch.getChildList(""); + + // an unordered set for shoving bridge types into + let bridgeTypes = new Set(); + // look for keys ending in ".N" and treat string before that as the bridge type + const pattern = /.[0-9]+$/; + for (const key of bridgePrefs) { + const offset = key.search(pattern); + if (offset != -1) { + const bt = key.substring(0, offset); + bridgeTypes.add(bt); + } + } + + // recommended bridge type goes first in the list + let recommendedBridgeType = Services.prefs.getCharPref(TorLauncherPrefs.default_bridge_recommended_type, null); + + let retval = []; + if (recommendedBridgeType && bridgeTypes.has(recommendedBridgeType)) { + retval.push(recommendedBridgeType); + } + + for (const bridgeType of bridgeTypes.values()) { + if (bridgeType != recommendedBridgeType) { + retval.push(bridgeType); + } + } + return retval; + })() +); + +/* Parsing Methods */ + +// expects a string representation of an integer from 1 to 65535 +let parsePort = function(aPort) { + // ensure port string is a valid positive integer + const validIntRegex = /^[0-9]+$/; + if (!validIntRegex.test(aPort)) { + return 0; + } + + // ensure port value is on valid range + let port = Number.parseInt(aPort); + if (port < 1 || port > 65535) { + return 0; + } + + return port; +}; +// expects a string in the format: "ADDRESS:PORT" +let parseAddrPort = function(aAddrColonPort) { + let tokens = aAddrColonPort.split(":"); + if (tokens.length != 2) { + return ["", 0]; + } + let address = tokens[0]; + let port = parsePort(tokens[1]); + return [address, port]; +}; + +// expects a string in the format: "USERNAME:PASSWORD" +// split on the first colon and any subsequent go into password +let parseUsernamePassword = function(aUsernameColonPassword) { + let colonIndex = aUsernameColonPassword.indexOf(":"); + if (colonIndex < 0) { + return ["", ""]; + } + + let username = aUsernameColonPassword.substring(0, colonIndex); + let password = aUsernameColonPassword.substring(colonIndex + 1); + + return [username, password]; +}; + +// expects a string in the format: ADDRESS:PORT,ADDRESS:PORT,... +// returns array of ports (as ints) +let parseAddrPortList = function(aAddrPortList) { + let addrPorts = aAddrPortList.split(","); + // parse ADDRESS:PORT string and only keep the port (second element in returned array) + let retval = addrPorts.map(addrPort => parseAddrPort(addrPort)[1]); + return retval; +}; + +// expects a '/n' or '/r/n' delimited bridge string, which we split and trim +// each bridge string can also optionally have 'bridge' at the beginning ie: +// bridge $(type) $(address):$(port) $(certificate) +// we strip out the 'bridge' prefix here +let parseBridgeStrings = function(aBridgeStrings) { + + // replace carriage returns ('\r') with new lines ('\n') + aBridgeStrings = aBridgeStrings.replace(/\r/g, "\n"); + // then replace contiguous new lines ('\n') with a single one + aBridgeStrings = aBridgeStrings.replace(/[\n]+/g, "\n"); + + // split on the newline and for each bridge string: trim, remove starting 'bridge' string + // finally discard entries that are empty strings; empty strings could occur if we receive + // a new line containing only whitespace + let splitStrings = aBridgeStrings.split("\n"); + return splitStrings.map(val => val.trim().replace(/^bridge\s+/i, "")) + .filter(bridgeString => bridgeString != ""); +}; + +// expecting a ',' delimited list of ints with possible white space between +// returns an array of ints +let parsePortList = function(aPortListString) { + let splitStrings = aPortListString.split(","); + // parse and remove duplicates + let portSet = new Set(splitStrings.map(val => parsePort(val.trim()))); + // parsePort returns 0 for failed parses, so remove 0 from list + portSet.delete(0); + return Array.from(portSet); +}; + +let getBuiltinBridgeStrings = function(builtinType) { + let bridgeBranch = Services.prefs.getBranch(TorLauncherPrefs.default_bridge); + let bridgeBranchPrefs = bridgeBranch.getChildList(""); + let retval = []; + + // regex matches against strings ending in ".N" where N is a positive integer + let pattern = /.[0-9]+$/; + for (const key of bridgeBranchPrefs) { + // verify the location of the match is the correct offset required for aBridgeType + // to fit, and that the string begins with aBridgeType + if (key.search(pattern) == builtinType.length && + key.startsWith(builtinType)) { + let bridgeStr = bridgeBranch.getCharPref(key); + retval.push(bridgeStr); + } + } + + // shuffle so that Tor Browser users don't all try the built-in bridges in the same order + arrayShuffle(retval); + + return retval; +}; + +/* Array methods */ + +let arrayShuffle = function(array) { + // fisher-yates shuffle + for (let i = array.length - 1; i > 0; --i) { + // number n such that 0.0 <= n < 1.0 + const n = Math.random(); + // integer j such that 0 <= j <= i + const j = Math.floor(n * (i + 1)); + + // swap values at indices i and j + const tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } +} + +let arrayCopy = function(array) { + return [].concat(array); +} + +/* TorSettings module */ + +const TorSettings = (() => { + let self = { + _settings: null, + + // tor daemon related settings + defaultSettings: function() { + let settings = { + quickstart: { + enabled: false + }, + bridges : { + enabled: false, + source: TorBridgeSource.Invalid, + builtin_type: null, + bridge_strings: [], + }, + proxy: { + enabled: false, + type: TorProxyType.Invalid, + address: null, + port: 0, + username: null, + password: null, + }, + firewall: { + enabled: false, + allowed_ports: [], + }, + }; + return settings; + }, + + /* try and load our settings, and register observers */ + init: function() { + if (TorProtocolService.ownsTorDaemon) { + // if the settings branch exists, load settings from prefs + if (Services.prefs.getBoolPref(TorSettingsPrefs.enabled, false)) { + this.loadFromPrefs(); + Services.obs.notifyObservers(null, TorSettingsTopics.Ready); + } + Services.obs.addObserver(this, BrowserTopics.ProfileAfterChange); + Services.obs.addObserver(this, TorTopics.ProcessIsReady); + } + }, + + /* wait for relevant life-cycle events to load and/or apply saved settings */ + observe: async function(subject, topic, data) { + console.log(`TorSettings: observed ${topic}`); + + // once the process is ready, we need to apply our settings + let handleProcessReady = async () => { + Services.obs.removeObserver(this, TorTopics.ProcessIsReady); + if (this._settings == null) { + // load settings from tor if our load in init() failed and save them to prefs + await this.loadLegacy(); + this.saveToPrefs(); + } else { + // push down settings to tor + await this.applySettings(); + } + Services.obs.notifyObservers(null, TorSettingsTopics.Ready); + }; + + switch (topic) { + case BrowserTopics.ProfileAfterChange: { + if (TorProtocolService.torProcessStatus == TorProcessStatus.Running) { + await handleProcessReady(); + } + } + break; + case TorTopics.ProcessIsReady: { + await handleProcessReady(); + } + break; + } + }, + + // load our settings from old locations (misc prefs and from tor daemon) + // TODO: remove this after some time has elapsed to ensure users have migrated to pref settings + loadLegacy: async function() { + console.log("TorSettings: loadLegacy()"); + + let settings = this.defaultSettings(); + + /* Quickstart */ + settings.quickstart.enabled = Services.prefs.getBoolPref(TorLauncherPrefs.quickstart, false); + + /* Bridges + + So the way tor-launcher determines the origin of the configured bridges is a bit + weird and depends on inferring our scenario based on some firefox prefs and the + relationship between the saved list of bridges in about:config vs the list saved in torrc + + first off, if "extensions.torlauncher.default_bridge_type" is set to one of our + builtin default types (obfs4, meek-azure, snowflake, etc) then we provide the + bridges in "extensions.torlauncher.default_bridge.*" (filtered by our default_bridge_type) + + next, we compare the list of bridges saved in torrc to the bridges stored in the + "extensions.torlauncher.bridgedb_bridge."" branch. If they match *exactly* then we assume + the bridges were retrieved from BridgeDB and use those. If the torrc list is empty then we know + we have no bridge settings + + finally, if none of the previous conditions are not met, it is assumed the bridges stored in + torrc are user-provided + */ + + let builtinType = Services.prefs.getCharPref(TorLauncherPrefs.default_bridge_type, null); + + // check if source is built-in + if (builtinType) { + let builtinBridgeStrings = getBuiltinBridgeStrings(builtinType); + if (builtinBridgeStrings.length > 0) { + settings.bridges.enabled = true; + settings.bridges.source = TorBridgeSource.BuiltIn; + settings.bridges.builtin_type = builtinType; + settings.bridges.bridge_strings = builtinBridgeStrings; + } + } else { + // get our currently configured bridges from tor + let torrcBridgeStrings = await (async () => { + let bridgeList = await TorProtocolService.readStringArraySetting(TorConfigKeys.bridgeList); + let retval = []; + for (const line of bridgeList) { + let trimmedLine = line.trim(); + if (trimmedLine) { + retval.push(trimmedLine); + } + } + return retval; + })(); + + // torrc has bridges configured + if (torrcBridgeStrings.length > 0) { + // compare tor's bridges to our saved bridgedb bridges + let bridgedbBBridgeStrings = (() => { + let bridgeBranch = Services.prefs.getBranch(TorLauncherPrefs.bridgedb_bridge); + let bridgeBranchPrefs = bridgeBranch.getChildList(""); + // the child prefs do not come in any particular order so sort the keys + // so the values can be compared to what we get out off torrc + bridgeBranchPrefs.sort(); + + // just assume all of the prefs under the parent point to valid bridge string + let retval = bridgeBranchPrefs.map(key => + bridgeBranch.getCharPref(key).trim() + ); + return retval; + })(); + + let arraysEqual = (left, right) => { + if (left.length != right.length) { + return false; + } + const length = left.length; + for (let i = 0; i < length; ++i) { + if (left[i] != right[i]) { + return false; + } + } + return true; + }; + + if (arraysEqual(torrcBridgeStrings, bridgedbBBridgeStrings)) { + settings.bridges.enabled = true; + settings.bridges.source = TorBridgeSource.BridgeDB; + settings.bridges.builtin_type = null; + settings.bridges.bridge_strings = torrcBridgeStrings; + } else { + settings.bridges.enabled = true; + settings.bridges.source = TorBridgeSource.UserProvided; + settings.bridges.builtin_type = null; + settings.bridges.bridge_strings = torrcBridgeStrings; + } + } else { + // tor has no bridge strings saved, so bridges not in use + settings.bridges.enabled = false; + settings.bridges.source = TorBridgeSource.Invalid; + settings.bridges.builtin_type = null; + settings.bridges.bridge_strings = []; + } + } + + /* Proxy */ + + let proxyString = null; + if (proxyString = await TorProtocolService.readStringSetting(TorConfigKeys.socks4Proxy)) { + let [address, port] = parseAddrPort(proxyString); + + settings.proxy.enabled = true; + settings.proxy.type = TorProxyType.Socks4; + settings.proxy.address = address; + settings.proxy.port = port; + settings.proxy.username = null; + settings.proxy.password = null; + } else if (proxyString = await TorProtocolService.readStringSetting(TorConfigKeys.socks5Proxy)) { + let [address, port] = parseAddrPort(proxyString); + let username = await TorProtocolService.readStringSetting(TorConfigKeys.socks5ProxyUsername); + let password = await TorProtocolService.readStringSetting(TorConfigKeys.socks5ProxyPassword); + + settings.proxy.enabled = true; + settings.proxy.type = TorProxyType.Socks5; + settings.proxy.address = address; + settings.proxy.port = port; + settings.proxy.username = username; + settings.proxy.password = password; + } else if (proxyString = await TorProtocolService.readStringSetting(TorConfigKeys.httpsProxy)) { + let [address, port] = parseAddrPort(proxyString); + let authenticator = await TorProtocolService.readStringSetting(TorConfigKeys.httpsProxyAuthenticator); + let [username, password] = parseUsernamePassword(authenticator); + + settings.proxy.enabled = true; + settings.proxy.type = TorProxyType.HTTPS; + settings.proxy.address = address; + settings.proxy.port = port; + settings.proxy.username = username; + settings.proxy.password = password; + } else { + settings.proxy.enabled = false; + settings.proxy.type = TorProxyType.Invalid; + settings.proxy.address = null; + settings.proxy.port = 0; + settings.proxy.username = null; + settings.proxy.password = null; + } + + /* Firewall */ + let firewallString = await TorProtocolService.readStringSetting(TorConfigKeys.reachableAddresses); + if (firewallString) { + let allowedPorts = parseAddrPortList(firewallString); + settings.firewall.enabled = allowedPorts.length > 0; + settings.firewall.allowed_ports = allowedPorts; + } else { + settings.firewall.enabled = false; + settings.firewall.allowed_ports = []; + } + + this._settings = settings; + + return this; + }, + + // load our settings from prefs + loadFromPrefs: function() { + console.log("TorSettings: loadFromPrefs()"); + + let settings = this.defaultSettings(); + + /* Quickstart */ + settings.quickstart.enabled = Services.prefs.getBoolPref(TorSettingsPrefs.quickstart.enabled); + /* Bridges */ + settings.bridges.enabled = Services.prefs.getBoolPref(TorSettingsPrefs.bridges.enabled); + if (settings.bridges.enabled) { + settings.bridges.source = Services.prefs.getIntPref(TorSettingsPrefs.bridges.source); + // builtin bridge (obfs4, meek, snowlfake, etc) + if (settings.bridges.source == TorBridgeSource.BuiltIn) { + let builtinType = Services.prefs.getStringPref(TorSettingsPrefs.bridges.builtin_type); + settings.bridges.builtin_type = builtinType; + // always dynamically load builtin bridges rather than loading the cached versions + // if the user upgrades and the builtin bridges have changed, we want to ensure the user + // can still bootstrap using the provided bridges + let bridgeStrings = getBuiltinBridgeStrings(builtinType); + if (bridgeStrings.length > 0) { + settings.bridges.bridge_strings = bridgeStrings; + } else { + // in this case the user is using a builtin bridge that is no longer supported, + // reset to settings to default values + settings.bridges.enabled = false; + settings.bridges.source = TorBridgeSource.Invalid; + settings.bridges.builtin_type = null; + } + } else { + settings.bridges.bridge_strings = []; + let bridgeBranchPrefs = Services.prefs.getBranch(TorSettingsPrefs.bridges.bridge_strings).getChildList(""); + bridgeBranchPrefs.forEach(pref => { + let bridgeString = Services.prefs.getStringPref(`${TorSettingsPrefs.bridges.bridge_strings}${pref}`); + settings.bridges.bridge_strings.push(bridgeString); + }); + } + } else { + settings.bridges.source = TorBridgeSource.Invalid; + settings.bridges.builtin_type = null; + settings.bridges.bridge_strings = []; + } + /* Proxy */ + settings.proxy.enabled = Services.prefs.getBoolPref(TorSettingsPrefs.proxy.enabled); + if (settings.proxy.enabled) { + settings.proxy.type = Services.prefs.getIntPref(TorSettingsPrefs.proxy.type); + settings.proxy.address = Services.prefs.getStringPref(TorSettingsPrefs.proxy.address); + settings.proxy.port = Services.prefs.getIntPref(TorSettingsPrefs.proxy.port); + settings.proxy.username = Services.prefs.getStringPref(TorSettingsPrefs.proxy.username); + settings.proxy.password = Services.prefs.getStringPref(TorSettingsPrefs.proxy.password); + } else { + settings.proxy.type = TorProxyType.Invalid; + settings.proxy.address = null; + settings.proxy.port = 0; + settings.proxy.username = null; + settings.proxy.password = null; + } + + /* Firewall */ + settings.firewall.enabled = Services.prefs.getBoolPref(TorSettingsPrefs.firewall.enabled); + if(settings.firewall.enabled) { + let portList = Services.prefs.getStringPref(TorSettingsPrefs.firewall.allowed_ports); + settings.firewall.allowed_ports = parsePortList(portList); + } else { + settings.firewall.allowed_ports = 0; + } + + this._settings = settings; + + return this; + }, + + // save our settings to prefs + saveToPrefs: function() { + console.log("TorSettings: saveToPrefs()"); + + let settings = this._settings; + + /* Quickstart */ + Services.prefs.setBoolPref(TorSettingsPrefs.quickstart.enabled, settings.quickstart.enabled); + /* Bridges */ + Services.prefs.setBoolPref(TorSettingsPrefs.bridges.enabled, settings.bridges.enabled); + if (settings.bridges.enabled) { + Services.prefs.setIntPref(TorSettingsPrefs.bridges.source, settings.bridges.source); + if (settings.bridges.source === TorBridgeSource.BuiltIn) { + Services.prefs.setStringPref(TorSettingsPrefs.bridges.builtin_type, settings.bridges.builtin_type); + } else { + Services.prefs.clearUserPref(TorSettingsPrefs.bridges.builtin_type); + } + // erase existing bridge strings + let bridgeBranchPrefs = Services.prefs.getBranch(TorSettingsPrefs.bridges.bridge_strings).getChildList(""); + bridgeBranchPrefs.forEach(pref => { + Services.prefs.clearUserPref(`${TorSettingsPrefs.bridges.bridge_strings}${pref}`); + }); + // write new ones + settings.bridges.bridge_strings.forEach((string, index) => { + Services.prefs.setStringPref(`${TorSettingsPrefs.bridges.bridge_strings}.${index}`, string); + }); + } else { + Services.prefs.clearUserPref(TorSettingsPrefs.bridges.source); + Services.prefs.clearUserPref(TorSettingsPrefs.bridges.builtin_type); + let bridgeBranchPrefs = Services.prefs.getBranch(TorSettingsPrefs.bridges.bridge_strings).getChildList(""); + bridgeBranchPrefs.forEach(pref => { + Services.prefs.clearUserPref(`${TorSettingsPrefs.bridges.bridge_strings}${pref}`); + }); + } + /* Proxy */ + Services.prefs.setBoolPref(TorSettingsPrefs.proxy.enabled, settings.proxy.enabled); + if (settings.proxy.enabled) { + Services.prefs.setIntPref(TorSettingsPrefs.proxy.type, settings.proxy.type); + Services.prefs.setStringPref(TorSettingsPrefs.proxy.address, settings.proxy.address); + Services.prefs.setIntPref(TorSettingsPrefs.proxy.port, settings.proxy.port); + Services.prefs.setStringPref(TorSettingsPrefs.proxy.username, settings.proxy.username); + Services.prefs.setStringPref(TorSettingsPrefs.proxy.password, settings.proxy.password); + } else { + Services.prefs.clearUserPref(TorSettingsPrefs.proxy.type); + Services.prefs.clearUserPref(TorSettingsPrefs.proxy.address); + Services.prefs.clearUserPref(TorSettingsPrefs.proxy.port); + Services.prefs.clearUserPref(TorSettingsPrefs.proxy.username); + Services.prefs.clearUserPref(TorSettingsPrefs.proxy.password); + } + /* Firewall */ + Services.prefs.setBoolPref(TorSettingsPrefs.firewall.enabled, settings.firewall.enabled); + if (settings.firewall.enabled) { + Services.prefs.setStringPref(TorSettingsPrefs.firewall.allowed_ports, settings.firewall.allowed_ports.join(",")); + } else { + Services.prefs.clearUserPref(TorSettingsPrefs.firewall.allowed_ports); + } + + // all tor settings now stored in prefs :) + Services.prefs.setBoolPref(TorSettingsPrefs.enabled, true); + + return this; + }, + + // push our settings down to the tor daemon + applySettings: async function() { + console.log("TorSettings: applySettings()"); + let settings = this._settings; + let settingsMap = new Map(); + + /* Bridges */ + settingsMap.set(TorConfigKeys.useBridges, settings.bridges.enabled); + if (settings.bridges.enabled) { + settingsMap.set(TorConfigKeys.bridgeList, settings.bridges.bridge_strings); + } else { + // shuffle bridge list + settingsMap.set(TorConfigKeys.bridgeList, null); + } + + /* Proxy */ + settingsMap.set(TorConfigKeys.socks4Proxy, null); + settingsMap.set(TorConfigKeys.socks5Proxy, null); + settingsMap.set(TorConfigKeys.socks5ProxyUsername, null); + settingsMap.set(TorConfigKeys.socks5ProxyPassword, null); + settingsMap.set(TorConfigKeys.httpsProxy, null); + settingsMap.set(TorConfigKeys.httpsProxyAuthenticator, null); + if (settings.proxy.enabled) { + let address = settings.proxy.address; + let port = settings.proxy.port; + let username = settings.proxy.username; + let password = settings.proxy.password; + + switch (settings.proxy.type) { + case TorProxyType.Socks4: + settingsMap.set(TorConfigKeys.socks4Proxy, `${address}:${port}`); + break; + case TorProxyType.Socks5: + settingsMap.set(TorConfigKeys.socks5Proxy, `${address}:${port}`); + settingsMap.set(TorConfigKeys.socks5ProxyUsername, username); + settingsMap.set(TorConfigKeys.socks5ProxyPassword, password); + break; + case TorProxyType.HTTPS: + settingsMap.set(TorConfigKeys.httpsProxy, `${address}:${port}`); + settingsMap.set(TorConfigKeys.httpsProxyAuthenticator, `${username}:${password}`); + break; + } + } + + /* Firewall */ + if (settings.firewall.enabled) { + let reachableAddresses = settings.firewall.allowed_ports.map(port => `*:${port}`).join(","); + settingsMap.set(TorConfigKeys.reachableAddresses, reachableAddresses); + } else { + settingsMap.set(TorConfigKeys.reachableAddresses, null); + } + + /* Push to Tor */ + await TorProtocolService.writeSettings(settingsMap); + + return this; + }, + + /* Getters and Setters */ + + + // Quickstart + get quickstart() { + return { + // Avoid a race-condition on first-start where this property + // may be accessed before `self._settings` is initialized. + // This work-around can be removed when #40598 is resolved. + get enabled() { return (self._settings ? self._settings.quickstart.enabled : false); }, + set enabled(val) { + if (val != self._settings.quickstart.enabled) + { + self._settings.quickstart.enabled = val; + Services.obs.notifyObservers({value: val}, TorSettingsTopics.SettingChanged, TorSettingsData.QuickStartEnabled); + } + }, + }; + }, + + // Bridges + get bridges() { + return { + get enabled() { return self._settings.bridges.enabled; }, + set enabled(val) { + self._settings.bridges.enabled = val; + // reset bridge settings + self._settings.bridges.source = TorBridgeSource.Invalid; + self._settings.bridges.builtin_type = null; + self._settings.bridges.bridge_strings = []; + }, + get source() { return self._settings.bridges.source; }, + set source(val) { self._settings.bridges.source = val; }, + get builtin_type() { return self._settings.bridges.builtin_type; }, + set builtin_type(val) { + let bridgeStrings = getBuiltinBridgeStrings(val); + if (bridgeStrings.length > 0) { + self._settings.bridges.builtin_type = val; + self._settings.bridges.bridge_strings = bridgeStrings; + } + }, + get bridge_strings() { return arrayCopy(self._settings.bridges.bridge_strings); }, + set bridge_strings(val) { + self._settings.bridges.bridge_strings = parseBridgeStrings(val); + }, + }; + }, + + // Proxy + get proxy() { + return { + get enabled() { return self._settings.proxy.enabled; }, + set enabled(val) { + self._settings.proxy.enabled = val; + // reset proxy settings + self._settings.proxy.type = TorProxyType.Invalid; + self._settings.proxy.address = null; + self._settings.proxy.port = 0; + self._settings.proxy.username = null; + self._settings.proxy.password = null; + }, + get type() { return self._settings.proxy.type; }, + set type(val) { self._settings.proxy.type = val; }, + get address() { return self._settings.proxy.address; }, + set address(val) { self._settings.proxy.address = val; }, + get port() { return arrayCopy(self._settings.proxy.port); }, + set port(val) { self._settings.proxy.port = parsePort(val); }, + get username() { return self._settings.proxy.username; }, + set username(val) { self._settings.proxy.username = val; }, + get password() { return self._settings.proxy.password; }, + set password(val) { self._settings.proxy.password = val; }, + get uri() { + switch (this.type) { + case TorProxyType.Socks4: + return `socks4a://${this.address}:${this.port}`; + case TorProxyType.Socks5: + if (this.username) { + return `socks5://${this.username}:${this.password}@${this.address}:${this.port}`; + } + return `socks5://${this.address}:${this.port}`; + case TorProxyType.HTTPS: + if (this._proxyUsername) { + return `http://$%7Bthis.username%7D:$%7Bthis.password%7D@$%7Bthis.address%7D:$%7Bthi...; + } + return `http://$%7Bthis.address%7D:$%7Bthis.port%7D%60; + } + return null; + }, + }; + }, + + // Firewall + get firewall() { + return { + get enabled() { return self._settings.firewall.enabled; }, + set enabled(val) { + self._settings.firewall.enabled = val; + // reset firewall settings + self._settings.firewall.allowed_ports = []; + }, + get allowed_ports() { return self._settings.firewall.allowed_ports; }, + set allowed_ports(val) { self._settings.firewall.allowed_ports = parsePortList(val); }, + }; + }, + }; + self.init(); + return self; +})(); diff --git a/browser/modules/moz.build b/browser/modules/moz.build index 1ea57aba1a93..8c001d4ac6ed 100644 --- a/browser/modules/moz.build +++ b/browser/modules/moz.build @@ -156,6 +156,7 @@ EXTRA_JS_MODULES += [ 'TorConnect.jsm', 'TorProcessService.jsm', "TorProtocolService.jsm", + "TorSettings.jsm", "TorStrings.jsm", "TransientPrefs.jsm", "webrtcUI.jsm", diff --git a/toolkit/components/processsingleton/MainProcessSingleton.jsm b/toolkit/components/processsingleton/MainProcessSingleton.jsm index 62afa98e1ffc..ba8cd0f3f97d 100644 --- a/toolkit/components/processsingleton/MainProcessSingleton.jsm +++ b/toolkit/components/processsingleton/MainProcessSingleton.jsm @@ -24,6 +24,11 @@ MainProcessSingleton.prototype = { null );
+ ChromeUtils.import( + "resource:///modules/TorSettings.jsm", + null + ); + ChromeUtils.import( "resource:///modules/TorConnect.jsm", null diff --git a/toolkit/modules/AsyncPrefs.jsm b/toolkit/modules/AsyncPrefs.jsm index f7d867e47dc0..2834c484c919 100644 --- a/toolkit/modules/AsyncPrefs.jsm +++ b/toolkit/modules/AsyncPrefs.jsm @@ -20,7 +20,6 @@ const kAllowedPrefs = new Set([
"browser.contentblocking.report.hide_vpn_banner", "browser.contentblocking.report.show_mobile_app", - "extensions.torlauncher.quickstart",
"narrate.rate", "narrate.voice",