Verified Commit 5dd564d5 authored by Pier Angelo Vendrame's avatar Pier Angelo Vendrame 🎃
Browse files

fixup! Bug 40933: Add tor-launcher functionality

Second chunk of changes requested during the review.
parent f28fe46a
Loading
Loading
Loading
Loading
+74 −9
Original line number Diff line number Diff line
@@ -246,12 +246,38 @@ class AsyncSocket {
 * @property {Function} reject The function to reject the promise associated to
 * the command
 */
/**
 * The ID of a circuit.
 * From control-spec.txt:
 *   CircuitID = 1*16 IDChar
 *   IDChar = ALPHA / DIGIT
 *   Currently, Tor only uses digits, but this may change.
 *
 * @typedef {string} CircuitID
 */
/**
 * The ID of a stream.
 * From control-spec.txt:
 *   CircuitID = 1*16 IDChar
 *   IDChar = ALPHA / DIGIT
 *   Currently, Tor only uses digits, but this may change.
 *
 * @typedef {string} StreamID
 */
/**
 * The fingerprint of a node.
 * From control-spec.txt:
 *   Fingerprint = "$" 40*HEXDIG
 * However, we do not keep the $ in our structures.
 *
 * @typedef {string} NodeFingerprint
 */
/**
 * @typedef {object} Bridge
 * @property {string} transport The transport of the bridge, or vanilla if not
 * specified.
 * @property {string} addr The IP address and port of the bridge
 * @property {string} id The fingerprint of the bridge
 * @property {NodeFingerprint} id The fingerprint of the bridge
 * @property {string} args Optional arguments passed to the bridge
 */
/**
@@ -990,7 +1016,7 @@ export class TorController {
          status = this.#parseBootstrapStatus(data.groups.data);
        } catch (e) {
          // Probably, a non bootstrap client status
          logger.debug(`Failed to parse STATUS_CLIENT: ${data.groups.data}`);
          logger.debug(`Failed to parse STATUS_CLIENT: ${data.groups.data}`, e);
          break;
        }
        this.#eventHandler.onBootstrapStatus(status);
@@ -1123,14 +1149,53 @@ export class TorController {
}

/**
 * @typedef {object} TorEventHandler
 * The event handler interface.
 * The controller owner can implement this methods to receive asynchronous
 * notifications from the controller.
 *
 * @property {OnBootstrapStatus} onBootstrapStatus Called when a bootstrap
 * status is received (i.e., a STATUS_CLIENT event with a BOOTSTRAP action)
 * @property {OnLogMessage} onLogMessage Called when a log message is received
 * (i.e., a NOTICE, WARN or ERR notification)
 * @property {OnCircuitBuilt} onCircuitBuilt Called when a circuit is built
 * (i.e., a CIRC event with a BUILT status)
 * @property {OnCircuitClosed} onCircuitClosed Called when a circuit is closed
 * (i.e., a CIRC event with a CLOSED status)
 * @property {OnStreamSucceeded} onStreamSucceeded Called when a stream receives
 * a reply (i.e., a STREAM event with a SUCCEEDED status)
 */
/**
 * @callback OnBootstrapStatus
 *
 * @param {object} status An object with the bootstrap information. Its keys
 * depend on what the arguments sent by the tor daemon
 */
/**
 * @callback OnLogMessage
 *
 * @param {string} type The type of message (NOTICE, WARNING, ERR, etc...)
 * @param {string} message The actual log message
 */
/**
 * @callback OnCircuitBuilt
 *
 * @param {CircuitID} id The id of the circuit that has been built
 * @param {NodeFingerprint[]} nodes The onion routers composing the circuit
 */
/**
 * @callback OnCircuitClosed
 *
 * @param {CircuitID} id The id of the circuit that has been closed
 */
/**
 * @callback OnStreamSucceeded
 *
 * @param {StreamID} streamId The id of the stream that switched to the succeeded
 * state
 * @param {CircuitID} circuitId The id of the circuit the stream is using
 * @param {string?} username The SOCKS username associated to the stream, or
 * null if not available
 * @param {string?} username The SOCKS password associated to the stream, or
 * null if not available
 */
export class TorEventHandler {
  onBootstrapStatus(status) {}
  onLogMessage(message) {}
  onCircuitBuilt(id, nodes) {}
  onCircuitClosed(id) {}
  onStreamSucceeded(streamId, circuitId, username, password) {}
}
+4 −4
Original line number Diff line number Diff line
@@ -503,10 +503,6 @@ export const TorLauncherUtil = Object.freeze({
   */
  getPreferredSocksConfiguration() {
    if (Services.env.exists("TOR_TRANSPROXY")) {
      Services.prefs.setBoolPref("network.proxy.socks_remote_dns", false);
      Services.prefs.setIntPref("network.proxy.type", 0);
      Services.prefs.setIntPref("network.proxy.socks_port", 0);
      Services.prefs.setCharPref("network.proxy.socks", "");
      return { transproxy: true };
    }

@@ -576,6 +572,10 @@ export const TorLauncherUtil = Object.freeze({

  setProxyConfiguration(socksPortInfo) {
    if (socksPortInfo.transproxy) {
      Services.prefs.setBoolPref("network.proxy.socks_remote_dns", false);
      Services.prefs.setIntPref("network.proxy.type", 0);
      Services.prefs.setIntPref("network.proxy.socks_port", 0);
      Services.prefs.setCharPref("network.proxy.socks", "");
      return;
    }

+1 −1
Original line number Diff line number Diff line
@@ -361,7 +361,7 @@ export class TorProcess {
   * Hash a password to then pass it to Tor as a command line argument.
   * Based on Vidalia's TorSettings::hashPassword().
   *
   * @param {Array|Uint8Array} password The password, as an array of bytes
   * @param {Uint8Array} password The password, as an array of bytes
   */
  #hashPassword(password) {
    // The password has already been checked by the caller.
+18 −40
Original line number Diff line number Diff line
@@ -26,7 +26,7 @@ const logger = new ConsoleAPI({
 * @typedef {object} ControlPortSettings An object with the settings to use for
 * the control port. All the entries are optional, but an authentication
 * mechanism and a communication method must be specified.
 * @property {Array|Uint8Array=} password The clear text password as an array of
 * @property {Uint8Array=} password The clear text password as an array of
 * bytes. It must always be defined, unless cookieFilePath is
 * @property {string=} cookieFilePath The path to the cookie file to use for
 * authentication
@@ -41,22 +41,6 @@ const logger = new ConsoleAPI({
 * @property {string} type The message level
 * @property {string} msg The message
 */
/**
 * From control-spec.txt:
 *   CircuitID = 1*16 IDChar
 *   IDChar = ALPHA / DIGIT
 *   Currently, Tor only uses digits, but this may change.
 *
 * @typedef {string} CircuitID
 */
/**
 * The fingerprint of a node.
 * From control-spec.txt:
 *   Fingerprint = "$" 40*HEXDIG
 * However, we do not keep the $ in our structures.
 *
 * @typedef {string} NodeFingerprint
 */
/**
 * Stores the data associated with a circuit node.
 *
@@ -189,7 +173,7 @@ export class TorProvider {

    if (socksSettings.transproxy) {
      logger.info("Transparent proxy required, not starting a Tor daemon.");
    } else if (TorLauncherUtil.shouldStartAndOwnTor) {
    } else if (this.ownsTorDaemon) {
      try {
        await this.#startDaemon(socksSettings);
      } catch (e) {
@@ -228,8 +212,6 @@ export class TorProvider {
    logger.debug("Uninitializing the Tor provider.");
    this.#forgetProcess();
    this.#closeConnection();
    this.#isBootstrapDone = false;
    this.#lastWarning = {};
  }

  // Provider API
@@ -439,16 +421,12 @@ export class TorProvider {
    this.#torProcess.onExit = exitCode => {
      logger.info(`The tor process exited with code ${exitCode}`);
      this.#forgetProcess();
      this.#isBootstrapDone = false;
      this.#lastWarning = {};
      Services.obs.notifyObservers(null, TorProviderTopics.ProcessExited);
      this.#closeConnection();
      Services.obs.notifyObservers(null, TorProviderTopics.ProcessExited);
    };
    this.#torProcess.onRestart = async () => {
      logger.info("Restarting the tor process");
      this.#closeConnection();
      this.#isBootstrapDone = false;
      this.#lastWarning = {};
      this.#circuits.clear();
      try {
        await this.#firstConnection();
@@ -469,8 +447,8 @@ export class TorProvider {
    if (this.#torProcess) {
      logger.trace('"Forgetting" the tor process.');
      this.#torProcess.forget();
      this.#torProcess.onExit = null;
      this.#torProcess.onRestart = null;
      this.#torProcess.onExit = () => {};
      this.#torProcess.onRestart = () => {};
      this.#torProcess = null;
    }
  }
@@ -542,9 +520,9 @@ export class TorProvider {
        const encoder = new TextEncoder();
        settings.password = encoder.encode(TorParsers.unescapeString(password));
      } else if (/^([0-9a-fA-F]{2})+$/.test(password)) {
        settings.password = [];
        for (let i = 0; i < password.length; i += 2) {
          settings.password.push(parseInt(password.substring(i, i + 2), 16));
        settings.password = new Uint8Array(password.length / 2);
        for (let i = 0, j = 0; i < settings.password.length; i++, j += 2) {
          settings.password[i] = parseInt(password.substring(j, j + 2), 16);
        }
      }
      if (password && !settings.password?.length) {
@@ -562,7 +540,7 @@ export class TorProvider {
      }
    }
    if (
      TorLauncherUtil.shouldStartAndOwnTor &&
      this.ownsTorDaemon &&
      !settings.password?.length &&
      !settings.cookieFilePath
    ) {
@@ -574,17 +552,19 @@ export class TorProvider {

  async #firstConnection() {
    // FIXME: No way to cancel this connection! Do we need one?
    const initialDelay = 5;
    const maxDelay = 10_000;
    let delay = initialDelay;
    let delay = 5;
    logger.debug("Connecting to the control port for the first time.");
    await new Promise((resolve, reject) => {
      const tryConnect = () => {
        this.#openControlPort()
          .then(resolve)
          .then(() => {
            this.#torProcess?.connectionWorked();
            resolve();
          })
          .catch(e => {
            if (delay < maxDelay) {
              logger.error(
              logger.info(
                `Failed to connect to the control port. Trying again in ${delay}ms.`,
                e
              );
@@ -707,6 +687,8 @@ export class TorProvider {
        "Requested to close an already closed control port connection"
      );
    }
    this.#isBootstrapDone = false;
    this.#lastWarning = {};
  }

  // Authentication
@@ -716,17 +698,13 @@ export class TorProvider {
  }

  /**
   * @returns {string} A random 16-byte password.
   * @returns {Uint8Array} A random 16-byte password.
   */
  #generateRandomPassword() {
    const kPasswordLen = 16;
    return crypto.getRandomValues(new Uint8Array(kPasswordLen));
  }

  #toHex(aValue, aMinLen) {
    return aValue.toString(16).padStart(aMinLen, "0");
  }

  // Notification handlers

  onBootstrapStatus(status) {