Verified Commit 72fa5715 authored by henry's avatar henry Committed by Pier Angelo Vendrame
Browse files

fixup! TB 40597: Implement TorSettings module

TB 41921: Have Moat bridges pass through TorSettings rather than using
TorProvider directly.

TorSettings should be the only caller to TorProvider.writeSettings, so
we can establish more control over which bridges are in use at any given
moment in one place.

Also add some sanitation to the Moat response.
parent bbe02c60
Loading
Loading
Loading
Loading
+9 −17
Original line number Diff line number Diff line
@@ -13,7 +13,6 @@ 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({
@@ -80,7 +79,7 @@ class InternetTestResponseListener {
 *
 * @property {number} source - The `TorBridgeSource` type.
 * @property {string} [builtin_type] - The built-in bridge type.
 * @property {string[]} bridge_strings - The bridge lines.
 * @property {string[]} [bridge_strings] - The bridge lines.
 */

/**
@@ -246,24 +245,17 @@ export class MoatRPC {
    switch (settings.bridges.source) {
      case "builtin":
        bridges.source = lazy.TorBridgeSource.BuiltIn;
        bridges.builtin_type = settings.bridges.type;
        // TorSettings will ignore strings for built-in bridges, and use the
        // 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 know, 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.
        bridges.bridge_strings = lazy.TorSettings.getBuiltinBridges(
          settings.bridges.type
        );
        bridges.builtin_type = String(settings.bridges.type);
        // Ignore the bridge_strings argument since we will use our built-in
        // bridge strings instead.
        break;
      case "bridgedb":
        bridges.source = lazy.TorBridgeSource.BridgeDB;
        if (settings.bridges.bridge_strings) {
          bridges.bridge_strings = settings.bridges.bridge_strings;
        if (settings.bridges.bridge_strings?.length) {
          bridges.bridge_strings = Array.from(
            settings.bridges.bridge_strings,
            item => String(item)
          );
        } else {
          throw new Error(
            "Received no bridge-strings for BridgeDB bridge source"
+26 −21
Original line number Diff line number Diff line
@@ -215,6 +215,14 @@ class BootstrapAttempt {
    return promise;
  }

  /**
   * Method to call just after the Bootstrapped stage is set in response to this
   * bootstrap attempt.
   */
  postBootstrapped() {
    // Nothing to do.
  }

  /**
   * Run the attempt.
   *
@@ -401,12 +409,6 @@ class AutoBootstrapAttempt {
   * @type {?MoatBridges[]}
   */
  #bridgesList = null;
  /**
   * The last settings that have been applied to the TorProvider, if any.
   *
   * @type {?object}
   */
  #changedSetting = null;
  /**
   * The detected region code returned by Moat, if any.
   *
@@ -439,13 +441,8 @@ class AutoBootstrapAttempt {
        // Run cleanup before we resolve the promise to ensure two instances
        // of AutoBootstrapAttempt are not trying to change the settings at
        // the same time.
        if (this.#changedSetting) {
          if (arg.result === "complete") {
            // Persist the current settings to preferences.
            lazy.TorSettings.setSettings(this.#changedSetting);
            lazy.TorSettings.saveToPrefs();
          } // else, applySettings will restore the current settings.
          await lazy.TorSettings.applySettings();
        if (arg.result !== "complete") {
          await lazy.TorSettings.clearTemporaryBridges();
        }
      } catch (error) {
        lazy.logger.error("Unexpected error in auto-bootstrap cleanup", error);
@@ -467,6 +464,15 @@ class AutoBootstrapAttempt {
    return promise;
  }

  /**
   * Method to call just after the Bootstrapped stage is set in response to this
   * bootstrap attempt.
   */
  postBootstrapped() {
    // Persist the current settings to preferences.
    lazy.TorSettings.saveTemporaryBridges();
  }

  /**
   * Run the attempt.
   *
@@ -617,14 +623,7 @@ class AutoBootstrapAttempt {
    // 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();
    this.#changedSetting = { bridges: { enabled: true, ...bridges } };
    // We need to merge with old settings, in case the user is using a proxy
    // or is behind a firewall.
    await provider.writeSettings({
      ...lazy.TorSettings.getSettings(),
      ...this.#changedSetting,
    });
    await lazy.TorSettings.applyTemporaryBridges(bridges);

    if (this.#cancelled || this.#resolved) {
      return;
@@ -1460,6 +1459,12 @@ export const TorConnect = {
      }
      this._setStage(TorConnectStage.Bootstrapped);
      Services.obs.notifyObservers(null, TorConnectTopics.BootstrapComplete);

      // Now call the postBootstrapped method. We do this after changing the
      // stage to ensure that AutoBootstrapAttempt.postBootstrapped call to
      // saveTemporaryBridges does not trigger SettingsChanged too early and
      // cancel itself.
      bootstrapAttempt.postBootstrapped();
      return;
    }

+98 −3
Original line number Diff line number Diff line
@@ -198,6 +198,14 @@ class TorSettingsImpl {
      allowed_ports: [],
    },
  };

  /**
   * Temporary bridge settings to apply instead of #settings.bridges.
   *
   * @type {?Object}
   */
  #temporaryBridgeSettings = null;

  /**
   * Accumulated errors from trying to set settings.
   *
@@ -713,6 +721,8 @@ class TorSettingsImpl {
      try {
        this.#allowUninitialized = true;
        this.#loadFromPrefs();
        // We do not pass on the loaded settings to the TorProvider yet. Instead
        // TorProvider will ask for these once it has initialised.
      } finally {
        this.#allowUninitialized = false;
        this.#notificationQueue.clear();
@@ -971,7 +981,7 @@ class TorSettingsImpl {
  async applySettings() {
    this.#checkIfInitialized();
    const provider = await lazy.TorProviderBuilder.build();
    await provider.writeSettings(this.getSettings());
    await provider.writeSettings();
  }

  /**
@@ -1073,12 +1083,20 @@ class TorSettingsImpl {
  /**
   * Get a copy of all our settings.
   *
   * @param {boolean} [useTemporary=false] - Whether the returned settings
   *   should use the temporary bridge settings, if any, instead.
   *
   * @returns {object} A copy of the settings object
   */
  getSettings() {
  getSettings(useTemporary = false) {
    lazy.logger.debug("getSettings()");
    this.#checkIfInitialized();
    return structuredClone(this.#settings);
    const settings = structuredClone(this.#settings);
    if (useTemporary && this.#temporaryBridgeSettings) {
      // Override the bridge settings with our temporary ones.
      settings.bridges = structuredClone(this.#temporaryBridgeSettings);
    }
    return settings;
  }

  /**
@@ -1097,6 +1115,83 @@ class TorSettingsImpl {
    }
    return types;
  }

  /**
   * Apply some Moat bridges temporarily.
   *
   * These bridges will not yet be saved to settings.
   *
   * @param {MoatBridges} bridges - The bridges to apply.
   */
  async applyTemporaryBridges(bridges) {
    this.#checkIfInitialized();

    if (
      bridges.source !== TorBridgeSource.BuiltIn &&
      bridges.source !== TorBridgeSource.BridgeDB
    ) {
      throw new Error(`Invalid bridge source ${bridges.source}`);
    }

    const bridgeSettings = {
      enabled: true,
      source: bridges.source,
    };

    if (bridges.source === TorBridgeSource.BuiltIn) {
      if (!bridges.builtin_type) {
        throw Error("Missing a built-in type");
      }
      bridgeSettings.builtin_type = String(bridges.builtin_type);
      const bridgeStrings = this.getBuiltinBridges(bridgeSettings.builtin_type);
      if (!bridgeStrings.length) {
        throw new Error(`No builtin bridges for type ${bridges.builtin_type}`);
      }
      bridgeSettings.bridge_strings = bridgeStrings;
    } else {
      // BridgeDB.
      if (!bridges.bridge_strings?.length) {
        throw new Error("Missing bridges strings");
      }
      // TODO: Can we safely verify the format of the bridge addresses sent from
      // Moat?
      bridgeSettings.bridge_strings = Array.from(bridges.bridge_strings, item =>
        String(item)
      );
    }

    // After checks are complete, we commit them.
    this.#temporaryBridgeSettings = bridgeSettings;
    await this.applySettings();
  }

  /**
   * Save to current temporary bridges to be permanent instead.
   */
  async saveTemporaryBridges() {
    this.#checkIfInitialized();
    if (!this.#temporaryBridgeSettings) {
      lazy.logger.warn("No temporary bridges to save");
      return;
    }
    this.setSettings({ bridges: this.#temporaryBridgeSettings });
    this.#temporaryBridgeSettings = null;
    this.saveToPrefs();
    await this.applySettings();
  }

  /**
   * Clear the current temporary bridges.
   */
  async clearTemporaryBridges() {
    this.#checkIfInitialized();
    if (!this.#temporaryBridgeSettings) {
      lazy.logger.debug("No temporary bridges to clear");
      return;
    }
    this.#temporaryBridgeSettings = null;
    await this.applySettings();
  }
}

export const TorSettings = new TorSettingsImpl();