Skip to content
Snippets Groups Projects
Verified Commit 138e8d6b authored by Pier Angelo Vendrame's avatar Pier Angelo Vendrame :jack_o_lantern:
Browse files

fixup! Bug 40597: Implement TorSettings module

Bug 42336: Rework the relationship between TorSettings and TorProvider.
parent 9cecc61f
Branches
Tags
1 merge request!927Bug 42336: Rework the relationship between TorSettings and TorProvider.
......@@ -31,15 +31,16 @@ export class TorStartupService {
}
}
async #init() {
#init() {
Services.obs.addObserver(this, BrowserTopics.QuitApplicationGranted);
lazy.TorSettings.init();
// Theoretically, build() is expected to await the initialization of the
// provider, and anything needing the Tor Provider should be able to just
// await on TorProviderBuilder.build().
lazy.TorProviderBuilder.init();
await lazy.TorSettings.init();
lazy.TorConnect.init();
lazy.TorDomainIsolator.init();
......
......
......@@ -2,13 +2,13 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { TorBridgeSource } from "resource://gre/modules/TorSettings.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
DomainFrontRequestBuilder:
"resource://gre/modules/DomainFrontedRequests.sys.mjs",
TorBridgeSource: "resource://gre/modules/TorSettings.sys.mjs",
TorSettings: "resource://gre/modules/TorSettings.sys.mjs",
});
const TorLauncherPrefs = Object.freeze({
......@@ -211,13 +211,23 @@ export class MoatRPC {
};
switch (settings.bridges.source) {
case "builtin":
retval.bridges.source = TorBridgeSource.BuiltIn;
retval.bridges.source = lazy.TorBridgeSource.BuiltIn;
retval.bridges.builtin_type = settings.bridges.type;
// TorSettings will ignore strings for built-in bridges, and use the
// ones it already knows, instead.
// ones it already knows, instead. However, when we try these settings
// in the connect assist, we skip TorSettings. Therefore, we set the
// lines also here (the ones we already known, not the ones we receive
// from Moat). This needs TorSettings to be initialized, which by now
// should have already happened (this method is used only by TorConnect,
// that needs TorSettings to be initialized).
// In any case, getBuiltinBridges will throw if the data is not ready,
// yet.
retval.bridges.bridge_strings = lazy.TorSettings.getBuiltinBridges(
settings.bridges.type
);
break;
case "bridgedb":
retval.bridges.source = TorBridgeSource.BridgeDB;
retval.bridges.source = lazy.TorBridgeSource.BridgeDB;
if (settings.bridges.bridge_strings) {
retval.bridges.bridge_strings = settings.bridges.bridge_strings;
} else {
......
......
......@@ -10,6 +10,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
MoatRPC: "resource://gre/modules/Moat.sys.mjs",
TorBootstrapRequest: "resource://gre/modules/TorBootstrapRequest.sys.mjs",
TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
});
// TODO: Should we move this to the about:torconnect actor?
......@@ -20,10 +21,7 @@ ChromeUtils.defineModuleGetter(
);
import { TorLauncherUtil } from "resource://gre/modules/TorLauncherUtil.sys.mjs";
import {
TorSettings,
TorSettingsTopics,
} from "resource://gre/modules/TorSettings.sys.mjs";
import { TorSettings } from "resource://gre/modules/TorSettings.sys.mjs";
import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs";
......@@ -651,10 +649,22 @@ export const TorConnect = (() => {
}
}
// apply each of our settings and try to bootstrap with each
const restoreOriginalSettings = async () => {
try {
this.originalSettings = TorSettings.getSettings();
await TorSettings.applySettings();
} catch (e) {
// We cannot do much if the original settings were bad or
// if the connection closed, so just report it in the
// console.
console.warn(
"TorConnect: Failed to restore original settings.",
e
);
}
};
// apply each of our settings and try to bootstrap with each
try {
for (const [
index,
currentSetting,
......@@ -670,8 +680,26 @@ export const TorConnect = (() => {
}/${this.settings.length}`
);
TorSettings.setSettings(currentSetting);
await TorSettings.applySettings();
// Send the new settings directly to the provider. We will
// save them only if the bootstrap succeeds.
// FIXME: We should somehow signal TorSettings users that we
// have set custom settings, and they should not apply
// theirs until we are done with trying ours.
// Otherwise, the new settings provided by the user while we
// were bootstrapping could be the ones that cause the
// bootstrap to succeed, but we overwrite them (unless we
// backup the original settings, and then save our new
// settings only if they have not changed).
// Another idea (maybe easier to implement) is to disable
// the settings UI while *any* bootstrap is going on.
// This is also documented in tor-browser#41921.
const provider = await lazy.TorProviderBuilder.build();
// We need to merge with old settings, in case the user is
// using a proxy or is behind a firewall.
await provider.writeSettings({
...TorSettings.getSettings(),
...currentSetting,
});
// build out our bootstrap request
const tbr = new lazy.TorBootstrapRequest();
......@@ -688,6 +716,7 @@ export const TorConnect = (() => {
this.on_transition = async nextState => {
if (nextState === TorConnectState.Configuring) {
await tbr.cancel();
await restoreOriginalSettings();
}
resolve();
};
......@@ -695,23 +724,20 @@ export const TorConnect = (() => {
// begin bootstrap
if (await tbr.bootstrap()) {
// persist the current settings to preferences
TorSettings.setSettings(currentSetting);
TorSettings.saveToPrefs();
await TorSettings.applySettings();
TorConnect._changeState(TorConnectState.Bootstrapped);
return;
}
}
// bootstrapped failed for all potential settings, so reset daemon to use original
TorSettings.setSettings(this.originalSettings);
// The original settings should be good, so we save them to
// preferences before trying to apply them, as it might fail
// if the actual problem is with the connection to the control
// port.
// FIXME: We should handle this case in a better way.
TorSettings.saveToPrefs();
await TorSettings.applySettings();
// Bootstrap failed for all potential settings, so restore the
// original settings the provider.
await restoreOriginalSettings();
// only explicitly change state here if something else has not transitioned us
// Only explicitly change state here if something else has not
// transitioned us.
if (!this.transitioning) {
throw_error(
TorStrings.torConnect.autoBootstrappingFailed,
......@@ -720,18 +746,8 @@ export const TorConnect = (() => {
}
return;
} catch (err) {
// restore original settings in case of error
try {
TorSettings.setSettings(this.originalSettings);
// As above
TorSettings.saveToPrefs();
await TorSettings.applySettings();
} catch (errRestore) {
console.log(
`TorConnect: Failed to restore original settings => ${errRestore}`
);
}
// throw to outer catch to transition us
await restoreOriginalSettings();
// throw to outer catch to transition us.
throw err;
}
} catch (err) {
......@@ -878,6 +894,14 @@ export const TorConnect = (() => {
console.log(`TorConnect: Observing topic '${addTopic}'`);
};
// Wait for TorSettings, as we will need it.
// We will wait for a TorProvider only after TorSettings is ready,
// because the TorProviderBuilder initialization might not have finished
// at this point, and TorSettings initialization is a prerequisite for
// having a provider.
// So, we prefer initializing TorConnect as soon as possible, so that
// the UI will be able to detect it is in the Initializing state and act
// consequently.
TorSettings.initializedPromise.then(() => this._settingsInitialized());
// register the Tor topics we always care about
......@@ -919,19 +943,25 @@ export const TorConnect = (() => {
}
},
_settingsInitialized() {
async _settingsInitialized() {
// TODO: Handle failures here, instead of the prompt to restart the
// daemon when it exits (tor-browser#21053, tor-browser#41921).
await lazy.TorProviderBuilder.build();
// tor-browser#41907: This is only a workaround to avoid users being
// bounced back to the initial panel without any explanation.
// Longer term we should disable the clickable elements, or find a UX
// to prevent this from happening (e.g., allow buttons to be clicked,
// but show an intermediate starting state, or a message that tor is
// starting while the butons are disabled, etc...).
// See also tor-browser#41921.
if (this.state !== TorConnectState.Initial) {
console.warn(
"TorConnect: Seen the torsettings:ready after the state has already changed, ignoring the notification."
"TorConnect: The TorProvider was built after the state had already changed."
);
return;
}
console.log("TorConnect: The TorProvider is ready, changing state.");
if (this.shouldQuickStart) {
// Quickstart
this._changeState(TorConnectState.Bootstrapping);
......
......
......@@ -6,10 +6,9 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
TorProviderTopics: "resource://gre/modules/TorProviderBuilder.sys.mjs",
Lox: "resource://gre/modules/Lox.sys.mjs",
TorParsers: "resource://gre/modules/TorParsers.sys.mjs",
TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "logger", () => {
......@@ -71,20 +70,6 @@ const TorSettingsPrefs = Object.freeze({
},
});
/* 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",
});
export const TorBridgeSource = Object.freeze({
Invalid: -1,
BuiltIn: 0,
......@@ -322,7 +307,7 @@ class TorSettingsImpl {
if (!val) {
return;
}
const bridgeStrings = this.#getBuiltinBridges(val);
const bridgeStrings = this.getBuiltinBridges(val);
if (bridgeStrings.length) {
this.bridges.bridge_strings = bridgeStrings;
return;
......@@ -659,14 +644,17 @@ class TorSettingsImpl {
* @param {string} pt The pluggable transport to return the lines for
* @returns {string[]} The bridge lines in random order
*/
#getBuiltinBridges(pt) {
getBuiltinBridges(pt) {
if (!this.#allowUninitialized) {
this.#checkIfInitialized();
}
// Shuffle so that Tor Browser users do not all try the built-in bridges in
// the same order.
return arrayShuffle(this.#builtinBridges[pt] ?? []);
}
/**
* Load or init our settings, and register observers.
* Load or init our settings.
*/
async init() {
if (this.#initialized) {
......@@ -677,6 +665,7 @@ class TorSettingsImpl {
await this.#initInternal();
this.#initialized = true;
this.#initComplete();
Services.obs.notifyObservers(null, TorSettingsTopics.Ready);
} catch (e) {
this.#initFailed(e);
throw e;
......@@ -698,19 +687,18 @@ class TorSettingsImpl {
lazy.logger.error("Could not load the built-in PT config.", e);
}
// Initialize this before loading from prefs because we need Lox initialized before
// any calls to Lox.getBridges()
// Initialize this before loading from prefs because we need Lox initialized
// before any calls to Lox.getBridges().
try {
await lazy.Lox.init();
} catch (e) {
lazy.logger.error("Could not initialize Lox.", e.type);
}
// TODO: We could use a shared promise, and wait for it to be fullfilled
// instead of Service.obs.
if (lazy.TorLauncherUtil.shouldStartAndOwnTor) {
// if the settings branch exists, load settings from prefs
if (Services.prefs.getBoolPref(TorSettingsPrefs.enabled, false)) {
if (
lazy.TorLauncherUtil.shouldStartAndOwnTor &&
Services.prefs.getBoolPref(TorSettingsPrefs.enabled, false)
) {
// Do not want notifications for initially loaded prefs.
this.freezeNotifications();
try {
......@@ -722,21 +710,12 @@ class TorSettingsImpl {
this.thawNotifications();
}
}
try {
const provider = await lazy.TorProviderBuilder.build();
if (provider.isRunning) {
this.#handleProcessReady();
// No need to add an observer to call this again.
return;
}
} catch {}
Services.obs.addObserver(this, lazy.TorProviderTopics.ProcessIsReady);
}
lazy.logger.info("Ready");
}
/**
* Unload or uninit our settings, and unregister observers.
* Unload or uninit our settings.
*/
async uninit() {
await lazy.Lox.uninit();
......@@ -764,34 +743,6 @@ class TorSettingsImpl {
return this.#initialized;
}
/**
* Wait for relevant life-cycle events to apply saved settings.
*/
async observe(subject, topic, data) {
lazy.logger.debug(`Observed ${topic}`);
switch (topic) {
case lazy.TorProviderTopics.ProcessIsReady:
Services.obs.removeObserver(
this,
lazy.TorProviderTopics.ProcessIsReady
);
await this.#handleProcessReady();
break;
}
}
/**
* Apply the settings once the tor provider is ready and notify any observer
* that the settings can be used.
*/
async #handleProcessReady() {
// push down settings to tor
await this.#applySettings(true);
lazy.logger.info("Ready");
Services.obs.notifyObservers(null, TorSettingsTopics.Ready);
}
/**
* Load our settings from prefs.
*/
......@@ -972,85 +923,14 @@ class TorSettingsImpl {
/**
* Push our settings down to the tor provider.
*
* Even though this introduces a circular depdency, it makes the API nicer for
* frontend consumers.
*/
async applySettings() {
this.#checkIfInitialized();
return this.#applySettings(false);
}
/**
* Internal implementation of applySettings that does not check if we are
* initialized.
*/
async #applySettings(allowUninitialized) {
lazy.logger.debug("#applySettings()");
this.#cleanupSettings();
const settingsMap = new Map();
// #applySettings can be called only when #allowUninitialized is false
this.#allowUninitialized = allowUninitialized;
try {
/* Bridges */
const haveBridges =
this.bridges.enabled && !!this.bridges.bridge_strings.length;
settingsMap.set(TorConfigKeys.useBridges, haveBridges);
if (haveBridges) {
settingsMap.set(TorConfigKeys.bridgeList, this.bridges.bridge_strings);
} else {
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 (this.proxy.enabled) {
const address = this.proxy.address;
const port = this.proxy.port;
const username = this.proxy.username;
const password = this.proxy.password;
switch (this.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 (this.firewall.enabled) {
const reachableAddresses = this.firewall.allowed_ports
.map(port => `*:${port}`)
.join(",");
settingsMap.set(TorConfigKeys.reachableAddresses, reachableAddresses);
} else {
settingsMap.set(TorConfigKeys.reachableAddresses, null);
}
} finally {
this.#allowUninitialized = false;
}
/* Push to Tor */
const provider = await lazy.TorProviderBuilder.build();
await provider.writeSettings(settingsMap);
await provider.writeSettings(this.getSettings());
}
/**
......
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment