This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-91.8.0esr-11.5-1 in repository tor-browser.
commit 3c85de34837cc10c2d182465e40ef6c02cc5d18f Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Thu Feb 10 10:03:39 2022 +0100
fixup! Bug 40597: Implement TorSettings module --- browser/modules/Moat.jsm | 122 ++++++++++----- browser/modules/TorConnect.jsm | 278 +++++++++++++++++++++++---------- browser/modules/TorProtocolService.jsm | 100 +++++++----- browser/modules/TorSettings.jsm | 138 +++++++--------- 4 files changed, 399 insertions(+), 239 deletions(-)
diff --git a/browser/modules/Moat.jsm b/browser/modules/Moat.jsm index d02075e4412f2..2995a9148f0a3 100644 --- a/browser/modules/Moat.jsm +++ b/browser/modules/Moat.jsm @@ -16,7 +16,7 @@ const { TorProtocolService } = ChromeUtils.import( "resource:///modules/TorProtocolService.jsm" );
-const { TorSettings, TorBridgeSource, TorProxyType } = ChromeUtils.import( +const { TorSettings, TorBridgeSource } = ChromeUtils.import( "resource:///modules/TorSettings.jsm" );
@@ -384,6 +384,42 @@ class MoatResponseListener { } }
+class InternetTestResponseListener { + constructor() { + this._promise = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + }); + } + + // callers wait on this for final response + status() { + return this._promise; + } + + onStartRequest(request) {} + + // resolve or reject our Promise + onStopRequest(request, status) { + let statuses = {}; + try { + statuses = { + components: status, + successful: Components.isSuccessCode(status), + http: request.responseStatus, + }; + } catch (err) { + this._reject(err); + } + this._resolve(statuses); + } + + onDataAvailable(request, stream, offset, length) { + // We do not care of the actual data, as long as we have a successful + // connection + } +} + // constructs the json objects and sends the request over moat class MoatRPC { constructor() { @@ -391,6 +427,10 @@ class MoatRPC { this._inited = false; }
+ get inited() { + return this._inited; + } + async init() { if (this._inited) { throw new Error("MoatRPC: Already initialized"); @@ -408,7 +448,7 @@ class MoatRPC { this._inited = false; }
- async _makeRequest(procedure, args) { + _makeHttpHandler(uriString) { if (!this._inited) { throw new Error("MoatRPC: Not initialized"); } @@ -437,16 +477,12 @@ class MoatRPC { undefined );
- const procedureURIString = `${Services.prefs.getStringPref( - TorLauncherPrefs.moat_service - )}/${procedure}`; - - const procedureURI = Services.io.newURI(procedureURIString); + const uri = Services.io.newURI(uriString); // There does not seem to be a way to directly create an nsILoadInfo from // JavaScript, so we create a throw away non-proxied channel to get one. const secFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL; const loadInfo = Services.io.newChannelFromURI( - procedureURI, + uri, undefined, Services.scriptSecurityManager.getSystemPrincipal(), undefined, @@ -458,7 +494,7 @@ class MoatRPC { .getProtocolHandler("http") .QueryInterface(Ci.nsIHttpProtocolHandler); const ch = httpHandler - .newProxiedChannel(procedureURI, proxyInfo, 0, undefined, loadInfo) + .newProxiedChannel(uri, proxyInfo, 0, undefined, loadInfo) .QueryInterface(Ci.nsIHttpChannel);
// remove all headers except for 'Host" @@ -472,6 +508,15 @@ class MoatRPC { }); headers.forEach(key => ch.setRequestHeader(key, "", false));
+ return ch; + } + + async _makeRequest(procedure, args) { + const procedureURIString = `${Services.prefs.getStringPref( + TorLauncherPrefs.moat_service + )}/${procedure}`; + const ch = this._makeHttpHandler(procedureURIString); + // Arrange for the POST data to be sent. const argsJson = JSON.stringify(args);
@@ -495,6 +540,18 @@ class MoatRPC { return JSON.parse(responseJSON); }
+ async testInternetConnection() { + const uri = `${Services.prefs.getStringPref( + TorLauncherPrefs.moat_service + )}/circumvention/countries`; + const ch = this._makeHttpHandler(uri); + ch.requestMethod = "HEAD"; + + const listener = new InternetTestResponseListener(); + await ch.asyncOpen(listener, ch); + return listener.status(); + } + // // Moat APIs // @@ -508,7 +565,6 @@ class MoatRPC { // - image: a base64 encoded jpeg with the captcha to complete // - challenge: a nonce/cookie string associated with this request async fetch(transports) { - if ( // ensure this is an array Array.isArray(transports) && @@ -588,10 +644,10 @@ class MoatRPC { // In the event of error, just return null _fixupSettings(settings) { try { - let retval = TorSettings.defaultSettings() + let retval = TorSettings.defaultSettings(); if ("bridges" in settings) { retval.bridges.enabled = true; - switch(settings.bridges.source) { + switch (settings.bridges.source) { case "builtin": retval.bridges.source = TorBridgeSource.BuiltIn; retval.bridges.builtin_type = settings.bridges.type; @@ -606,12 +662,17 @@ class MoatRPC { retval.bridges.source = TorBridgeSource.BridgeDB; if (settings.bridges.bridge_strings) { retval.bridges.bridge_strings = settings.bridges.bridge_strings; + retval.bridges.disabled_strings = []; } else { - throw new Error("MoatRPC::_fixupSettings(): Received no bridge-strings for BridgeDB bridge source"); + throw new Error( + "MoatRPC::_fixupSettings(): Received no bridge-strings for BridgeDB bridge source" + ); } break; default: - throw new Error(`MoatRPC::_fixupSettings(): Unexpected bridge source '${settings.bridges.source}'`); + throw new Error( + `MoatRPC::_fixupSettings(): Unexpected bridge source '${settings.bridges.source}'` + ); } } if ("proxy" in settings) { @@ -621,7 +682,7 @@ class MoatRPC { // TODO: populate firewall settings } return retval; - } catch(ex) { + } catch (ex) { console.log(ex.message); return null; } @@ -661,14 +722,16 @@ class MoatRPC { async circumvention_settings(transports, country) { const args = { transports: transports ? transports : [], - country: country, + country, }; const response = await this._makeRequest("circumvention/settings", args); if ("errors" in response) { const code = response.errors[0].code; const detail = response.errors[0].detail; if (code == 406) { - console.log("MoatRPC::circumvention_settings(): Cannot automatically determine user's country-code"); + console.log( + "MoatRPC::circumvention_settings(): Cannot automatically determine user's country-code" + ); // cannot determine user's country return null; } @@ -681,26 +744,13 @@ class MoatRPC { return []; }
- // Request a copy of the censorship circumvention map (as if cirumvention_settings were - // queried for all country codes) + // Request a list of country codes with available censorship circumvention settings // - // returns a map whose key is an ISO 3166-1 alpha-2 country code and whose - // values are arrays of settings objects - async circumvention_map() { - const args = { }; - const response = await this._makeRequest("circumvention/map", args); - if ("errors" in response) { - const code = response.errors[0].code; - const detail = response.errors[0].detail; - throw new Error(`MoatRPC: ${detail} (${code})`); - } - - let map = new Map(); - for (const [country, config] of Object.entries(response)) { - map.set(country, this._fixupSettingsList(config.settings)); - } - - return map; + // returns an array of ISO 3166-1 alpha-2 country codes which we can query settings + // for + async circumvention_countries() { + const args = {}; + return this._makeRequest("circumvention/countries", args); }
// Request a copy of the builtin bridges, takes the following parameters: diff --git a/browser/modules/TorConnect.jsm b/browser/modules/TorConnect.jsm index c7ab480e2be06..13c1f54d2ee97 100644 --- a/browser/modules/TorConnect.jsm +++ b/browser/modules/TorConnect.jsm @@ -1,11 +1,15 @@ "use strict";
-var EXPORTED_SYMBOLS = ["TorConnect", "TorConnectTopics", "TorConnectState"]; +var EXPORTED_SYMBOLS = ["TorConnect", "TorConnectTopics", "TorConnectState", "TorCensorshipLevel"];
const { Services } = ChromeUtils.import( "resource://gre/modules/Services.jsm" );
+const { setTimeout } = ChromeUtils.import( + "resource://gre/modules/Timer.jsm" +); + const { BrowserWindowTracker } = ChromeUtils.import( "resource:///modules/BrowserWindowTracker.jsm" ); @@ -22,6 +26,8 @@ const { TorSettings, TorSettingsTopics, TorBridgeSource, TorBuiltinBridgeTypes, "resource:///modules/TorSettings.jsm" );
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + const { MoatRPC } = ChromeUtils.import("resource:///modules/Moat.jsm");
/* Browser observer topis */ @@ -31,7 +37,11 @@ const BrowserTopics = Object.freeze({
/* Relevant prefs used by tor-launcher */ const TorLauncherPrefs = Object.freeze({ - prompt_at_startup: "extensions.torlauncher.prompt_at_startup", + prompt_at_startup: "extensions.torlauncher.prompt_at_startup", +}); + +const TorConnectPrefs = Object.freeze({ + censorship_level: "torbrowser.debug.censorship_level", });
const TorConnectState = Object.freeze({ @@ -51,6 +61,17 @@ const TorConnectState = Object.freeze({ Disabled: "Disabled", });
+const TorCensorshipLevel = Object.freeze({ + /* No censorship detected */ + None: 0, + /* Moderate censorship detected, autobootstrap may evade it */ + Moderate: 1, + /* Severe censorship detected, but connection may still succeed */ + Severe: 2, + /* Extreme censorship detected, connection will always end in an error */ + Extreme: 3, +}); + /* TorConnect State Transitions
@@ -197,12 +218,31 @@ class StateCallback { } }
+// async method to sleep for a given amount of time +const debug_sleep = async (ms) => { + return new Promise((resolve, reject) => { + setTimeout(resolve, ms); + }); +} + const TorConnect = (() => { let retval = {
_state: TorConnectState.Initial, _bootstrapProgress: 0, _bootstrapStatus: null, + _detectedCensorshiplevel: TorCensorshipLevel.None, + // list of country codes Moat has settings for + _countryCodes: [], + _countryNames: Object.freeze((() => { + const codes = Services.intl.getAvailableLocaleDisplayNames("region"); + const names = Services.intl.getRegionDisplayNames(undefined, codes); + let codesNames = {}; + for (let i = 0; i < codes.length; i++) { + codesNames[codes[i]] = names[i]; + } + return codesNames; + })()), _errorMessage: null, _errorDetails: null, _logHasWarningOrError: false, @@ -254,6 +294,22 @@ const TorConnect = (() => { [TorConnectState.Bootstrapping, new StateCallback(TorConnectState.Bootstrapping, async function() { // wait until bootstrap completes or we get an error await new Promise(async (resolve, reject) => { + + // we reset the bootstrap failure count so users can manually try settings without the + // censorship circumvention state machine getting in the way + TorConnect._detectedCensorshipLevel = TorCensorshipLevel.None; + + // debug hook to simulate censorship preventing bootstrapping + if (Services.prefs.getIntPref(TorConnectPrefs.censorship_level, 0) > 0) { + this.on_transition = (nextState) => { + resolve(); + }; + await debug_sleep(1500); + TorConnect._changeState(TorConnectState.Error, "Bootstrap failed (for debugging purposes)", "Error: Censorship simulation", true); + TorProtocolService._torBootstrapDebugSetError(); + return; + } + const tbr = new TorBootstrapRequest(); this.on_transition = async (nextState) => { if (nextState === TorConnectState.Configuring) { @@ -270,7 +326,7 @@ const TorConnect = (() => { TorConnect._changeState(TorConnectState.Bootstrapped); }; tbr.onbootstraperror = (message, details) => { - TorConnect._changeState(TorConnectState.Error, message, details); + TorConnect._changeState(TorConnectState.Error, message, details, true); };
tbr.bootstrap(); @@ -283,91 +339,123 @@ const TorConnect = (() => { resolve(); };
+ // debug hook to simulate censorship preventing bootstrapping + { + const censorshipLevel = Services.prefs.getIntPref(TorConnectPrefs.censorship_level, 0); + if (censorshipLevel > 1) { + this.on_transition = (nextState) => { + resolve(); + }; + // always fail even after manually selecting location specific settings + if (censorshipLevel == 3) { + await debug_sleep(2500); + TorConnect._changeState(TorConnectState.Error, "Error: Extreme Censorship simulation", "", true); + return; + // only fail after auto selecting, manually selecting succeeds + } else if (censorshipLevel == 2 && !countryCode) { + await debug_sleep(2500); + TorConnect._changeState(TorConnectState.Error, "Error: Severe Censorship simulation", "", true); + return; + } + TorProtocolService._torBootstrapDebugSetError(); + } + } + + const throw_error = (message, details) => { + let err = new Error(message); + err.details = details; + throw err; + }; + // lookup user's potential censorship circumvention settings from Moat service try { this.mrpc = new MoatRPC(); await this.mrpc.init();
+ if (this.transitioning) return; + this.settings = await this.mrpc.circumvention_settings([...TorBuiltinBridgeTypes, "vanilla"], countryCode);
if (this.transitioning) return;
if (this.settings === null) { // unable to determine country - TorConnect._changeState(TorConnectState.Error, "Unable to determine user country", "DETAILS_STRING"); - return; + throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.cannotDetermineCountry); } else if (this.settings.length === 0) { // no settings available for country - TorConnect._changeState(TorConnectState.Error, "No settings available for your location", "DETAILS_STRING"); - return; + throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.noSettingsForCountry); } - } catch (err) { - TorConnect._changeState(TorConnectState.Error, err?.message, err?.details); - return; - } finally { - // important to uninit MoatRPC object or else the pt process will live as long as tor-browser - this.mrpc?.uninit(); - }
- // apply each of our settings and try to bootstrap with each - try { - this.originalSettings = TorSettings.getSettings(); - - let index = 0; - for (let currentSetting of this.settings) { - // let us early out if user cancels - if (this.transitioning) return; - - console.log(`TorConnect: Attempting Bootstrap with configuration ${++index}/${this.settings.length}`); + // apply each of our settings and try to bootstrap with each + try { + this.originalSettings = TorSettings.getSettings(); + + for (const [index, currentSetting] of this.settings.entries()) { + + // we want to break here so we can fall through and restore original settings + if (this.transitioning) break; + + console.log(`TorConnect: Attempting Bootstrap with configuration ${index+1}/${this.settings.length}`); + + TorSettings.setSettings(currentSetting); + await TorSettings.applySettings(); + + // build out our bootstrap request + const tbr = new TorBootstrapRequest(); + tbr.onbootstrapstatus = (progress, status) => { + TorConnect._updateBootstrapStatus(progress, status); + }; + tbr.onbootstraperror = (message, details) => { + console.log(`TorConnect: Auto-Bootstrap error => ${message}; ${details}`); + }; + + // update transition callback for user cancel + this.on_transition = async (nextState) => { + if (nextState === TorConnectState.Configuring) { + await tbr.cancel(); + } + resolve(); + }; + + // begin bootstrap + if (await tbr.bootstrap()) { + // persist the current settings to preferences + TorSettings.saveToPrefs(); + TorConnect._changeState(TorConnectState.Bootstrapped); + return; + } + }
- TorSettings.setSettings(currentSetting); + // bootstrapped failed for all potential settings, so reset daemon to use original + TorSettings.setSettings(this.originalSettings); await TorSettings.applySettings(); + TorSettings.saveToPrefs();
- // build out our bootstrap request - const tbr = new TorBootstrapRequest(); - tbr.onbootstrapstatus = (progress, status) => { - TorConnect._updateBootstrapStatus(progress, status); - }; - tbr.onbootstraperror = (message, details) => { - console.log(`TorConnect: Auto-Bootstrap error => ${message}; ${details}`); - }; - - // update transition callback for user cancel - this.on_transition = async (nextState) => { - if (nextState === TorConnectState.Configuring) { - await tbr.cancel(); - } - resolve(); - }; - - // begin bootstrap - if (await tbr.bootstrap()) { - // persist the current settings to preferences - TorSettings.saveToPrefs(); - TorConnect._changeState(TorConnectState.Bootstrapped); - return; + // only explicitly change state here if something else has not transitioned us + if (!this.transitioning) { + throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.autoBootstrappingAllFailed); } + return; + } catch (err) { + // restore original settings in case of error + try { + TorSettings.setSettings(this.originalSettings); + await TorSettings.applySettings(); + } catch(err) { + console.log(`TorConnect: Failed to restore original settings => ${err}`); + } + // throw to outer catch to transition us + throw err; } - // bootstrapped failed for all potential settings, so reset daemon to use original - TorSettings.setSettings(this.originalSettings); - await TorSettings.applySettings(); - TorSettings.saveToPrefs(); - - // only explicitly change state here if something else has not transitioned us - if (!this.transitioning) { - TorConnect._changeState(TorConnectState.Error, "AutoBootstrapping failed", "DETAILS_STRING"); - } - return; - } catch (err) { - // restore original settings in case of error - try { - TorSettings.setSettings(this.originalSettings); - await TorSettings.applySettings(); - } catch(err) { - console.log(`TorConnect: Failed to restore original settings => ${err}`); + } catch(err) { + if (this.mrpc?.inited) { + // lookup countries which have settings available + TorConnect._countryCodes = await this.mrpc.circumvention_countries(); } - TorConnect._changeState(TorConnectState.Error, err?.message, err?.details); - return; + TorConnect._changeState(TorConnectState.Error, err?.message, err?.details, true); + } finally { + // important to uninit MoatRPC object or else the pt process will live as long as tor-browser + this.mrpc?.uninit(); } }); })], @@ -380,7 +468,7 @@ const TorConnect = (() => { }); })], /* Error */ - [TorConnectState.Error, new StateCallback(TorConnectState.Error, async function(errorMessage, errorDetails) { + [TorConnectState.Error, new StateCallback(TorConnectState.Error, async function(errorMessage, errorDetails, bootstrappingFailure) { await new Promise((resolve, reject) => { this.on_transition = async(nextState) => { resolve(); @@ -389,7 +477,11 @@ const TorConnect = (() => { TorConnect._errorMessage = errorMessage; TorConnect._errorDetails = errorDetails;
- Services.obs.notifyObservers({message: errorMessage, details: errorDetails}, TorConnectTopics.BootstrapError); + if (bootstrappingFailure && TorConnect._detectedCensorshipLevel < TorCensorshipLevel.Extreme) { + TorConnect._detectedCensorshipLevel += 1; + } + + Services.obs.notifyObservers({message: errorMessage, details: errorDetails, censorshipLevel: TorConnect.detectedCensorshipLevel}, TorConnectTopics.BootstrapError);
TorConnect._changeState(TorConnectState.Configuring); }); @@ -523,6 +615,10 @@ const TorConnect = (() => { return this._bootstrapStatus; },
+ get detectedCensorshipLevel() { + return this._detectedCensorshipLevel; + }, + get errorMessage() { return this._errorMessage; }, @@ -535,6 +631,14 @@ const TorConnect = (() => { return this._logHasWarningOrError; },
+ get countryCodes() { + return this._countryCodes; + }, + + get countryNames() { + return this._countryNames; + }, + /* These functions allow external consumers to tell TorConnect to transition states */ @@ -564,7 +668,7 @@ const TorConnect = (() => { */ openTorPreferences: function() { const win = BrowserWindowTracker.getTopWindow(); - win.switchToTabHavingURI("about:preferences#tor", true); + win.switchToTabHavingURI("about:preferences#connection", true); },
openTorConnect: function() { @@ -572,19 +676,27 @@ const TorConnect = (() => { win.switchToTabHavingURI("about:torconnect", true, {ignoreQueryString: true}); },
- copyTorLogs: function() { - // Copy tor log messages to the system clipboard. - const chSvc = Cc["@mozilla.org/widget/clipboardhelper;1"].getService( - Ci.nsIClipboardHelper - ); - const countObj = { value: 0 }; - chSvc.copyString(TorProtocolService.getLog(countObj)); - const count = countObj.value; - return TorLauncherUtil.getFormattedLocalizedString( - "copiedNLogMessagesShort", - [count], - 1 - ); + viewTorLogs: function() { + const win = BrowserWindowTracker.getTopWindow(); + win.switchToTabHavingURI("about:preferences#connection-viewlogs", true); + }, + + getCountryCodes: async function() { + // Difference with the getter: this is to be called by TorConnectParent, and downloads + // the country codes if they are not already in cache. + if (this._countryCodes.length) { + return this._countryCodes; + } + const mrpc = new MoatRPC(); + try { + await mrpc.init(); + this._countryCodes = await mrpc.circumvention_countries(); + } catch(err) { + console.log("An error occurred while fetching country codes", err); + } finally { + mrpc.uninit(); + } + return this._countryCodes; },
getRedirectURL: function(url) { diff --git a/browser/modules/TorProtocolService.jsm b/browser/modules/TorProtocolService.jsm index ac6d643691f6b..aa27e13e11711 100644 --- a/browser/modules/TorProtocolService.jsm +++ b/browser/modules/TorProtocolService.jsm @@ -2,16 +2,21 @@
"use strict";
-var EXPORTED_SYMBOLS = ["TorProtocolService", "TorProcessStatus", "TorTopics", "TorBootstrapRequest"]; +var EXPORTED_SYMBOLS = [ + "TorProtocolService", + "TorProcessStatus", + "TorTopics", + "TorBootstrapRequest", +];
-const { Services } = ChromeUtils.import( - "resource://gre/modules/Services.jsm" -); +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const { setTimeout, clearTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm"); +const { setTimeout, clearTimeout } = ChromeUtils.import( + "resource://gre/modules/Timer.jsm" +);
const { TorLauncherUtil } = ChromeUtils.import( - "resource://torlauncher/modules/tl-util.jsm" + "resource://torlauncher/modules/tl-util.jsm" );
// see tl-process.js @@ -24,24 +29,18 @@ const TorProcessStatus = Object.freeze({
/* tor-launcher observer topics */ const TorTopics = Object.freeze({ - BootstrapStatus: "TorBootstrapStatus", - BootstrapError: "TorBootstrapError", - ProcessExited: "TorProcessExited", - LogHasWarnOrErr: "TorLogHasWarnOrErr", + BootstrapStatus: "TorBootstrapStatus", + BootstrapError: "TorBootstrapError", + ProcessExited: "TorProcessExited", + LogHasWarnOrErr: "TorLogHasWarnOrErr", });
/* Browser observer topis */ const BrowserTopics = Object.freeze({ - ProfileAfterChange: "profile-after-change", + ProfileAfterChange: "profile-after-change", });
var TorProtocolService = { - _TorLauncherUtil: function() { - let { TorLauncherUtil } = ChromeUtils.import( - "resource://torlauncher/modules/tl-util.jsm" - ); - return TorLauncherUtil; - }(), _TorLauncherProtocolService: null, _TorProcessService: null,
@@ -58,13 +57,12 @@ var TorProtocolService = { if (topic === BrowserTopics.ProfileAfterChange) { // we have to delay init'ing this or else the crypto service inits too early without a profile // which breaks the password manager - this._TorLauncherProtocolService = Cc["@torproject.org/torlauncher-protocol-service;1"].getService( - Ci.nsISupports - ).wrappedJSObject; - this._TorProcessService = Cc["@torproject.org/torlauncher-process-service;1"].getService( - Ci.nsISupports - ).wrappedJSObject, - + this._TorLauncherProtocolService = Cc[ + "@torproject.org/torlauncher-protocol-service;1" + ].getService(Ci.nsISupports).wrappedJSObject; + this._TorProcessService = Cc[ + "@torproject.org/torlauncher-process-service;1" + ].getService(Ci.nsISupports).wrappedJSObject; Services.obs.removeObserver(this, topic); } }, @@ -171,7 +169,12 @@ var TorProtocolService = { }
let errorObject = {}; - if (! await this._TorLauncherProtocolService.TorSetConfWithReply(settingsObject, errorObject)) { + if ( + !(await this._TorLauncherProtocolService.TorSetConfWithReply( + settingsObject, + errorObject + )) + ) { throw new Error(errorObject.details); }
@@ -195,9 +198,7 @@ var TorProtocolService = { let lineArray = await this._readSetting(aSetting); if (lineArray.length != 1) { throw new Error( - `Expected an array with length 1 but received array of length ${ - lineArray.length - }` + `Expected an array with length 1 but received array of length ${lineArray.length}` ); }
@@ -216,9 +217,7 @@ var TorProtocolService = { let lineArray = await this._readSetting(aSetting); if (lineArray.length != 1) { throw new Error( - `Expected an array with length 1 but received array of length ${ - lineArray.length - }` + `Expected an array with length 1 but received array of length ${lineArray.length}` ); } return lineArray[0]; @@ -260,7 +259,7 @@ var TorProtocolService = {
// true if we launched and control tor, false if using system tor get ownsTorDaemon() { - return this._TorLauncherUtil.shouldStartAndOwnTor; + return TorLauncherUtil.shouldStartAndOwnTor; },
// Assumes `ownsTorDaemon` is true @@ -269,7 +268,9 @@ var TorProtocolService = { "DisableNetwork", true ); - if (TorProtocolService._TorLauncherProtocolService.TorCommandSucceeded(reply)) { + if ( + TorProtocolService._TorLauncherProtocolService.TorCommandSucceeded(reply) + ) { return reply.retVal; } return true; @@ -279,13 +280,18 @@ var TorProtocolService = { let settings = {}; settings.DisableNetwork = false; let errorObject = {}; - if (! await this._TorLauncherProtocolService.TorSetConfWithReply(settings, errorObject)) { + if ( + !(await this._TorLauncherProtocolService.TorSetConfWithReply( + settings, + errorObject + )) + ) { throw new Error(errorObject.details); } },
async sendCommand(cmd) { - return await this._TorLauncherProtocolService.TorSendCommand(cmd); + return this._TorLauncherProtocolService.TorSendCommand(cmd); },
retrieveBootstrapStatus() { @@ -294,7 +300,7 @@ var TorProtocolService = {
_GetSaveSettingsErrorMessage(aDetails) { try { - return this._TorLauncherUtil.getSaveSettingsErrorMessage(aDetails); + return TorLauncherUtil.getSaveSettingsErrorMessage(aDetails); } catch (e) { console.log("GetSaveSettingsErrorMessage error", e); return "Unexpected Error"; @@ -305,7 +311,10 @@ var TorProtocolService = { let result = false; const error = {}; try { - result = await this._TorLauncherProtocolService.TorSetConfWithReply(settings, error); + result = await this._TorLauncherProtocolService.TorSetConfWithReply( + settings, + error + ); } catch (e) { console.log("TorSetConfWithReply error", e); error.details = this._GetSaveSettingsErrorMessage(e.message); @@ -325,6 +334,10 @@ var TorProtocolService = { return this._TorProcessService.TorBootstrapErrorOccurred; },
+ _torBootstrapDebugSetError() { + this._TorProcessService._TorSetBootstrapErrorForDebug(); + }, + // Resolves to null if ok, or an error otherwise async connect() { const kTorConfKeyDisableNetwork = "DisableNetwork"; @@ -396,7 +409,7 @@ class TorBootstrapRequest {
async observe(subject, topic, data) { const obj = subject?.wrappedJSObject; - switch(topic) { + switch (topic) { case TorTopics.BootstrapStatus: { const progress = obj.PROGRESS; const status = TorLauncherUtil.getLocalizedBootstrapStatus(obj, "TAG"); @@ -432,7 +445,9 @@ class TorBootstrapRequest {
// resolves 'true' if bootstrap succeeds, false otherwise async bootstrap() { - if (this._bootstrapPromise) return this._bootstrapPromise; + if (this._bootstrapPromise) { + return this._bootstrapPromise; + }
this._bootstrapPromise = new Promise(async (resolve, reject) => { this._bootstrapPromiseResolve = resolve; @@ -446,7 +461,10 @@ class TorBootstrapRequest { this._timeoutID = setTimeout(async () => { await TorProtocolService.torStopBootstrap(); if (this.onbootstraperror) { - this.onbootstraperror("Tor Bootstrap process timed out", `Bootstrap attempt abandoned after waiting ${this.timeout} ms`); + this.onbootstraperror( + "Tor Bootstrap process timed out", + `Bootstrap attempt abandoned after waiting ${this.timeout} ms` + ); } this._bootstrapPromiseResolve(false); }, this.timeout); @@ -481,4 +499,4 @@ class TorBootstrapRequest {
this._bootstrapPromiseResolve(false); } -}; +} diff --git a/browser/modules/TorSettings.jsm b/browser/modules/TorSettings.jsm index 1b5b564e1e62c..87bc129602930 100644 --- a/browser/modules/TorSettings.jsm +++ b/browser/modules/TorSettings.jsm @@ -195,7 +195,7 @@ let parseAddrPortList = function(aAddrPortList) { return retval; };
-// expects a '/n' or '/r/n' delimited bridge string, which we split and trim +// 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 @@ -226,6 +226,10 @@ let parsePortList = function(aPortListString) { };
let getBuiltinBridgeStrings = function(builtinType) { + if (!builtinType) { + return []; + } + let bridgeBranch = Services.prefs.getBranch(TorLauncherPrefs.default_bridge); let bridgeBranchPrefs = bridgeBranch.getChildList(""); let retval = []; @@ -248,7 +252,7 @@ let getBuiltinBridgeStrings = function(builtinType) { return retval; };
-/* Array methods */ +/* Helper methods */
let arrayShuffle = function(array) { // fisher-yates shuffle @@ -356,37 +360,24 @@ const TorSettings = (() => { 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); - }); + settings.bridges.source = Services.prefs.getIntPref(TorSettingsPrefs.bridges.source, TorBridgeSource.Invalid); + if (settings.bridges.source == TorBridgeSource.BuiltIn) { + let builtinType = Services.prefs.getStringPref(TorSettingsPrefs.bridges.builtin_type); + settings.bridges.builtin_type = builtinType; + settings.bridges.bridge_strings = getBuiltinBridgeStrings(builtinType); + if (settings.bridges.bridge_strings.length == 0) { + // in this case the user is using a builtin bridge that is no longer supported, + // reset to settings to default values + settings.bridges.source = TorBridgeSource.Invalid; + settings.bridges.builtin_type = null; } } else { - settings.bridges.source = TorBridgeSource.Invalid; - settings.bridges.builtin_type = null; settings.bridges.bridge_strings = []; + let bridgeBranchPrefs = Services.prefs.getBranch(TorSettingsPrefs.bridges.bridge_strings).getChildList(""); + bridgeBranchPrefs.forEach(pref => { + const bridgeString = Services.prefs.getStringPref(`${TorSettingsPrefs.bridges.bridge_strings}${pref}`); + settings.bridges.bridge_strings.push(bridgeString); + }); } /* Proxy */ settings.proxy.enabled = Services.prefs.getBoolPref(TorSettingsPrefs.proxy.enabled); @@ -428,29 +419,18 @@ const TorSettings = (() => { 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 + Services.prefs.setIntPref(TorSettingsPrefs.bridges.source, settings.bridges.source); + Services.prefs.setStringPref(TorSettingsPrefs.bridges.builtin_type, settings.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 + if (settings.bridges.source !== TorBridgeSource.BuiltIn) { 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); @@ -488,11 +468,11 @@ const TorSettings = (() => { let settingsMap = new Map();
/* Bridges */ - settingsMap.set(TorConfigKeys.useBridges, settings.bridges.enabled); - if (settings.bridges.enabled) { + const haveBridges = settings.bridges.enabled && settings.bridges.bridge_strings.length > 0; + settingsMap.set(TorConfigKeys.useBridges, haveBridges); + if (haveBridges) { settingsMap.set(TorConfigKeys.bridgeList, settings.bridges.bridge_strings); } else { - // shuffle bridge list settingsMap.set(TorConfigKeys.bridgeList, null); }
@@ -545,29 +525,28 @@ const TorSettings = (() => { let backup = this.getSettings();
try { - if (settings.bridges.enabled) { - this._settings.bridges.enabled = true; - this._settings.bridges.source = settings.bridges.source; - switch(settings.bridges.source) { - case TorBridgeSource.BridgeDB: - case TorBridgeSource.UserProvided: - this._settings.bridges.bridge_strings = settings.bridges.bridge_strings - break; - case TorBridgeSource.BuiltIn: { - this._settings.bridges.builtin_type = settings.bridges.builtin_type; - let bridgeStrings = getBuiltinBridgeStrings(settings.bridges.builtin_type); - if (bridgeStrings.length > 0) { - this._settings.bridges.bridge_strings = bridgeStrings; - } else { - throw new Error(`No available builtin bridges of type ${settings.bridges.builtin_type}`); - } - break; + this._settings.bridges.enabled = !!settings.bridges.enabled; + this._settings.bridges.source = settings.bridges.source; + switch(settings.bridges.source) { + case TorBridgeSource.BridgeDB: + case TorBridgeSource.UserProvided: + this._settings.bridges.bridge_strings = settings.bridges.bridge_strings; + break; + case TorBridgeSource.BuiltIn: { + this._settings.bridges.builtin_type = settings.bridges.builtin_type; + settings.bridges.bridge_strings = getBuiltinBridgeStrings(settings.bridges.builtin_type); + if (settings.bridges.bridge_strings.length == 0 && settings.bridges.enabled) { + throw new Error(`No available builtin bridges of type ${settings.bridges.builtin_type}`); } - default: - throw new Error(`Bridge source '${settings.source}' is not a valid source`); + break; } - } else { - this.bridges.enabled = false; + case TorBridgeSource.Invalid: + break; + default: + if (settings.bridges.enabled) { + throw new Error(`Bridge source '${settings.source}' is not a valid source`); + } + break; }
// TODO: proxy and firewall @@ -609,19 +588,20 @@ const TorSettings = (() => { 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); + const bridgeStrings = getBuiltinBridgeStrings(val); if (bridgeStrings.length > 0) { self._settings.bridges.builtin_type = val; self._settings.bridges.bridge_strings = bridgeStrings; + } else { + self._settings.bridges.builtin_type = ""; + if (self._settings.bridges.source === TorBridgeSource.BuiltIn) { + self._settings.bridges.source = TorBridgeSource.Invalid; + } } }, get bridge_strings() { return arrayCopy(self._settings.bridges.bridge_strings); },