[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [Git][tpo/applications/tor-browser][tor-browser-115.1.0esr-13.0-1] 2 commits: fixup! Bug 40933: Add tor-launcher functionality



Title: GitLab

Pier Angelo Vendrame pushed to branch tor-browser-115.1.0esr-13.0-1 at The Tor Project / Applications / Tor Browser

Commits:

  • d5283d94
    by Pier Angelo Vendrame at 2023-08-04T20:03:25+02:00
    fixup! Bug 40933: Add tor-launcher functionality
    
    Make TorProtocolService an ES class, and change _ with actual private
    stuff.
    
  • ab8a15b7
    by Pier Angelo Vendrame at 2023-08-04T20:03:26+02:00
    fixup! Bug 40933: Add tor-launcher functionality
    
    Merged TorMonitorService into TorProtocolService.
    

5 changed files:

Changes:

  • toolkit/components/tor-launcher/TorMonitorService.sys.mjs
    1 1
     // Copyright (c) 2022, The Tor Project, Inc.
    
    2 2
     
    
    3
    -import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";
    
    4
    -import { ConsoleAPI } from "resource://gre/modules/Console.sys.mjs";
    
    5
    -
    
    6
    -import {
    
    7
    -  TorParsers,
    
    8
    -  TorStatuses,
    
    9
    -} from "resource://gre/modules/TorParsers.sys.mjs";
    
    10
    -import { TorProcess } from "resource://gre/modules/TorProcess.sys.mjs";
    
    11
    -
    
    12
    -import { TorLauncherUtil } from "resource://gre/modules/TorLauncherUtil.sys.mjs";
    
    3
    +import { TorProviderTopics } from "resource://gre/modules/TorProviderBuilder.sys.mjs";
    
    13 4
     
    
    14 5
     const lazy = {};
    
    15
    -
    
    16
    -ChromeUtils.defineESModuleGetters(lazy, {
    
    17
    -  TorProtocolService: "resource://gre/modules/TorProtocolService.sys.mjs",
    
    18
    -  controller: "resource://gre/modules/TorControlPort.sys.mjs",
    
    19
    -});
    
    20
    -
    
    21 6
     ChromeUtils.defineESModuleGetters(lazy, {
    
    22 7
       TorProtocolService: "resource://gre/modules/TorProtocolService.sys.mjs",
    
    23 8
     });
    
    24 9
     
    
    25
    -const logger = new ConsoleAPI({
    
    26
    -  maxLogLevel: "warn",
    
    27
    -  maxLogLevelPref: "browser.tor_monitor_service.log_level",
    
    28
    -  prefix: "TorMonitorService",
    
    29
    -});
    
    30
    -
    
    31
    -const Preferences = Object.freeze({
    
    32
    -  PromptAtStartup: "extensions.torlauncher.prompt_at_startup",
    
    33
    -});
    
    34
    -
    
    35
    -const TorTopics = Object.freeze({
    
    36
    -  BootstrapError: "TorBootstrapError",
    
    37
    -  HasWarnOrErr: "TorLogHasWarnOrErr",
    
    38
    -  ProcessExited: "TorProcessExited",
    
    39
    -  ProcessIsReady: "TorProcessIsReady",
    
    40
    -  ProcessRestarted: "TorProcessRestarted",
    
    41
    -});
    
    42
    -
    
    43 10
     export const TorMonitorTopics = Object.freeze({
    
    44
    -  BridgeChanged: "TorBridgeChanged",
    
    45
    -  StreamSucceeded: "TorStreamSucceeded",
    
    46
    -});
    
    47
    -
    
    48
    -const ControlConnTimings = Object.freeze({
    
    49
    -  initialDelayMS: 25, // Wait 25ms after the process has started, before trying to connect
    
    50
    -  maxRetryMS: 10000, // Retry at most every 10 seconds
    
    51
    -  timeoutMS: 5 * 60 * 1000, // Wait at most 5 minutes for tor to start
    
    11
    +  BridgeChanged: TorProviderTopics.BridgeChanged,
    
    12
    +  StreamSucceeded: TorProviderTopics.StreamSucceeded,
    
    52 13
     });
    
    53 14
     
    
    54
    -/**
    
    55
    - * From control-spec.txt:
    
    56
    - *   CircuitID = 1*16 IDChar
    
    57
    - *   IDChar = ALPHA / DIGIT
    
    58
    - *   Currently, Tor only uses digits, but this may change.
    
    59
    - *
    
    60
    - * @typedef {string} CircuitID
    
    61
    - */
    
    62
    -/**
    
    63
    - * The fingerprint of a node.
    
    64
    - * From control-spec.txt:
    
    65
    - *   Fingerprint = "$" 40*HEXDIG
    
    66
    - * However, we do not keep the $ in our structures.
    
    67
    - *
    
    68
    - * @typedef {string} NodeFingerprint
    
    69
    - */
    
    70
    -
    
    71 15
     /**
    
    72 16
      * This service monitors an existing Tor instance, or starts one, if needed, and
    
    73 17
      * then starts monitoring it.
    
    ... ... @@ -76,575 +20,23 @@ const ControlConnTimings = Object.freeze({
    76 20
      * status of the bootstrap, the logs, etc...
    
    77 21
      */
    
    78 22
     export const TorMonitorService = {
    
    79
    -  _connection: null,
    
    80
    -  _eventHandlers: {},
    
    81
    -  _torLog: [], // Array of objects with date, type, and msg properties.
    
    82
    -  _startTimeout: null,
    
    83
    -
    
    84
    -  _isBootstrapDone: false,
    
    85
    -  _lastWarningPhase: null,
    
    86
    -  _lastWarningReason: null,
    
    87
    -
    
    88
    -  _torProcess: null,
    
    89
    -
    
    90
    -  _inited: false,
    
    91
    -
    
    92
    -  /**
    
    93
    -   * Stores the nodes of a circuit. Keys are cicuit IDs, and values are the node
    
    94
    -   * fingerprints.
    
    95
    -   *
    
    96
    -   * Theoretically, we could hook this map up to the new identity notification,
    
    97
    -   * but in practice it does not work. Tor pre-builds circuits, and the NEWNYM
    
    98
    -   * signal does not affect them. So, we might end up using a circuit that was
    
    99
    -   * built before the new identity but not yet used. If we cleaned the map, we
    
    100
    -   * risked of not having the data about it.
    
    101
    -   *
    
    102
    -   * @type {Map<CircuitID, NodeFingerprint[]>}
    
    103
    -   */
    
    104
    -  _circuits: new Map(),
    
    105
    -  /**
    
    106
    -   * The last used bridge, or null if bridges are not in use or if it was not
    
    107
    -   * possible to detect the bridge. This needs the user to have specified bridge
    
    108
    -   * lines with fingerprints to work.
    
    109
    -   *
    
    110
    -   * @type {NodeFingerprint?}
    
    111
    -   */
    
    112
    -  _currentBridge: null,
    
    113
    -
    
    114
    -  // Public methods
    
    115
    -
    
    116
    -  // Starts Tor, if needed, and starts monitoring for events
    
    117
    -  init() {
    
    118
    -    if (this._inited) {
    
    119
    -      return;
    
    120
    -    }
    
    121
    -    this._inited = true;
    
    122
    -
    
    123
    -    // We always liten to these events, because they are needed for the circuit
    
    124
    -    // display.
    
    125
    -    this._eventHandlers = new Map([
    
    126
    -      ["CIRC", this._processCircEvent.bind(this)],
    
    127
    -      ["STREAM", this._processStreamEvent.bind(this)],
    
    128
    -    ]);
    
    129
    -
    
    130
    -    if (this.ownsTorDaemon) {
    
    131
    -      // When we own the tor daemon, we listen to more events, that are used
    
    132
    -      // for about:torconnect or for showing the logs in the settings page.
    
    133
    -      this._eventHandlers.set("STATUS_CLIENT", (_eventType, lines) =>
    
    134
    -        this._processBootstrapStatus(lines[0], false)
    
    135
    -      );
    
    136
    -      this._eventHandlers.set("NOTICE", this._processLog.bind(this));
    
    137
    -      this._eventHandlers.set("WARN", this._processLog.bind(this));
    
    138
    -      this._eventHandlers.set("ERR", this._processLog.bind(this));
    
    139
    -      this._controlTor();
    
    140
    -    } else {
    
    141
    -      this._startEventMonitor();
    
    142
    -    }
    
    143
    -    logger.info("TorMonitorService initialized");
    
    144
    -  },
    
    145
    -
    
    146
    -  // Closes the connection that monitors for events.
    
    147
    -  // When Tor is started by Tor Browser, it is configured to exit when the
    
    148
    -  // control connection is closed. Therefore, as a matter of facts, calling this
    
    149
    -  // function also makes the child Tor instance stop.
    
    150
    -  uninit() {
    
    151
    -    if (this._torProcess) {
    
    152
    -      this._torProcess.forget();
    
    153
    -      this._torProcess.onExit = null;
    
    154
    -      this._torProcess.onRestart = null;
    
    155
    -      this._torProcess = null;
    
    156
    -    }
    
    157
    -    this._shutDownEventMonitor();
    
    158
    -  },
    
    159
    -
    
    160
    -  async retrieveBootstrapStatus() {
    
    161
    -    if (!this._connection) {
    
    162
    -      throw new Error("Event monitor connection not available");
    
    163
    -    }
    
    164
    -
    
    165
    -    // TODO: Unify with TorProtocolService.sendCommand and put everything in the
    
    166
    -    // reviewed torbutton replacement.
    
    167
    -    const cmd = "GETINFO";
    
    168
    -    const key = "status/bootstrap-phase";
    
    169
    -    let reply = await this._connection.sendCommand(`${cmd} ${key}`);
    
    170
    -
    
    171
    -    // A typical reply looks like:
    
    172
    -    //  250-status/bootstrap-phase=NOTICE BOOTSTRAP PROGRESS=100 TAG=done SUMMARY="Done"
    
    173
    -    //  250 OK
    
    174
    -    reply = TorParsers.parseCommandResponse(reply);
    
    175
    -    if (!TorParsers.commandSucceeded(reply)) {
    
    176
    -      throw new Error(`${cmd} failed`);
    
    177
    -    }
    
    178
    -    reply = TorParsers.parseReply(cmd, key, reply);
    
    179
    -    if (reply.length) {
    
    180
    -      this._processBootstrapStatus(reply[0], true);
    
    181
    -    }
    
    182
    -  },
    
    183
    -
    
    184
    -  // Returns captured log message as a text string (one message per line).
    
    185
    -  getLog() {
    
    186
    -    return this._torLog
    
    187
    -      .map(logObj => {
    
    188
    -        const timeStr = logObj.date
    
    189
    -          .toISOString()
    
    190
    -          .replace("T", " ")
    
    191
    -          .replace("Z", "");
    
    192
    -        return `${timeStr} [${logObj.type}] ${logObj.msg}`;
    
    193
    -      })
    
    194
    -      .join(TorLauncherUtil.isWindows ? "\r\n" : "\n");
    
    23
    +  get currentBridge() {
    
    24
    +    return lazy.TorProtocolService.currentBridge;
    
    195 25
       },
    
    196 26
     
    
    197
    -  // true if we launched and control tor, false if using system tor
    
    198 27
       get ownsTorDaemon() {
    
    199
    -    return TorLauncherUtil.shouldStartAndOwnTor;
    
    200
    -  },
    
    201
    -
    
    202
    -  get isBootstrapDone() {
    
    203
    -    return this._isBootstrapDone;
    
    204
    -  },
    
    205
    -
    
    206
    -  clearBootstrapError() {
    
    207
    -    this._lastWarningPhase = null;
    
    208
    -    this._lastWarningReason = null;
    
    28
    +    return lazy.TorProtocolService.ownsTorDaemon;
    
    209 29
       },
    
    210 30
     
    
    211 31
       get isRunning() {
    
    212
    -    return !!this._connection;
    
    32
    +    return lazy.TorProtocolService.isRunning;
    
    213 33
       },
    
    214 34
     
    
    215
    -  /**
    
    216
    -   * Return the data about the current bridge, if any, or null.
    
    217
    -   * We can detect bridge only when the configured bridge lines include the
    
    218
    -   * fingerprints.
    
    219
    -   *
    
    220
    -   * @returns {NodeData?} The node information, or null if the first node
    
    221
    -   * is not a bridge, or no circuit has been opened, yet.
    
    222
    -   */
    
    223
    -  get currentBridge() {
    
    224
    -    return this._currentBridge;
    
    225
    -  },
    
    226
    -
    
    227
    -  // Private methods
    
    228
    -
    
    229
    -  async _startProcess() {
    
    230
    -    // TorProcess should be instanced once, then always reused and restarted
    
    231
    -    // only through the prompt it exposes when the controlled process dies.
    
    232
    -    if (!this._torProcess) {
    
    233
    -      this._torProcess = new TorProcess(
    
    234
    -        lazy.TorProtocolService.torControlPortInfo,
    
    235
    -        lazy.TorProtocolService.torSOCKSPortInfo
    
    236
    -      );
    
    237
    -      this._torProcess.onExit = () => {
    
    238
    -        this._shutDownEventMonitor();
    
    239
    -        Services.obs.notifyObservers(null, TorTopics.ProcessExited);
    
    240
    -      };
    
    241
    -      this._torProcess.onRestart = async () => {
    
    242
    -        this._shutDownEventMonitor();
    
    243
    -        await this._controlTor();
    
    244
    -        Services.obs.notifyObservers(null, TorTopics.ProcessRestarted);
    
    245
    -      };
    
    246
    -    }
    
    247
    -
    
    248
    -    // Already running, but we did not start it
    
    249
    -    if (this._torProcess.isRunning) {
    
    250
    -      return false;
    
    251
    -    }
    
    252
    -
    
    253
    -    try {
    
    254
    -      await this._torProcess.start();
    
    255
    -      if (this._torProcess.isRunning) {
    
    256
    -        logger.info("tor started");
    
    257
    -        this._torProcessStartTime = Date.now();
    
    258
    -      }
    
    259
    -    } catch (e) {
    
    260
    -      // TorProcess already logs the error.
    
    261
    -      this._lastWarningPhase = "startup";
    
    262
    -      this._lastWarningReason = e.toString();
    
    263
    -    }
    
    264
    -    return this._torProcess.isRunning;
    
    265
    -  },
    
    266
    -
    
    267
    -  async _controlTor() {
    
    268
    -    if (!this._torProcess?.isRunning && !(await this._startProcess())) {
    
    269
    -      logger.error("Tor not running, not starting to monitor it.");
    
    270
    -      return;
    
    271
    -    }
    
    272
    -
    
    273
    -    let delayMS = ControlConnTimings.initialDelayMS;
    
    274
    -    const callback = async () => {
    
    275
    -      if (await this._startEventMonitor()) {
    
    276
    -        this.retrieveBootstrapStatus().catch(e => {
    
    277
    -          logger.warn("Could not get the initial bootstrap status", e);
    
    278
    -        });
    
    279
    -
    
    280
    -        // FIXME: TorProcess is misleading here. We should use a topic related
    
    281
    -        // to having a control port connection, instead.
    
    282
    -        logger.info(`Notifying ${TorTopics.ProcessIsReady}`);
    
    283
    -        Services.obs.notifyObservers(null, TorTopics.ProcessIsReady);
    
    284
    -
    
    285
    -        // We reset this here hoping that _shutDownEventMonitor can interrupt
    
    286
    -        // the current monitor, either by calling clearTimeout and preventing it
    
    287
    -        // from starting, or by closing the control port connection.
    
    288
    -        if (this._startTimeout === null) {
    
    289
    -          logger.warn("Someone else reset _startTimeout!");
    
    290
    -        }
    
    291
    -        this._startTimeout = null;
    
    292
    -      } else if (
    
    293
    -        Date.now() - this._torProcessStartTime >
    
    294
    -        ControlConnTimings.timeoutMS
    
    295
    -      ) {
    
    296
    -        let s = TorLauncherUtil.getLocalizedString("tor_controlconn_failed");
    
    297
    -        this._lastWarningPhase = "startup";
    
    298
    -        this._lastWarningReason = s;
    
    299
    -        logger.info(s);
    
    300
    -        if (this._startTimeout === null) {
    
    301
    -          logger.warn("Someone else reset _startTimeout!");
    
    302
    -        }
    
    303
    -        this._startTimeout = null;
    
    304
    -      } else {
    
    305
    -        delayMS *= 2;
    
    306
    -        if (delayMS > ControlConnTimings.maxRetryMS) {
    
    307
    -          delayMS = ControlConnTimings.maxRetryMS;
    
    308
    -        }
    
    309
    -        this._startTimeout = setTimeout(() => {
    
    310
    -          logger.debug(`Control port not ready, waiting ${delayMS / 1000}s.`);
    
    311
    -          callback();
    
    312
    -        }, delayMS);
    
    313
    -      }
    
    314
    -    };
    
    315
    -    // Check again, in the unfortunate case in which the execution was alrady
    
    316
    -    // queued, but was waiting network code.
    
    317
    -    if (this._startTimeout === null) {
    
    318
    -      this._startTimeout = setTimeout(callback, delayMS);
    
    319
    -    } else {
    
    320
    -      logger.error("Possible race? Refusing to start the timeout again");
    
    321
    -    }
    
    322
    -  },
    
    323
    -
    
    324
    -  async _startEventMonitor() {
    
    325
    -    if (this._connection) {
    
    326
    -      return true;
    
    327
    -    }
    
    328
    -
    
    329
    -    let conn;
    
    330
    -    try {
    
    331
    -      conn = await lazy.controller();
    
    332
    -    } catch (e) {
    
    333
    -      logger.error("Cannot open a control port connection", e);
    
    334
    -      if (conn) {
    
    335
    -        try {
    
    336
    -          conn.close();
    
    337
    -        } catch (e) {
    
    338
    -          logger.error(
    
    339
    -            "Also, the connection is not null but cannot be closed",
    
    340
    -            e
    
    341
    -          );
    
    342
    -        }
    
    343
    -      }
    
    344
    -      return false;
    
    345
    -    }
    
    346
    -
    
    347
    -    // TODO: optionally monitor INFO and DEBUG log messages.
    
    348
    -    try {
    
    349
    -      await conn.setEvents(Array.from(this._eventHandlers.keys()));
    
    350
    -    } catch (e) {
    
    351
    -      logger.error("SETEVENTS failed", e);
    
    352
    -      conn.close();
    
    353
    -      return false;
    
    354
    -    }
    
    355
    -
    
    356
    -    if (this._torProcess) {
    
    357
    -      this._torProcess.connectionWorked();
    
    358
    -    }
    
    359
    -    if (this.ownsTorDaemon && !TorLauncherUtil.shouldOnlyConfigureTor) {
    
    360
    -      try {
    
    361
    -        await this._takeTorOwnership(conn);
    
    362
    -      } catch (e) {
    
    363
    -        logger.warn("Could not take ownership of the Tor daemon", e);
    
    364
    -      }
    
    365
    -    }
    
    366
    -
    
    367
    -    this._connection = conn;
    
    368
    -
    
    369
    -    for (const [type, callback] of this._eventHandlers.entries()) {
    
    370
    -      this._monitorEvent(type, callback);
    
    371
    -    }
    
    372
    -
    
    373
    -    // Populate the circuit map already, in case we are connecting to an
    
    374
    -    // external tor daemon.
    
    375
    -    try {
    
    376
    -      const reply = await this._connection.sendCommand(
    
    377
    -        "GETINFO circuit-status"
    
    378
    -      );
    
    379
    -      const lines = reply.split(/\r?\n/);
    
    380
    -      if (lines.shift() === "250+circuit-status=") {
    
    381
    -        for (const line of lines) {
    
    382
    -          if (line === ".") {
    
    383
    -            break;
    
    384
    -          }
    
    385
    -          // _processCircEvent processes only one line at a time
    
    386
    -          this._processCircEvent("CIRC", [line]);
    
    387
    -        }
    
    388
    -      }
    
    389
    -    } catch (e) {
    
    390
    -      logger.warn("Could not populate the initial circuit map", e);
    
    391
    -    }
    
    392
    -
    
    393
    -    return true;
    
    394
    -  },
    
    395
    -
    
    396
    -  // Try to become the primary controller (TAKEOWNERSHIP).
    
    397
    -  async _takeTorOwnership(conn) {
    
    398
    -    try {
    
    399
    -      conn.takeOwnership();
    
    400
    -    } catch (e) {
    
    401
    -      logger.warn("Take ownership failed", e);
    
    402
    -      return;
    
    403
    -    }
    
    404
    -    try {
    
    405
    -      conn.resetOwningControllerProcess();
    
    406
    -    } catch (e) {
    
    407
    -      logger.warn("Clear owning controller process failed", e);
    
    408
    -    }
    
    409
    -  },
    
    410
    -
    
    411
    -  _monitorEvent(type, callback) {
    
    412
    -    logger.info(`Watching events of type ${type}.`);
    
    413
    -    let replyObj = {};
    
    414
    -    this._connection.watchEvent(
    
    415
    -      type,
    
    416
    -      null,
    
    417
    -      line => {
    
    418
    -        if (!line) {
    
    419
    -          return;
    
    420
    -        }
    
    421
    -        logger.debug("Event response: ", line);
    
    422
    -        const isComplete = TorParsers.parseReplyLine(line, replyObj);
    
    423
    -        if (!isComplete || replyObj._parseError || !replyObj.lineArray.length) {
    
    424
    -          return;
    
    425
    -        }
    
    426
    -        const reply = replyObj;
    
    427
    -        replyObj = {};
    
    428
    -        if (reply.statusCode !== TorStatuses.EventNotification) {
    
    429
    -          logger.error("Unexpected event status code:", reply.statusCode);
    
    430
    -          return;
    
    431
    -        }
    
    432
    -        if (!reply.lineArray[0].startsWith(`${type} `)) {
    
    433
    -          logger.error("Wrong format for the first line:", reply.lineArray[0]);
    
    434
    -          return;
    
    435
    -        }
    
    436
    -        reply.lineArray[0] = reply.lineArray[0].substring(type.length + 1);
    
    437
    -        try {
    
    438
    -          callback(type, reply.lineArray);
    
    439
    -        } catch (e) {
    
    440
    -          logger.error("Exception while handling an event", reply, e);
    
    441
    -        }
    
    442
    -      },
    
    443
    -      true
    
    444
    -    );
    
    445
    -  },
    
    446
    -
    
    447
    -  _processLog(type, lines) {
    
    448
    -    if (type === "WARN" || type === "ERR") {
    
    449
    -      // Notify so that Copy Log can be enabled.
    
    450
    -      Services.obs.notifyObservers(null, TorTopics.HasWarnOrErr);
    
    451
    -    }
    
    452
    -
    
    453
    -    const date = new Date();
    
    454
    -    const maxEntries = Services.prefs.getIntPref(
    
    455
    -      "extensions.torlauncher.max_tor_log_entries",
    
    456
    -      1000
    
    457
    -    );
    
    458
    -    if (maxEntries > 0 && this._torLog.length >= maxEntries) {
    
    459
    -      this._torLog.splice(0, 1);
    
    460
    -    }
    
    461
    -
    
    462
    -    const msg = lines.join("\n");
    
    463
    -    this._torLog.push({ date, type, msg });
    
    464
    -    const logString = `Tor ${type}: ${msg}`;
    
    465
    -    logger.info(logString);
    
    466
    -  },
    
    467
    -
    
    468
    -  // Process a bootstrap status to update the current state, and broadcast it
    
    469
    -  // to TorBootstrapStatus observers.
    
    470
    -  // If aSuppressErrors is true, errors are ignored. This is used when we
    
    471
    -  // are handling the response to a "GETINFO status/bootstrap-phase" command.
    
    472
    -  _processBootstrapStatus(aStatusMsg, aSuppressErrors) {
    
    473
    -    const statusObj = TorParsers.parseBootstrapStatus(aStatusMsg);
    
    474
    -    if (!statusObj) {
    
    475
    -      return;
    
    476
    -    }
    
    477
    -
    
    478
    -    // Notify observers
    
    479
    -    statusObj.wrappedJSObject = statusObj;
    
    480
    -    Services.obs.notifyObservers(statusObj, "TorBootstrapStatus");
    
    481
    -
    
    482
    -    if (statusObj.PROGRESS === 100) {
    
    483
    -      this._isBootstrapDone = true;
    
    484
    -      try {
    
    485
    -        Services.prefs.setBoolPref(Preferences.PromptAtStartup, false);
    
    486
    -      } catch (e) {
    
    487
    -        logger.warn(`Cannot set ${Preferences.PromptAtStartup}`, e);
    
    488
    -      }
    
    489
    -      return;
    
    490
    -    }
    
    491
    -
    
    492
    -    this._isBootstrapDone = false;
    
    493
    -
    
    494
    -    if (
    
    495
    -      statusObj.TYPE === "WARN" &&
    
    496
    -      statusObj.RECOMMENDATION !== "ignore" &&
    
    497
    -      !aSuppressErrors
    
    498
    -    ) {
    
    499
    -      this._notifyBootstrapError(statusObj);
    
    500
    -    }
    
    501
    -  },
    
    502
    -
    
    503
    -  _notifyBootstrapError(statusObj) {
    
    504
    -    try {
    
    505
    -      Services.prefs.setBoolPref(Preferences.PromptAtStartup, true);
    
    506
    -    } catch (e) {
    
    507
    -      logger.warn(`Cannot set ${Preferences.PromptAtStartup}`, e);
    
    508
    -    }
    
    509
    -    const phase = TorLauncherUtil.getLocalizedBootstrapStatus(statusObj, "TAG");
    
    510
    -    const reason = TorLauncherUtil.getLocalizedBootstrapStatus(
    
    511
    -      statusObj,
    
    512
    -      "REASON"
    
    513
    -    );
    
    514
    -    const details = TorLauncherUtil.getFormattedLocalizedString(
    
    515
    -      "tor_bootstrap_failed_details",
    
    516
    -      [phase, reason],
    
    517
    -      2
    
    518
    -    );
    
    519
    -    logger.error(
    
    520
    -      `Tor bootstrap error: [${statusObj.TAG}/${statusObj.REASON}] ${details}`
    
    521
    -    );
    
    522
    -
    
    523
    -    if (
    
    524
    -      statusObj.TAG !== this._lastWarningPhase ||
    
    525
    -      statusObj.REASON !== this._lastWarningReason
    
    526
    -    ) {
    
    527
    -      this._lastWarningPhase = statusObj.TAG;
    
    528
    -      this._lastWarningReason = statusObj.REASON;
    
    529
    -
    
    530
    -      const message = TorLauncherUtil.getLocalizedString(
    
    531
    -        "tor_bootstrap_failed"
    
    532
    -      );
    
    533
    -      Services.obs.notifyObservers(
    
    534
    -        { message, details },
    
    535
    -        TorTopics.BootstrapError
    
    536
    -      );
    
    537
    -    }
    
    538
    -  },
    
    539
    -
    
    540
    -  async _processCircEvent(_type, lines) {
    
    541
    -    const builtEvent =
    
    542
    -      /^(?<CircuitID>[a-zA-Z0-9]{1,16})\sBUILT\s(?<Path>(?:,?\$[0-9a-fA-F]{40}(?:~[a-zA-Z0-9]{1,19})?)+)/.exec(
    
    543
    -        lines[0]
    
    544
    -      );
    
    545
    -    const closedEvent = /^(?<ID>[a-zA-Z0-9]{1,16})\sCLOSED/.exec(lines[0]);
    
    546
    -    if (builtEvent) {
    
    547
    -      const fp = /\$([0-9a-fA-F]{40})/g;
    
    548
    -      const nodes = Array.from(builtEvent.groups.Path.matchAll(fp), g =>
    
    549
    -        g[1].toUpperCase()
    
    550
    -      );
    
    551
    -      this._circuits.set(builtEvent.groups.CircuitID, nodes);
    
    552
    -      // Ignore circuits of length 1, that are used, for example, to probe
    
    553
    -      // bridges. So, only store them, since we might see streams that use them,
    
    554
    -      // but then early-return.
    
    555
    -      if (nodes.length === 1) {
    
    556
    -        return;
    
    557
    -      }
    
    558
    -      // In some cases, we might already receive SOCKS credentials in the line.
    
    559
    -      // However, this might be a problem with onion services: we get also a
    
    560
    -      // 4-hop circuit that we likely do not want to show to the user,
    
    561
    -      // especially because it is used only temporarily, and it would need a
    
    562
    -      // technical explaination.
    
    563
    -      // this._checkCredentials(lines[0], nodes);
    
    564
    -      if (this._currentBridge?.fingerprint !== nodes[0]) {
    
    565
    -        const nodeInfo = await lazy.TorProtocolService.getNodeInfo(nodes[0]);
    
    566
    -        let notify = false;
    
    567
    -        if (nodeInfo?.bridgeType) {
    
    568
    -          logger.info(`Bridge changed to ${nodes[0]}`);
    
    569
    -          this._currentBridge = nodeInfo;
    
    570
    -          notify = true;
    
    571
    -        } else if (this._currentBridge) {
    
    572
    -          logger.info("Bridges disabled");
    
    573
    -          this._currentBridge = null;
    
    574
    -          notify = true;
    
    575
    -        }
    
    576
    -        if (notify) {
    
    577
    -          Services.obs.notifyObservers(
    
    578
    -            null,
    
    579
    -            TorMonitorTopics.BridgeChanged,
    
    580
    -            this._currentBridge
    
    581
    -          );
    
    582
    -        }
    
    583
    -      }
    
    584
    -    } else if (closedEvent) {
    
    585
    -      this._circuits.delete(closedEvent.groups.ID);
    
    586
    -    }
    
    587
    -  },
    
    588
    -
    
    589
    -  _processStreamEvent(_type, lines) {
    
    590
    -    // The first block is the stream ID, which we do not need at the moment.
    
    591
    -    const succeeedEvent =
    
    592
    -      /^[a-zA-Z0-9]{1,16}\sSUCCEEDED\s(?<CircuitID>[a-zA-Z0-9]{1,16})/.exec(
    
    593
    -        lines[0]
    
    594
    -      );
    
    595
    -    if (!succeeedEvent) {
    
    596
    -      return;
    
    597
    -    }
    
    598
    -    const circuit = this._circuits.get(succeeedEvent.groups.CircuitID);
    
    599
    -    if (!circuit) {
    
    600
    -      logger.error(
    
    601
    -        "Seen a STREAM SUCCEEDED with an unknown circuit. Not notifying observers.",
    
    602
    -        lines[0]
    
    603
    -      );
    
    604
    -      return;
    
    605
    -    }
    
    606
    -    this._checkCredentials(lines[0], circuit);
    
    607
    -  },
    
    608
    -
    
    609
    -  /**
    
    610
    -   * Check if a STREAM or CIRC response line contains SOCKS_USERNAME and
    
    611
    -   * SOCKS_PASSWORD. In case, notify observers that we could associate a certain
    
    612
    -   * circuit to these credentials.
    
    613
    -   *
    
    614
    -   * @param {string} line The circ or stream line to check
    
    615
    -   * @param {NodeFingerprint[]} circuit The fingerprints of the nodes in the
    
    616
    -   * circuit.
    
    617
    -   */
    
    618
    -  _checkCredentials(line, circuit) {
    
    619
    -    const username = /SOCKS_USERNAME=("(?:[^"\\]|\\.)*")/.exec(line);
    
    620
    -    const password = /SOCKS_PASSWORD=("(?:[^"\\]|\\.)*")/.exec(line);
    
    621
    -    if (!username || !password) {
    
    622
    -      return;
    
    623
    -    }
    
    624
    -    Services.obs.notifyObservers(
    
    625
    -      {
    
    626
    -        wrappedJSObject: {
    
    627
    -          username: TorParsers.unescapeString(username[1]),
    
    628
    -          password: TorParsers.unescapeString(password[1]),
    
    629
    -          circuit,
    
    630
    -        },
    
    631
    -      },
    
    632
    -      TorMonitorTopics.StreamSucceeded
    
    633
    -    );
    
    35
    +  get isBootstrapDone() {
    
    36
    +    return lazy.TorProtocolService.isBootstrapDone;
    
    634 37
       },
    
    635 38
     
    
    636
    -  _shutDownEventMonitor() {
    
    637
    -    try {
    
    638
    -      this._connection?.close();
    
    639
    -    } catch (e) {
    
    640
    -      logger.error("Could not close the connection to the control port", e);
    
    641
    -    }
    
    642
    -    this._connection = null;
    
    643
    -    if (this._startTimeout !== null) {
    
    644
    -      clearTimeout(this._startTimeout);
    
    645
    -      this._startTimeout = null;
    
    646
    -    }
    
    647
    -    this._isBootstrapDone = false;
    
    648
    -    this.clearBootstrapError();
    
    39
    +  getLog() {
    
    40
    +    return lazy.TorProtocolService.getLog();
    
    649 41
       },
    
    650 42
     };

  • toolkit/components/tor-launcher/TorProtocolService.sys.mjs
    1 1
     // Copyright (c) 2021, The Tor Project, Inc.
    
    2 2
     
    
    3
    -import { setTimeout } from "resource://gre/modules/Timer.sys.mjs";
    
    3
    +import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";
    
    4 4
     import { ConsoleAPI } from "resource://gre/modules/Console.sys.mjs";
    
    5 5
     
    
    6
    -import { TorParsers } from "resource://gre/modules/TorParsers.sys.mjs";
    
    7 6
     import { TorLauncherUtil } from "resource://gre/modules/TorLauncherUtil.sys.mjs";
    
    7
    +import {
    
    8
    +  TorParsers,
    
    9
    +  TorStatuses,
    
    10
    +} from "resource://gre/modules/TorParsers.sys.mjs";
    
    11
    +import { TorProviderTopics } from "resource://gre/modules/TorProviderBuilder.sys.mjs";
    
    8 12
     
    
    9 13
     const lazy = {};
    
    10 14
     
    
    11
    -ChromeUtils.defineModuleGetter(
    
    12
    -  lazy,
    
    13
    -  "FileUtils",
    
    14
    -  "resource://gre/modules/FileUtils.jsm"
    
    15
    -);
    
    16
    -
    
    17
    -ChromeUtils.defineModuleGetter(
    
    18
    -  lazy,
    
    19
    -  "TorMonitorService",
    
    20
    -  "resource://gre/modules/TorMonitorService.jsm"
    
    21
    -);
    
    22 15
     ChromeUtils.defineESModuleGetters(lazy, {
    
    23 16
       controller: "resource://gre/modules/TorControlPort.sys.mjs",
    
    24 17
       configureControlPortModule: "resource://gre/modules/TorControlPort.sys.mjs",
    
    25
    -});
    
    26
    -
    
    27
    -const TorTopics = Object.freeze({
    
    28
    -  ProcessExited: "TorProcessExited",
    
    29
    -  ProcessRestarted: "TorProcessRestarted",
    
    18
    +  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
    
    19
    +  TorProcess: "resource://gre/modules/TorProcess.sys.mjs",
    
    30 20
     });
    
    31 21
     
    
    32 22
     const logger = new ConsoleAPI({
    
    ... ... @@ -34,11 +24,27 @@ const logger = new ConsoleAPI({
    34 24
       prefix: "TorProtocolService",
    
    35 25
     });
    
    36 26
     
    
    27
    +/**
    
    28
    + * From control-spec.txt:
    
    29
    + *   CircuitID = 1*16 IDChar
    
    30
    + *   IDChar = ALPHA / DIGIT
    
    31
    + *   Currently, Tor only uses digits, but this may change.
    
    32
    + *
    
    33
    + * @typedef {string} CircuitID
    
    34
    + */
    
    35
    +/**
    
    36
    + * The fingerprint of a node.
    
    37
    + * From control-spec.txt:
    
    38
    + *   Fingerprint = "$" 40*HEXDIG
    
    39
    + * However, we do not keep the $ in our structures.
    
    40
    + *
    
    41
    + * @typedef {string} NodeFingerprint
    
    42
    + */
    
    37 43
     /**
    
    38 44
      * Stores the data associated with a circuit node.
    
    39 45
      *
    
    40 46
      * @typedef NodeData
    
    41
    - * @property {string} fingerprint The node fingerprint.
    
    47
    + * @property {NodeFingerprint} fingerprint The node fingerprint.
    
    42 48
      * @property {string[]} ipAddrs - The ip addresses associated with this node.
    
    43 49
      * @property {string?} bridgeType - The bridge type for this node, or "" if the
    
    44 50
      *   node is a bridge but the type is unknown, or null if this is not a bridge
    
    ... ... @@ -48,60 +54,71 @@ const logger = new ConsoleAPI({
    48 54
      *   valid BCP47 Region subtag.
    
    49 55
      */
    
    50 56
     
    
    51
    -// Manage the connection to tor's control port, to update its settings and query
    
    52
    -// other useful information.
    
    53
    -//
    
    54
    -// NOTE: Many Tor protocol functions return a reply object, which is a
    
    55
    -// a _javascript_ object that has the following fields:
    
    56
    -//   reply.statusCode  -- integer, e.g., 250
    
    57
    -//   reply.lineArray   -- an array of strings returned by tor
    
    58
    -// For GetConf calls, the aKey prefix is removed from the lineArray strings.
    
    59
    -export const TorProtocolService = {
    
    60
    -  _inited: false,
    
    57
    +const Preferences = Object.freeze({
    
    58
    +  PromptAtStartup: "extensions.torlauncher.prompt_at_startup",
    
    59
    +});
    
    60
    +
    
    61
    +const ControlConnTimings = Object.freeze({
    
    62
    +  initialDelayMS: 25, // Wait 25ms after the process has started, before trying to connect
    
    63
    +  maxRetryMS: 10000, // Retry at most every 10 seconds
    
    64
    +  timeoutMS: 5 * 60 * 1000, // Wait at most 5 minutes for tor to start
    
    65
    +});
    
    66
    +
    
    67
    +/**
    
    68
    + * This is a Tor provider for the C Tor daemon.
    
    69
    + *
    
    70
    + * It can start a new tor instance, or connect to an existing one.
    
    71
    + * In the former case, it also takes its ownership by default.
    
    72
    + */
    
    73
    +class TorProvider {
    
    74
    +  #inited = false;
    
    61 75
     
    
    62 76
       // Maintain a map of tor settings set by Tor Browser so that we don't
    
    63 77
       // repeatedly set the same key/values over and over.
    
    64 78
       // This map contains string keys to primitives or array values.
    
    65
    -  _settingsCache: new Map(),
    
    79
    +  #settingsCache = new Map();
    
    66 80
     
    
    67
    -  _controlPort: null,
    
    68
    -  _controlHost: null,
    
    69
    -  _controlIPCFile: null, // An nsIFile if using IPC for control port.
    
    70
    -  _controlPassword: null, // JS string that contains hex-encoded password.
    
    71
    -  _SOCKSPortInfo: null, // An object that contains ipcFile, host, port.
    
    81
    +  #controlPort = null;
    
    82
    +  #controlHost = null;
    
    83
    +  #controlIPCFile = null; // An nsIFile if using IPC for control port.
    
    84
    +  #controlPassword = null; // JS string that contains hex-encoded password.
    
    85
    +  #SOCKSPortInfo = null; // An object that contains ipcFile, host, port.
    
    72 86
     
    
    73
    -  _controlConnection: null, // This is cached and reused.
    
    74
    -  _connectionQueue: [],
    
    87
    +  #controlConnection = null; // This is cached and reused.
    
    88
    +  #connectionQueue = [];
    
    75 89
     
    
    76 90
       // Public methods
    
    77 91
     
    
    78 92
       async init() {
    
    79
    -    if (this._inited) {
    
    93
    +    if (this.#inited) {
    
    80 94
           return;
    
    81 95
         }
    
    82
    -    this._inited = true;
    
    96
    +    this.#inited = true;
    
    97
    +
    
    98
    +    Services.obs.addObserver(this, TorProviderTopics.ProcessExited);
    
    99
    +    Services.obs.addObserver(this, TorProviderTopics.ProcessRestarted);
    
    83 100
     
    
    84
    -    Services.obs.addObserver(this, TorTopics.ProcessExited);
    
    85
    -    Services.obs.addObserver(this, TorTopics.ProcessRestarted);
    
    101
    +    await this.#setSockets();
    
    86 102
     
    
    87
    -    await this._setSockets();
    
    103
    +    this._monitorInit();
    
    88 104
     
    
    89
    -    logger.debug("TorProtocolService initialized");
    
    90
    -  },
    
    105
    +    logger.debug("TorProvider initialized");
    
    106
    +  }
    
    91 107
     
    
    92 108
       uninit() {
    
    93
    -    Services.obs.removeObserver(this, TorTopics.ProcessExited);
    
    94
    -    Services.obs.removeObserver(this, TorTopics.ProcessRestarted);
    
    95
    -    this._closeConnection();
    
    96
    -  },
    
    109
    +    Services.obs.removeObserver(this, TorProviderTopics.ProcessExited);
    
    110
    +    Services.obs.removeObserver(this, TorProviderTopics.ProcessRestarted);
    
    111
    +    this.#closeConnection();
    
    112
    +    this._monitorUninit();
    
    113
    +  }
    
    97 114
     
    
    98 115
       observe(subject, topic, data) {
    
    99
    -    if (topic === TorTopics.ProcessExited) {
    
    100
    -      this._closeConnection();
    
    101
    -    } else if (topic === TorTopics.ProcessRestarted) {
    
    102
    -      this._reconnect();
    
    116
    +    if (topic === TorProviderTopics.ProcessExited) {
    
    117
    +      this.#closeConnection();
    
    118
    +    } else if (topic === TorProviderTopics.ProcessRestarted) {
    
    119
    +      this.#reconnect();
    
    103 120
         }
    
    104
    -  },
    
    121
    +  }
    
    105 122
     
    
    106 123
       // takes a Map containing tor settings
    
    107 124
       // throws on error
    
    ... ... @@ -109,14 +126,14 @@ export const TorProtocolService = {
    109 126
         // only write settings that have changed
    
    110 127
         const newSettings = Array.from(aSettingsObj).filter(([setting, value]) => {
    
    111 128
           // make sure we have valid data here
    
    112
    -      this._assertValidSetting(setting, value);
    
    129
    +      this.#assertValidSetting(setting, value);
    
    113 130
     
    
    114
    -      if (!this._settingsCache.has(setting)) {
    
    131
    +      if (!this.#settingsCache.has(setting)) {
    
    115 132
             // no cached setting, so write
    
    116 133
             return true;
    
    117 134
           }
    
    118 135
     
    
    119
    -      const cachedValue = this._settingsCache.get(setting);
    
    136
    +      const cachedValue = this.#settingsCache.get(setting);
    
    120 137
           if (value === cachedValue) {
    
    121 138
             return false;
    
    122 139
           } else if (Array.isArray(value) && Array.isArray(cachedValue)) {
    
    ... ... @@ -142,21 +159,21 @@ export const TorProtocolService = {
    142 159
     
    
    143 160
           // save settings to cache after successfully writing to Tor
    
    144 161
           for (const [setting, value] of newSettings) {
    
    145
    -        this._settingsCache.set(setting, value);
    
    162
    +        this.#settingsCache.set(setting, value);
    
    146 163
           }
    
    147 164
         }
    
    148
    -  },
    
    165
    +  }
    
    149 166
     
    
    150 167
       async readStringArraySetting(aSetting) {
    
    151
    -    const value = await this._readSetting(aSetting);
    
    152
    -    this._settingsCache.set(aSetting, value);
    
    168
    +    const value = await this.#readSetting(aSetting);
    
    169
    +    this.#settingsCache.set(aSetting, value);
    
    153 170
         return value;
    
    154
    -  },
    
    171
    +  }
    
    155 172
     
    
    156 173
       // writes current tor settings to disk
    
    157 174
       async flushSettings() {
    
    158 175
         await this.sendCommand("SAVECONF");
    
    159
    -  },
    
    176
    +  }
    
    160 177
     
    
    161 178
       async connect() {
    
    162 179
         const kTorConfKeyDisableNetwork = "DisableNetwork";
    
    ... ... @@ -164,9 +181,9 @@ export const TorProtocolService = {
    164 181
         settings[kTorConfKeyDisableNetwork] = false;
    
    165 182
         await this.setConfWithReply(settings);
    
    166 183
         await this.sendCommand("SAVECONF");
    
    167
    -    lazy.TorMonitorService.clearBootstrapError();
    
    168
    -    lazy.TorMonitorService.retrieveBootstrapStatus();
    
    169
    -  },
    
    184
    +    this.clearBootstrapError();
    
    185
    +    this.retrieveBootstrapStatus();
    
    186
    +  }
    
    170 187
     
    
    171 188
       async stopBootstrap() {
    
    172 189
         // Tell tor to disable use of the network; this should stop the bootstrap
    
    ... ... @@ -180,12 +197,12 @@ export const TorProtocolService = {
    180 197
         // We are not interested in waiting for this, nor in **catching its error**,
    
    181 198
         // so we do not await this. We just want to be notified when the bootstrap
    
    182 199
         // status is actually updated through observers.
    
    183
    -    lazy.TorMonitorService.retrieveBootstrapStatus();
    
    184
    -  },
    
    200
    +    this.retrieveBootstrapStatus();
    
    201
    +  }
    
    185 202
     
    
    186 203
       async newnym() {
    
    187 204
         return this.sendCommand("SIGNAL NEWNYM");
    
    188
    -  },
    
    205
    +  }
    
    189 206
     
    
    190 207
       // Ask tor which ports it is listening to for SOCKS connections.
    
    191 208
       // At the moment this is used only in TorCheckService.
    
    ... ... @@ -194,7 +211,7 @@ export const TorProtocolService = {
    194 211
         const keyword = "net/listeners/socks";
    
    195 212
         const response = await this.sendCommand(cmd, keyword);
    
    196 213
         return TorParsers.parseReply(cmd, keyword, response);
    
    197
    -  },
    
    214
    +  }
    
    198 215
     
    
    199 216
       async getBridges() {
    
    200 217
         // Ideally, we would not need this function, because we should be the one
    
    ... ... @@ -203,19 +220,19 @@ export const TorProtocolService = {
    203 220
         // is the most reliable way of getting the configured bridges, at the
    
    204 221
         // moment. Also, we are using this for the circuit display, which should
    
    205 222
         // work also when we are not configuring the tor daemon, but just using it.
    
    206
    -    return this._withConnection(conn => {
    
    223
    +    return this.#withConnection(conn => {
    
    207 224
           return conn.getConf("bridge");
    
    208 225
         });
    
    209
    -  },
    
    226
    +  }
    
    210 227
     
    
    211 228
       /**
    
    212 229
        * Returns tha data about a relay or a bridge.
    
    213 230
        *
    
    214 231
        * @param {string} id The fingerprint of the node to get data about
    
    215
    -   * @returns {NodeData}
    
    232
    +   * @returns {Promise<NodeData>}
    
    216 233
        */
    
    217 234
       async getNodeInfo(id) {
    
    218
    -    return this._withConnection(async conn => {
    
    235
    +    return this.#withConnection(async conn => {
    
    219 236
           const node = {
    
    220 237
             fingerprint: id,
    
    221 238
             ipAddrs: [],
    
    ... ... @@ -259,62 +276,62 @@ export const TorProtocolService = {
    259 276
           }
    
    260 277
           return node;
    
    261 278
         });
    
    262
    -  },
    
    279
    +  }
    
    263 280
     
    
    264 281
       async onionAuthAdd(hsAddress, b64PrivateKey, isPermanent) {
    
    265
    -    return this._withConnection(conn => {
    
    282
    +    return this.#withConnection(conn => {
    
    266 283
           return conn.onionAuthAdd(hsAddress, b64PrivateKey, isPermanent);
    
    267 284
         });
    
    268
    -  },
    
    285
    +  }
    
    269 286
     
    
    270 287
       async onionAuthRemove(hsAddress) {
    
    271
    -    return this._withConnection(conn => {
    
    288
    +    return this.#withConnection(conn => {
    
    272 289
           return conn.onionAuthRemove(hsAddress);
    
    273 290
         });
    
    274
    -  },
    
    291
    +  }
    
    275 292
     
    
    276 293
       async onionAuthViewKeys() {
    
    277
    -    return this._withConnection(conn => {
    
    294
    +    return this.#withConnection(conn => {
    
    278 295
           return conn.onionAuthViewKeys();
    
    279 296
         });
    
    280
    -  },
    
    297
    +  }
    
    281 298
     
    
    282 299
       // TODO: transform the following 4 functions in getters.
    
    283 300
     
    
    284 301
       // Returns Tor password string or null if an error occurs.
    
    285 302
       torGetPassword() {
    
    286
    -    return this._controlPassword;
    
    287
    -  },
    
    303
    +    return this.#controlPassword;
    
    304
    +  }
    
    288 305
     
    
    289 306
       torGetControlIPCFile() {
    
    290
    -    return this._controlIPCFile?.clone();
    
    291
    -  },
    
    307
    +    return this.#controlIPCFile?.clone();
    
    308
    +  }
    
    292 309
     
    
    293 310
       torGetControlPort() {
    
    294
    -    return this._controlPort;
    
    295
    -  },
    
    311
    +    return this.#controlPort;
    
    312
    +  }
    
    296 313
     
    
    297 314
       torGetSOCKSPortInfo() {
    
    298
    -    return this._SOCKSPortInfo;
    
    299
    -  },
    
    315
    +    return this.#SOCKSPortInfo;
    
    316
    +  }
    
    300 317
     
    
    301 318
       get torControlPortInfo() {
    
    302 319
         const info = {
    
    303
    -      password: this._controlPassword,
    
    320
    +      password: this.#controlPassword,
    
    304 321
         };
    
    305
    -    if (this._controlIPCFile) {
    
    306
    -      info.ipcFile = this._controlIPCFile?.clone();
    
    322
    +    if (this.#controlIPCFile) {
    
    323
    +      info.ipcFile = this.#controlIPCFile?.clone();
    
    307 324
         }
    
    308
    -    if (this._controlPort) {
    
    309
    -      info.host = this._controlHost;
    
    310
    -      info.port = this._controlPort;
    
    325
    +    if (this.#controlPort) {
    
    326
    +      info.host = this.#controlHost;
    
    327
    +      info.port = this.#controlPort;
    
    311 328
         }
    
    312 329
         return info;
    
    313
    -  },
    
    330
    +  }
    
    314 331
     
    
    315 332
       get torSOCKSPortInfo() {
    
    316
    -    return this._SOCKSPortInfo;
    
    317
    -  },
    
    333
    +    return this.#SOCKSPortInfo;
    
    334
    +  }
    
    318 335
     
    
    319 336
       // Public, but called only internally
    
    320 337
     
    
    ... ... @@ -326,7 +343,7 @@ export const TorProtocolService = {
    326 343
         let timeout = 250;
    
    327 344
         let reply;
    
    328 345
         while (leftConnAttempts-- > 0) {
    
    329
    -      const response = await this._trySend(cmd, args, leftConnAttempts == 0);
    
    346
    +      const response = await this.#trySend(cmd, args, leftConnAttempts === 0);
    
    330 347
           if (response.connected) {
    
    331 348
             reply = response.reply;
    
    332 349
             break;
    
    ... ... @@ -360,7 +377,7 @@ export const TorProtocolService = {
    360 377
         }
    
    361 378
     
    
    362 379
         return reply;
    
    363
    -  },
    
    380
    +  }
    
    364 381
     
    
    365 382
       // Perform a SETCONF command.
    
    366 383
       // aSettingsObj should be a _javascript_ object with keys (property values)
    
    ... ... @@ -398,39 +415,39 @@ export const TorProtocolService = {
    398 415
         }
    
    399 416
     
    
    400 417
         await this.sendCommand("SETCONF", args.join(" "));
    
    401
    -  },
    
    418
    +  }
    
    402 419
     
    
    403 420
       // Public, never called?
    
    404 421
     
    
    405 422
       async readBoolSetting(aSetting) {
    
    406
    -    let value = await this._readBoolSetting(aSetting);
    
    407
    -    this._settingsCache.set(aSetting, value);
    
    423
    +    let value = await this.#readBoolSetting(aSetting);
    
    424
    +    this.#settingsCache.set(aSetting, value);
    
    408 425
         return value;
    
    409
    -  },
    
    426
    +  }
    
    410 427
     
    
    411 428
       async readStringSetting(aSetting) {
    
    412
    -    let value = await this._readStringSetting(aSetting);
    
    413
    -    this._settingsCache.set(aSetting, value);
    
    429
    +    let value = await this.#readStringSetting(aSetting);
    
    430
    +    this.#settingsCache.set(aSetting, value);
    
    414 431
         return value;
    
    415
    -  },
    
    432
    +  }
    
    416 433
     
    
    417 434
       // Private
    
    418 435
     
    
    419
    -  async _setSockets() {
    
    436
    +  async #setSockets() {
    
    420 437
         try {
    
    421 438
           const isWindows = TorLauncherUtil.isWindows;
    
    422 439
           // Determine how Tor Launcher will connect to the Tor control port.
    
    423 440
           // Environment variables get top priority followed by preferences.
    
    424 441
           if (!isWindows && Services.env.exists("TOR_CONTROL_IPC_PATH")) {
    
    425 442
             const ipcPath = Services.env.get("TOR_CONTROL_IPC_PATH");
    
    426
    -        this._controlIPCFile = new lazy.FileUtils.File(ipcPath);
    
    443
    +        this.#controlIPCFile = new lazy.FileUtils.File(ipcPath);
    
    427 444
           } else {
    
    428 445
             // Check for TCP host and port environment variables.
    
    429 446
             if (Services.env.exists("TOR_CONTROL_HOST")) {
    
    430
    -          this._controlHost = Services.env.get("TOR_CONTROL_HOST");
    
    447
    +          this.#controlHost = Services.env.get("TOR_CONTROL_HOST");
    
    431 448
             }
    
    432 449
             if (Services.env.exists("TOR_CONTROL_PORT")) {
    
    433
    -          this._controlPort = parseInt(
    
    450
    +          this.#controlPort = parseInt(
    
    434 451
                 Services.env.get("TOR_CONTROL_PORT"),
    
    435 452
                 10
    
    436 453
               );
    
    ... ... @@ -442,20 +459,20 @@ export const TorProtocolService = {
    442 459
                 "extensions.torlauncher.control_port_use_ipc",
    
    443 460
                 false
    
    444 461
               );
    
    445
    -        if (!this._controlHost && !this._controlPort && useIPC) {
    
    446
    -          this._controlIPCFile = TorLauncherUtil.getTorFile(
    
    462
    +        if (!this.#controlHost && !this.#controlPort && useIPC) {
    
    463
    +          this.#controlIPCFile = TorLauncherUtil.getTorFile(
    
    447 464
                 "control_ipc",
    
    448 465
                 false
    
    449 466
               );
    
    450 467
             } else {
    
    451
    -          if (!this._controlHost) {
    
    452
    -            this._controlHost = Services.prefs.getCharPref(
    
    468
    +          if (!this.#controlHost) {
    
    469
    +            this.#controlHost = Services.prefs.getCharPref(
    
    453 470
                   "extensions.torlauncher.control_host",
    
    454 471
                   "127.0.0.1"
    
    455 472
                 );
    
    456 473
               }
    
    457
    -          if (!this._controlPort) {
    
    458
    -            this._controlPort = Services.prefs.getIntPref(
    
    474
    +          if (!this.#controlPort) {
    
    475
    +            this.#controlPort = Services.prefs.getIntPref(
    
    459 476
                   "extensions.torlauncher.control_port",
    
    460 477
                   9151
    
    461 478
                 );
    
    ... ... @@ -465,46 +482,46 @@ export const TorProtocolService = {
    465 482
     
    
    466 483
           // Populate _controlPassword so it is available when starting tor.
    
    467 484
           if (Services.env.exists("TOR_CONTROL_PASSWD")) {
    
    468
    -        this._controlPassword = Services.env.get("TOR_CONTROL_PASSWD");
    
    485
    +        this.#controlPassword = Services.env.get("TOR_CONTROL_PASSWD");
    
    469 486
           } else if (Services.env.exists("TOR_CONTROL_COOKIE_AUTH_FILE")) {
    
    470 487
             // TODO: test this code path (TOR_CONTROL_COOKIE_AUTH_FILE).
    
    471 488
             const cookiePath = Services.env.get("TOR_CONTROL_COOKIE_AUTH_FILE");
    
    472 489
             if (cookiePath) {
    
    473
    -          this._controlPassword = await this._readAuthenticationCookie(
    
    490
    +          this.#controlPassword = await this.#readAuthenticationCookie(
    
    474 491
                 cookiePath
    
    475 492
               );
    
    476 493
             }
    
    477 494
           }
    
    478
    -      if (!this._controlPassword) {
    
    479
    -        this._controlPassword = this._generateRandomPassword();
    
    495
    +      if (!this.#controlPassword) {
    
    496
    +        this.#controlPassword = this.#generateRandomPassword();
    
    480 497
           }
    
    481 498
     
    
    482
    -      this._SOCKSPortInfo = TorLauncherUtil.getPreferredSocksConfiguration();
    
    483
    -      TorLauncherUtil.setProxyConfiguration(this._SOCKSPortInfo);
    
    499
    +      this.#SOCKSPortInfo = TorLauncherUtil.getPreferredSocksConfiguration();
    
    500
    +      TorLauncherUtil.setProxyConfiguration(this.#SOCKSPortInfo);
    
    484 501
     
    
    485 502
           // Set the global control port info parameters.
    
    486 503
           lazy.configureControlPortModule(
    
    487
    -        this._controlIPCFile,
    
    488
    -        this._controlHost,
    
    489
    -        this._controlPort,
    
    490
    -        this._controlPassword
    
    504
    +        this.#controlIPCFile,
    
    505
    +        this.#controlHost,
    
    506
    +        this.#controlPort,
    
    507
    +        this.#controlPassword
    
    491 508
           );
    
    492 509
         } catch (e) {
    
    493 510
           logger.error("Failed to get environment variables", e);
    
    494 511
         }
    
    495
    -  },
    
    512
    +  }
    
    496 513
     
    
    497
    -  _assertValidSettingKey(aSetting) {
    
    514
    +  #assertValidSettingKey(aSetting) {
    
    498 515
         // ensure the 'key' is a string
    
    499 516
         if (typeof aSetting !== "string") {
    
    500 517
           throw new Error(
    
    501 518
             `Expected setting of type string but received ${typeof aSetting}`
    
    502 519
           );
    
    503 520
         }
    
    504
    -  },
    
    521
    +  }
    
    505 522
     
    
    506
    -  _assertValidSetting(aSetting, aValue) {
    
    507
    -    this._assertValidSettingKey(aSetting);
    
    523
    +  #assertValidSetting(aSetting, aValue) {
    
    524
    +    this.#assertValidSettingKey(aSetting);
    
    508 525
         switch (typeof aValue) {
    
    509 526
           case "boolean":
    
    510 527
           case "string":
    
    ... ... @@ -528,29 +545,29 @@ export const TorProtocolService = {
    528 545
               `Invalid object type received for setting '${aSetting}'`
    
    529 546
             );
    
    530 547
         }
    
    531
    -  },
    
    548
    +  }
    
    532 549
     
    
    533 550
       // Perform a GETCONF command.
    
    534
    -  async _readSetting(aSetting) {
    
    535
    -    this._assertValidSettingKey(aSetting);
    
    551
    +  async #readSetting(aSetting) {
    
    552
    +    this.#assertValidSettingKey(aSetting);
    
    536 553
     
    
    537 554
         const cmd = "GETCONF";
    
    538 555
         let reply = await this.sendCommand(cmd, aSetting);
    
    539 556
         return TorParsers.parseReply(cmd, aSetting, reply);
    
    540
    -  },
    
    557
    +  }
    
    541 558
     
    
    542
    -  async _readStringSetting(aSetting) {
    
    543
    -    let lineArray = await this._readSetting(aSetting);
    
    559
    +  async #readStringSetting(aSetting) {
    
    560
    +    let lineArray = await this.#readSetting(aSetting);
    
    544 561
         if (lineArray.length !== 1) {
    
    545 562
           throw new Error(
    
    546 563
             `Expected an array with length 1 but received array of length ${lineArray.length}`
    
    547 564
           );
    
    548 565
         }
    
    549 566
         return lineArray[0];
    
    550
    -  },
    
    567
    +  }
    
    551 568
     
    
    552
    -  async _readBoolSetting(aSetting) {
    
    553
    -    const value = this._readStringSetting(aSetting);
    
    569
    +  async #readBoolSetting(aSetting) {
    
    570
    +    const value = this.#readStringSetting(aSetting);
    
    554 571
         switch (value) {
    
    555 572
           case "0":
    
    556 573
             return false;
    
    ... ... @@ -559,16 +576,16 @@ export const TorProtocolService = {
    559 576
           default:
    
    560 577
             throw new Error(`Expected boolean (1 or 0) but received '${value}'`);
    
    561 578
         }
    
    562
    -  },
    
    579
    +  }
    
    563 580
     
    
    564
    -  async _trySend(cmd, args, rethrow) {
    
    581
    +  async #trySend(cmd, args, rethrow) {
    
    565 582
         let connected = false;
    
    566 583
         let reply;
    
    567 584
         let leftAttempts = 2;
    
    568 585
         while (leftAttempts-- > 0) {
    
    569 586
           let conn;
    
    570 587
           try {
    
    571
    -        conn = await this._getConnection();
    
    588
    +        conn = await this.#getConnection();
    
    572 589
           } catch (e) {
    
    573 590
             logger.error("Cannot get a connection to the control port", e);
    
    574 591
             if (leftAttempts == 0 && rethrow) {
    
    ... ... @@ -584,105 +601,105 @@ export const TorProtocolService = {
    584 601
             reply = await conn.sendCommand(cmd + (args ? " " + args : ""));
    
    585 602
             if (reply) {
    
    586 603
               // Return for reuse.
    
    587
    -          this._returnConnection();
    
    604
    +          this.#returnConnection();
    
    588 605
             } else {
    
    589 606
               // Connection is bad.
    
    590 607
               logger.warn(
    
    591 608
                 "sendCommand returned an empty response, taking the connection as broken and closing it."
    
    592 609
               );
    
    593
    -          this._closeConnection();
    
    610
    +          this.#closeConnection();
    
    594 611
             }
    
    595 612
           } catch (e) {
    
    596 613
             logger.error(`Cannot send the command ${cmd}`, e);
    
    597
    -        this._closeConnection();
    
    614
    +        this.#closeConnection();
    
    598 615
             if (leftAttempts == 0 && rethrow) {
    
    599 616
               throw e;
    
    600 617
             }
    
    601 618
           }
    
    602 619
         }
    
    603 620
         return { connected, reply };
    
    604
    -  },
    
    621
    +  }
    
    605 622
     
    
    606
    -  // Opens an authenticated connection, sets it to this._controlConnection, and
    
    623
    +  // Opens an authenticated connection, sets it to this.#controlConnection, and
    
    607 624
       // return it.
    
    608
    -  async _getConnection() {
    
    609
    -    if (!this._controlConnection) {
    
    610
    -      this._controlConnection = await lazy.controller();
    
    625
    +  async #getConnection() {
    
    626
    +    if (!this.#controlConnection) {
    
    627
    +      this.#controlConnection = await lazy.controller();
    
    611 628
         }
    
    612
    -    if (this._controlConnection.inUse) {
    
    629
    +    if (this.#controlConnection.inUse) {
    
    613 630
           await new Promise((resolve, reject) =>
    
    614
    -        this._connectionQueue.push({ resolve, reject })
    
    631
    +        this.#connectionQueue.push({ resolve, reject })
    
    615 632
           );
    
    616 633
         } else {
    
    617
    -      this._controlConnection.inUse = true;
    
    634
    +      this.#controlConnection.inUse = true;
    
    618 635
         }
    
    619
    -    return this._controlConnection;
    
    620
    -  },
    
    636
    +    return this.#controlConnection;
    
    637
    +  }
    
    621 638
     
    
    622
    -  _returnConnection() {
    
    623
    -    if (this._connectionQueue.length) {
    
    624
    -      this._connectionQueue.shift().resolve();
    
    639
    +  #returnConnection() {
    
    640
    +    if (this.#connectionQueue.length) {
    
    641
    +      this.#connectionQueue.shift().resolve();
    
    625 642
         } else {
    
    626
    -      this._controlConnection.inUse = false;
    
    643
    +      this.#controlConnection.inUse = false;
    
    627 644
         }
    
    628
    -  },
    
    645
    +  }
    
    629 646
     
    
    630
    -  async _withConnection(func) {
    
    647
    +  async #withConnection(func) {
    
    631 648
         // TODO: Make more robust?
    
    632
    -    const conn = await this._getConnection();
    
    649
    +    const conn = await this.#getConnection();
    
    633 650
         try {
    
    634 651
           return await func(conn);
    
    635 652
         } finally {
    
    636
    -      this._returnConnection();
    
    653
    +      this.#returnConnection();
    
    637 654
         }
    
    638
    -  },
    
    655
    +  }
    
    639 656
     
    
    640 657
       // If aConn is omitted, the cached connection is closed.
    
    641
    -  _closeConnection() {
    
    642
    -    if (this._controlConnection) {
    
    658
    +  #closeConnection() {
    
    659
    +    if (this.#controlConnection) {
    
    643 660
           logger.info("Closing the control connection");
    
    644
    -      this._controlConnection.close();
    
    645
    -      this._controlConnection = null;
    
    661
    +      this.#controlConnection.close();
    
    662
    +      this.#controlConnection = null;
    
    646 663
         }
    
    647
    -    for (const promise of this._connectionQueue) {
    
    664
    +    for (const promise of this.#connectionQueue) {
    
    648 665
           promise.reject("Connection closed");
    
    649 666
         }
    
    650
    -    this._connectionQueue = [];
    
    651
    -  },
    
    667
    +    this.#connectionQueue = [];
    
    668
    +  }
    
    652 669
     
    
    653
    -  async _reconnect() {
    
    654
    -    this._closeConnection();
    
    655
    -    const conn = await this._getConnection();
    
    670
    +  async #reconnect() {
    
    671
    +    this.#closeConnection();
    
    672
    +    const conn = await this.#getConnection();
    
    656 673
         logger.debug("Reconnected to the control port.");
    
    657
    -    this._returnConnection(conn);
    
    658
    -  },
    
    674
    +    this.#returnConnection(conn);
    
    675
    +  }
    
    659 676
     
    
    660
    -  async _readAuthenticationCookie(aPath) {
    
    677
    +  async #readAuthenticationCookie(aPath) {
    
    661 678
         const bytes = await IOUtils.read(aPath);
    
    662
    -    return Array.from(bytes, b => this._toHex(b, 2)).join("");
    
    663
    -  },
    
    679
    +    return Array.from(bytes, b => this.#toHex(b, 2)).join("");
    
    680
    +  }
    
    664 681
     
    
    665 682
       // Returns a random 16 character password, hex-encoded.
    
    666
    -  _generateRandomPassword() {
    
    683
    +  #generateRandomPassword() {
    
    667 684
         // Similar to Vidalia's crypto_rand_string().
    
    668 685
         const kPasswordLen = 16;
    
    669 686
         const kMinCharCode = "!".charCodeAt(0);
    
    670 687
         const kMaxCharCode = "~".charCodeAt(0);
    
    671 688
         let pwd = "";
    
    672 689
         for (let i = 0; i < kPasswordLen; ++i) {
    
    673
    -      const val = this._cryptoRandInt(kMaxCharCode - kMinCharCode + 1);
    
    690
    +      const val = this.#cryptoRandInt(kMaxCharCode - kMinCharCode + 1);
    
    674 691
           if (val < 0) {
    
    675 692
             logger.error("_cryptoRandInt() failed");
    
    676 693
             return null;
    
    677 694
           }
    
    678
    -      pwd += this._toHex(kMinCharCode + val, 2);
    
    695
    +      pwd += this.#toHex(kMinCharCode + val, 2);
    
    679 696
         }
    
    680 697
     
    
    681 698
         return pwd;
    
    682
    -  },
    
    699
    +  }
    
    683 700
     
    
    684 701
       // Returns -1 upon failure.
    
    685
    -  _cryptoRandInt(aMax) {
    
    702
    +  #cryptoRandInt(aMax) {
    
    686 703
         // Based on tor's crypto_rand_int().
    
    687 704
         const maxUInt = 0xffffffff;
    
    688 705
         if (aMax <= 0 || aMax > maxUInt) {
    
    ... ... @@ -697,9 +714,588 @@ export const TorProtocolService = {
    697 714
           val = uint32[0];
    
    698 715
         }
    
    699 716
         return val % aMax;
    
    700
    -  },
    
    717
    +  }
    
    701 718
     
    
    702
    -  _toHex(aValue, aMinLen) {
    
    719
    +  #toHex(aValue, aMinLen) {
    
    703 720
         return aValue.toString(16).padStart(aMinLen, "0");
    
    704
    -  },
    
    705
    -};
    721
    +  }
    
    722
    +
    
    723
    +  // Former TorMonitorService implementation.
    
    724
    +  // FIXME: Refactor and integrate more with the rest of the class.
    
    725
    +
    
    726
    +  _connection = null;
    
    727
    +  _eventHandlers = {};
    
    728
    +  _torLog = []; // Array of objects with date, type, and msg properties
    
    729
    +  _startTimeout = null;
    
    730
    +
    
    731
    +  _isBootstrapDone = false;
    
    732
    +  _lastWarningPhase = null;
    
    733
    +  _lastWarningReason = null;
    
    734
    +
    
    735
    +  _torProcess = null;
    
    736
    +
    
    737
    +  _inited = false;
    
    738
    +
    
    739
    +  /**
    
    740
    +   * Stores the nodes of a circuit. Keys are cicuit IDs, and values are the node
    
    741
    +   * fingerprints.
    
    742
    +   *
    
    743
    +   * Theoretically, we could hook this map up to the new identity notification,
    
    744
    +   * but in practice it does not work. Tor pre-builds circuits, and the NEWNYM
    
    745
    +   * signal does not affect them. So, we might end up using a circuit that was
    
    746
    +   * built before the new identity but not yet used. If we cleaned the map, we
    
    747
    +   * risked of not having the data about it.
    
    748
    +   *
    
    749
    +   * @type {Map<CircuitID, NodeFingerprint[]>}
    
    750
    +   */
    
    751
    +  _circuits = new Map();
    
    752
    +  /**
    
    753
    +   * The last used bridge, or null if bridges are not in use or if it was not
    
    754
    +   * possible to detect the bridge. This needs the user to have specified bridge
    
    755
    +   * lines with fingerprints to work.
    
    756
    +   *
    
    757
    +   * @type {NodeFingerprint?}
    
    758
    +   */
    
    759
    +  _currentBridge = null;
    
    760
    +
    
    761
    +  // Public methods
    
    762
    +
    
    763
    +  // Starts Tor, if needed, and starts monitoring for events
    
    764
    +  _monitorInit() {
    
    765
    +    if (this._inited) {
    
    766
    +      return;
    
    767
    +    }
    
    768
    +    this._inited = true;
    
    769
    +
    
    770
    +    // We always liten to these events, because they are needed for the circuit
    
    771
    +    // display.
    
    772
    +    this._eventHandlers = new Map([
    
    773
    +      ["CIRC", this._processCircEvent.bind(this)],
    
    774
    +      ["STREAM", this._processStreamEvent.bind(this)],
    
    775
    +    ]);
    
    776
    +
    
    777
    +    if (this.ownsTorDaemon) {
    
    778
    +      // When we own the tor daemon, we listen to more events, that are used
    
    779
    +      // for about:torconnect or for showing the logs in the settings page.
    
    780
    +      this._eventHandlers.set("STATUS_CLIENT", (_eventType, lines) =>
    
    781
    +        this._processBootstrapStatus(lines[0], false)
    
    782
    +      );
    
    783
    +      this._eventHandlers.set("NOTICE", this._processLog.bind(this));
    
    784
    +      this._eventHandlers.set("WARN", this._processLog.bind(this));
    
    785
    +      this._eventHandlers.set("ERR", this._processLog.bind(this));
    
    786
    +      this._controlTor();
    
    787
    +    } else {
    
    788
    +      this._startEventMonitor();
    
    789
    +    }
    
    790
    +    logger.info("TorMonitorService initialized");
    
    791
    +  }
    
    792
    +
    
    793
    +  // Closes the connection that monitors for events.
    
    794
    +  // When Tor is started by Tor Browser, it is configured to exit when the
    
    795
    +  // control connection is closed. Therefore, as a matter of facts, calling this
    
    796
    +  // function also makes the child Tor instance stop.
    
    797
    +  _monitorUninit() {
    
    798
    +    if (this._torProcess) {
    
    799
    +      this._torProcess.forget();
    
    800
    +      this._torProcess.onExit = null;
    
    801
    +      this._torProcess.onRestart = null;
    
    802
    +      this._torProcess = null;
    
    803
    +    }
    
    804
    +    this._shutDownEventMonitor();
    
    805
    +  }
    
    806
    +
    
    807
    +  async retrieveBootstrapStatus() {
    
    808
    +    if (!this._connection) {
    
    809
    +      throw new Error("Event monitor connection not available");
    
    810
    +    }
    
    811
    +
    
    812
    +    // TODO: Unify with TorProtocolService.sendCommand and put everything in the
    
    813
    +    // reviewed torbutton replacement.
    
    814
    +    const cmd = "GETINFO";
    
    815
    +    const key = "status/bootstrap-phase";
    
    816
    +    let reply = await this._connection.sendCommand(`${cmd} ${key}`);
    
    817
    +
    
    818
    +    // A typical reply looks like:
    
    819
    +    //  250-status/bootstrap-phase=NOTICE BOOTSTRAP PROGRESS=100 TAG=done SUMMARY="Done"
    
    820
    +    //  250 OK
    
    821
    +    reply = TorParsers.parseCommandResponse(reply);
    
    822
    +    if (!TorParsers.commandSucceeded(reply)) {
    
    823
    +      throw new Error(`${cmd} failed`);
    
    824
    +    }
    
    825
    +    reply = TorParsers.parseReply(cmd, key, reply);
    
    826
    +    if (reply.length) {
    
    827
    +      this._processBootstrapStatus(reply[0], true);
    
    828
    +    }
    
    829
    +  }
    
    830
    +
    
    831
    +  // Returns captured log message as a text string (one message per line).
    
    832
    +  getLog() {
    
    833
    +    return this._torLog
    
    834
    +      .map(logObj => {
    
    835
    +        const timeStr = logObj.date
    
    836
    +          .toISOString()
    
    837
    +          .replace("T", " ")
    
    838
    +          .replace("Z", "");
    
    839
    +        return `${timeStr} [${logObj.type}] ${logObj.msg}`;
    
    840
    +      })
    
    841
    +      .join(TorLauncherUtil.isWindows ? "\r\n" : "\n");
    
    842
    +  }
    
    843
    +
    
    844
    +  // true if we launched and control tor, false if using system tor
    
    845
    +  get ownsTorDaemon() {
    
    846
    +    return TorLauncherUtil.shouldStartAndOwnTor;
    
    847
    +  }
    
    848
    +
    
    849
    +  get isBootstrapDone() {
    
    850
    +    return this._isBootstrapDone;
    
    851
    +  }
    
    852
    +
    
    853
    +  clearBootstrapError() {
    
    854
    +    this._lastWarningPhase = null;
    
    855
    +    this._lastWarningReason = null;
    
    856
    +  }
    
    857
    +
    
    858
    +  get isRunning() {
    
    859
    +    return !!this._connection;
    
    860
    +  }
    
    861
    +
    
    862
    +  /**
    
    863
    +   * Return the data about the current bridge, if any, or null.
    
    864
    +   * We can detect bridge only when the configured bridge lines include the
    
    865
    +   * fingerprints.
    
    866
    +   *
    
    867
    +   * @returns {NodeData?} The node information, or null if the first node
    
    868
    +   * is not a bridge, or no circuit has been opened, yet.
    
    869
    +   */
    
    870
    +  get currentBridge() {
    
    871
    +    return this._currentBridge;
    
    872
    +  }
    
    873
    +
    
    874
    +  // Private methods
    
    875
    +
    
    876
    +  async _startProcess() {
    
    877
    +    // TorProcess should be instanced once, then always reused and restarted
    
    878
    +    // only through the prompt it exposes when the controlled process dies.
    
    879
    +    if (!this._torProcess) {
    
    880
    +      this._torProcess = new lazy.TorProcess(
    
    881
    +        this.torControlPortInfo,
    
    882
    +        this.torSOCKSPortInfo
    
    883
    +      );
    
    884
    +      this._torProcess.onExit = () => {
    
    885
    +        this._shutDownEventMonitor();
    
    886
    +        Services.obs.notifyObservers(null, TorProviderTopics.ProcessExited);
    
    887
    +      };
    
    888
    +      this._torProcess.onRestart = async () => {
    
    889
    +        this._shutDownEventMonitor();
    
    890
    +        await this._controlTor();
    
    891
    +        Services.obs.notifyObservers(null, TorProviderTopics.ProcessRestarted);
    
    892
    +      };
    
    893
    +    }
    
    894
    +
    
    895
    +    // Already running, but we did not start it
    
    896
    +    if (this._torProcess.isRunning) {
    
    897
    +      return false;
    
    898
    +    }
    
    899
    +
    
    900
    +    try {
    
    901
    +      await this._torProcess.start();
    
    902
    +      if (this._torProcess.isRunning) {
    
    903
    +        logger.info("tor started");
    
    904
    +        this._torProcessStartTime = Date.now();
    
    905
    +      }
    
    906
    +    } catch (e) {
    
    907
    +      // TorProcess already logs the error.
    
    908
    +      this._lastWarningPhase = "startup";
    
    909
    +      this._lastWarningReason = e.toString();
    
    910
    +    }
    
    911
    +    return this._torProcess.isRunning;
    
    912
    +  }
    
    913
    +
    
    914
    +  async _controlTor() {
    
    915
    +    if (!this._torProcess?.isRunning && !(await this._startProcess())) {
    
    916
    +      logger.error("Tor not running, not starting to monitor it.");
    
    917
    +      return;
    
    918
    +    }
    
    919
    +
    
    920
    +    let delayMS = ControlConnTimings.initialDelayMS;
    
    921
    +    const callback = async () => {
    
    922
    +      if (await this._startEventMonitor()) {
    
    923
    +        this.retrieveBootstrapStatus().catch(e => {
    
    924
    +          logger.warn("Could not get the initial bootstrap status", e);
    
    925
    +        });
    
    926
    +
    
    927
    +        // FIXME: TorProcess is misleading here. We should use a topic related
    
    928
    +        // to having a control port connection, instead.
    
    929
    +        logger.info(`Notifying ${TorProviderTopics.ProcessIsReady}`);
    
    930
    +        Services.obs.notifyObservers(null, TorProviderTopics.ProcessIsReady);
    
    931
    +
    
    932
    +        // We reset this here hoping that _shutDownEventMonitor can interrupt
    
    933
    +        // the current monitor, either by calling clearTimeout and preventing it
    
    934
    +        // from starting, or by closing the control port connection.
    
    935
    +        if (this._startTimeout === null) {
    
    936
    +          logger.warn("Someone else reset _startTimeout!");
    
    937
    +        }
    
    938
    +        this._startTimeout = null;
    
    939
    +      } else if (
    
    940
    +        Date.now() - this._torProcessStartTime >
    
    941
    +        ControlConnTimings.timeoutMS
    
    942
    +      ) {
    
    943
    +        let s = TorLauncherUtil.getLocalizedString("tor_controlconn_failed");
    
    944
    +        this._lastWarningPhase = "startup";
    
    945
    +        this._lastWarningReason = s;
    
    946
    +        logger.info(s);
    
    947
    +        if (this._startTimeout === null) {
    
    948
    +          logger.warn("Someone else reset _startTimeout!");
    
    949
    +        }
    
    950
    +        this._startTimeout = null;
    
    951
    +      } else {
    
    952
    +        delayMS *= 2;
    
    953
    +        if (delayMS > ControlConnTimings.maxRetryMS) {
    
    954
    +          delayMS = ControlConnTimings.maxRetryMS;
    
    955
    +        }
    
    956
    +        this._startTimeout = setTimeout(() => {
    
    957
    +          logger.debug(`Control port not ready, waiting ${delayMS / 1000}s.`);
    
    958
    +          callback();
    
    959
    +        }, delayMS);
    
    960
    +      }
    
    961
    +    };
    
    962
    +    // Check again, in the unfortunate case in which the execution was alrady
    
    963
    +    // queued, but was waiting network code.
    
    964
    +    if (this._startTimeout === null) {
    
    965
    +      this._startTimeout = setTimeout(callback, delayMS);
    
    966
    +    } else {
    
    967
    +      logger.error("Possible race? Refusing to start the timeout again");
    
    968
    +    }
    
    969
    +  }
    
    970
    +
    
    971
    +  async _startEventMonitor() {
    
    972
    +    if (this._connection) {
    
    973
    +      return true;
    
    974
    +    }
    
    975
    +
    
    976
    +    let conn;
    
    977
    +    try {
    
    978
    +      conn = await lazy.controller();
    
    979
    +    } catch (e) {
    
    980
    +      logger.error("Cannot open a control port connection", e);
    
    981
    +      if (conn) {
    
    982
    +        try {
    
    983
    +          conn.close();
    
    984
    +        } catch (e) {
    
    985
    +          logger.error(
    
    986
    +            "Also, the connection is not null but cannot be closed",
    
    987
    +            e
    
    988
    +          );
    
    989
    +        }
    
    990
    +      }
    
    991
    +      return false;
    
    992
    +    }
    
    993
    +
    
    994
    +    // TODO: optionally monitor INFO and DEBUG log messages.
    
    995
    +    try {
    
    996
    +      await conn.setEvents(Array.from(this._eventHandlers.keys()));
    
    997
    +    } catch (e) {
    
    998
    +      logger.error("SETEVENTS failed", e);
    
    999
    +      conn.close();
    
    1000
    +      return false;
    
    1001
    +    }
    
    1002
    +
    
    1003
    +    if (this._torProcess) {
    
    1004
    +      this._torProcess.connectionWorked();
    
    1005
    +    }
    
    1006
    +    if (this.ownsTorDaemon && !TorLauncherUtil.shouldOnlyConfigureTor) {
    
    1007
    +      try {
    
    1008
    +        await this._takeTorOwnership(conn);
    
    1009
    +      } catch (e) {
    
    1010
    +        logger.warn("Could not take ownership of the Tor daemon", e);
    
    1011
    +      }
    
    1012
    +    }
    
    1013
    +
    
    1014
    +    this._connection = conn;
    
    1015
    +
    
    1016
    +    for (const [type, callback] of this._eventHandlers.entries()) {
    
    1017
    +      this._monitorEvent(type, callback);
    
    1018
    +    }
    
    1019
    +
    
    1020
    +    // Populate the circuit map already, in case we are connecting to an
    
    1021
    +    // external tor daemon.
    
    1022
    +    try {
    
    1023
    +      const reply = await this._connection.sendCommand(
    
    1024
    +        "GETINFO circuit-status"
    
    1025
    +      );
    
    1026
    +      const lines = reply.split(/\r?\n/);
    
    1027
    +      if (lines.shift() === "250+circuit-status=") {
    
    1028
    +        for (const line of lines) {
    
    1029
    +          if (line === ".") {
    
    1030
    +            break;
    
    1031
    +          }
    
    1032
    +          // _processCircEvent processes only one line at a time
    
    1033
    +          this._processCircEvent("CIRC", [line]);
    
    1034
    +        }
    
    1035
    +      }
    
    1036
    +    } catch (e) {
    
    1037
    +      logger.warn("Could not populate the initial circuit map", e);
    
    1038
    +    }
    
    1039
    +
    
    1040
    +    return true;
    
    1041
    +  }
    
    1042
    +
    
    1043
    +  // Try to become the primary controller (TAKEOWNERSHIP).
    
    1044
    +  async _takeTorOwnership(conn) {
    
    1045
    +    try {
    
    1046
    +      conn.takeOwnership();
    
    1047
    +    } catch (e) {
    
    1048
    +      logger.warn("Take ownership failed", e);
    
    1049
    +      return;
    
    1050
    +    }
    
    1051
    +    try {
    
    1052
    +      conn.resetOwningControllerProcess();
    
    1053
    +    } catch (e) {
    
    1054
    +      logger.warn("Clear owning controller process failed", e);
    
    1055
    +    }
    
    1056
    +  }
    
    1057
    +
    
    1058
    +  _monitorEvent(type, callback) {
    
    1059
    +    logger.info(`Watching events of type ${type}.`);
    
    1060
    +    let replyObj = {};
    
    1061
    +    this._connection.watchEvent(
    
    1062
    +      type,
    
    1063
    +      null,
    
    1064
    +      line => {
    
    1065
    +        if (!line) {
    
    1066
    +          return;
    
    1067
    +        }
    
    1068
    +        logger.debug("Event response: ", line);
    
    1069
    +        const isComplete = TorParsers.parseReplyLine(line, replyObj);
    
    1070
    +        if (!isComplete || replyObj._parseError || !replyObj.lineArray.length) {
    
    1071
    +          return;
    
    1072
    +        }
    
    1073
    +        const reply = replyObj;
    
    1074
    +        replyObj = {};
    
    1075
    +        if (reply.statusCode !== TorStatuses.EventNotification) {
    
    1076
    +          logger.error("Unexpected event status code:", reply.statusCode);
    
    1077
    +          return;
    
    1078
    +        }
    
    1079
    +        if (!reply.lineArray[0].startsWith(`${type} `)) {
    
    1080
    +          logger.error("Wrong format for the first line:", reply.lineArray[0]);
    
    1081
    +          return;
    
    1082
    +        }
    
    1083
    +        reply.lineArray[0] = reply.lineArray[0].substring(type.length + 1);
    
    1084
    +        try {
    
    1085
    +          callback(type, reply.lineArray);
    
    1086
    +        } catch (e) {
    
    1087
    +          logger.error("Exception while handling an event", reply, e);
    
    1088
    +        }
    
    1089
    +      },
    
    1090
    +      true
    
    1091
    +    );
    
    1092
    +  }
    
    1093
    +
    
    1094
    +  _processLog(type, lines) {
    
    1095
    +    if (type === "WARN" || type === "ERR") {
    
    1096
    +      // Notify so that Copy Log can be enabled.
    
    1097
    +      Services.obs.notifyObservers(null, TorProviderTopics.HasWarnOrErr);
    
    1098
    +    }
    
    1099
    +
    
    1100
    +    const date = new Date();
    
    1101
    +    const maxEntries = Services.prefs.getIntPref(
    
    1102
    +      "extensions.torlauncher.max_tor_log_entries",
    
    1103
    +      1000
    
    1104
    +    );
    
    1105
    +    if (maxEntries > 0 && this._torLog.length >= maxEntries) {
    
    1106
    +      this._torLog.splice(0, 1);
    
    1107
    +    }
    
    1108
    +
    
    1109
    +    const msg = lines.join("\n");
    
    1110
    +    this._torLog.push({ date, type, msg });
    
    1111
    +    const logString = `Tor ${type}: ${msg}`;
    
    1112
    +    logger.info(logString);
    
    1113
    +  }
    
    1114
    +
    
    1115
    +  // Process a bootstrap status to update the current state, and broadcast it
    
    1116
    +  // to TorBootstrapStatus observers.
    
    1117
    +  // If aSuppressErrors is true, errors are ignored. This is used when we
    
    1118
    +  // are handling the response to a "GETINFO status/bootstrap-phase" command.
    
    1119
    +  _processBootstrapStatus(aStatusMsg, aSuppressErrors) {
    
    1120
    +    const statusObj = TorParsers.parseBootstrapStatus(aStatusMsg);
    
    1121
    +    if (!statusObj) {
    
    1122
    +      return;
    
    1123
    +    }
    
    1124
    +
    
    1125
    +    // Notify observers
    
    1126
    +    statusObj.wrappedJSObject = statusObj;
    
    1127
    +    Services.obs.notifyObservers(statusObj, "TorBootstrapStatus");
    
    1128
    +
    
    1129
    +    if (statusObj.PROGRESS === 100) {
    
    1130
    +      this._isBootstrapDone = true;
    
    1131
    +      try {
    
    1132
    +        Services.prefs.setBoolPref(Preferences.PromptAtStartup, false);
    
    1133
    +      } catch (e) {
    
    1134
    +        logger.warn(`Cannot set ${Preferences.PromptAtStartup}`, e);
    
    1135
    +      }
    
    1136
    +      return;
    
    1137
    +    }
    
    1138
    +
    
    1139
    +    this._isBootstrapDone = false;
    
    1140
    +
    
    1141
    +    if (
    
    1142
    +      statusObj.TYPE === "WARN" &&
    
    1143
    +      statusObj.RECOMMENDATION !== "ignore" &&
    
    1144
    +      !aSuppressErrors
    
    1145
    +    ) {
    
    1146
    +      this._notifyBootstrapError(statusObj);
    
    1147
    +    }
    
    1148
    +  }
    
    1149
    +
    
    1150
    +  _notifyBootstrapError(statusObj) {
    
    1151
    +    try {
    
    1152
    +      Services.prefs.setBoolPref(Preferences.PromptAtStartup, true);
    
    1153
    +    } catch (e) {
    
    1154
    +      logger.warn(`Cannot set ${Preferences.PromptAtStartup}`, e);
    
    1155
    +    }
    
    1156
    +    const phase = TorLauncherUtil.getLocalizedBootstrapStatus(statusObj, "TAG");
    
    1157
    +    const reason = TorLauncherUtil.getLocalizedBootstrapStatus(
    
    1158
    +      statusObj,
    
    1159
    +      "REASON"
    
    1160
    +    );
    
    1161
    +    const details = TorLauncherUtil.getFormattedLocalizedString(
    
    1162
    +      "tor_bootstrap_failed_details",
    
    1163
    +      [phase, reason],
    
    1164
    +      2
    
    1165
    +    );
    
    1166
    +    logger.error(
    
    1167
    +      `Tor bootstrap error: [${statusObj.TAG}/${statusObj.REASON}] ${details}`
    
    1168
    +    );
    
    1169
    +
    
    1170
    +    if (
    
    1171
    +      statusObj.TAG !== this._lastWarningPhase ||
    
    1172
    +      statusObj.REASON !== this._lastWarningReason
    
    1173
    +    ) {
    
    1174
    +      this._lastWarningPhase = statusObj.TAG;
    
    1175
    +      this._lastWarningReason = statusObj.REASON;
    
    1176
    +
    
    1177
    +      const message = TorLauncherUtil.getLocalizedString(
    
    1178
    +        "tor_bootstrap_failed"
    
    1179
    +      );
    
    1180
    +      Services.obs.notifyObservers(
    
    1181
    +        { message, details },
    
    1182
    +        TorProviderTopics.BootstrapError
    
    1183
    +      );
    
    1184
    +    }
    
    1185
    +  }
    
    1186
    +
    
    1187
    +  async _processCircEvent(_type, lines) {
    
    1188
    +    const builtEvent =
    
    1189
    +      /^(?<CircuitID>[a-zA-Z0-9]{1,16})\sBUILT\s(?<Path>(?:,?\$[0-9a-fA-F]{40}(?:~[a-zA-Z0-9]{1,19})?)+)/.exec(
    
    1190
    +        lines[0]
    
    1191
    +      );
    
    1192
    +    const closedEvent = /^(?<ID>[a-zA-Z0-9]{1,16})\sCLOSED/.exec(lines[0]);
    
    1193
    +    if (builtEvent) {
    
    1194
    +      const fp = /\$([0-9a-fA-F]{40})/g;
    
    1195
    +      const nodes = Array.from(builtEvent.groups.Path.matchAll(fp), g =>
    
    1196
    +        g[1].toUpperCase()
    
    1197
    +      );
    
    1198
    +      this._circuits.set(builtEvent.groups.CircuitID, nodes);
    
    1199
    +      // Ignore circuits of length 1, that are used, for example, to probe
    
    1200
    +      // bridges. So, only store them, since we might see streams that use them,
    
    1201
    +      // but then early-return.
    
    1202
    +      if (nodes.length === 1) {
    
    1203
    +        return;
    
    1204
    +      }
    
    1205
    +      // In some cases, we might already receive SOCKS credentials in the line.
    
    1206
    +      // However, this might be a problem with onion services: we get also a
    
    1207
    +      // 4-hop circuit that we likely do not want to show to the user,
    
    1208
    +      // especially because it is used only temporarily, and it would need a
    
    1209
    +      // technical explaination.
    
    1210
    +      // this._checkCredentials(lines[0], nodes);
    
    1211
    +      if (this._currentBridge?.fingerprint !== nodes[0]) {
    
    1212
    +        const nodeInfo = await this.getNodeInfo(nodes[0]);
    
    1213
    +        let notify = false;
    
    1214
    +        if (nodeInfo?.bridgeType) {
    
    1215
    +          logger.info(`Bridge changed to ${nodes[0]}`);
    
    1216
    +          this._currentBridge = nodeInfo;
    
    1217
    +          notify = true;
    
    1218
    +        } else if (this._currentBridge) {
    
    1219
    +          logger.info("Bridges disabled");
    
    1220
    +          this._currentBridge = null;
    
    1221
    +          notify = true;
    
    1222
    +        }
    
    1223
    +        if (notify) {
    
    1224
    +          Services.obs.notifyObservers(
    
    1225
    +            null,
    
    1226
    +            TorProviderTopics.BridgeChanged,
    
    1227
    +            this._currentBridge
    
    1228
    +          );
    
    1229
    +        }
    
    1230
    +      }
    
    1231
    +    } else if (closedEvent) {
    
    1232
    +      this._circuits.delete(closedEvent.groups.ID);
    
    1233
    +    }
    
    1234
    +  }
    
    1235
    +
    
    1236
    +  _processStreamEvent(_type, lines) {
    
    1237
    +    // The first block is the stream ID, which we do not need at the moment.
    
    1238
    +    const succeeedEvent =
    
    1239
    +      /^[a-zA-Z0-9]{1,16}\sSUCCEEDED\s(?<CircuitID>[a-zA-Z0-9]{1,16})/.exec(
    
    1240
    +        lines[0]
    
    1241
    +      );
    
    1242
    +    if (!succeeedEvent) {
    
    1243
    +      return;
    
    1244
    +    }
    
    1245
    +    const circuit = this._circuits.get(succeeedEvent.groups.CircuitID);
    
    1246
    +    if (!circuit) {
    
    1247
    +      logger.error(
    
    1248
    +        "Seen a STREAM SUCCEEDED with an unknown circuit. Not notifying observers.",
    
    1249
    +        lines[0]
    
    1250
    +      );
    
    1251
    +      return;
    
    1252
    +    }
    
    1253
    +    this._checkCredentials(lines[0], circuit);
    
    1254
    +  }
    
    1255
    +
    
    1256
    +  /**
    
    1257
    +   * Check if a STREAM or CIRC response line contains SOCKS_USERNAME and
    
    1258
    +   * SOCKS_PASSWORD. In case, notify observers that we could associate a certain
    
    1259
    +   * circuit to these credentials.
    
    1260
    +   *
    
    1261
    +   * @param {string} line The circ or stream line to check
    
    1262
    +   * @param {NodeFingerprint[]} circuit The fingerprints of the nodes in the
    
    1263
    +   * circuit.
    
    1264
    +   */
    
    1265
    +  _checkCredentials(line, circuit) {
    
    1266
    +    const username = /SOCKS_USERNAME=("(?:[^"\\]|\\.)*")/.exec(line);
    
    1267
    +    const password = /SOCKS_PASSWORD=("(?:[^"\\]|\\.)*")/.exec(line);
    
    1268
    +    if (!username || !password) {
    
    1269
    +      return;
    
    1270
    +    }
    
    1271
    +    Services.obs.notifyObservers(
    
    1272
    +      {
    
    1273
    +        wrappedJSObject: {
    
    1274
    +          username: TorParsers.unescapeString(username[1]),
    
    1275
    +          password: TorParsers.unescapeString(password[1]),
    
    1276
    +          circuit,
    
    1277
    +        },
    
    1278
    +      },
    
    1279
    +      TorProviderTopics.StreamSucceeded
    
    1280
    +    );
    
    1281
    +  }
    
    1282
    +
    
    1283
    +  _shutDownEventMonitor() {
    
    1284
    +    try {
    
    1285
    +      this._connection?.close();
    
    1286
    +    } catch (e) {
    
    1287
    +      logger.error("Could not close the connection to the control port", e);
    
    1288
    +    }
    
    1289
    +    this._connection = null;
    
    1290
    +    if (this._startTimeout !== null) {
    
    1291
    +      clearTimeout(this._startTimeout);
    
    1292
    +      this._startTimeout = null;
    
    1293
    +    }
    
    1294
    +    this._isBootstrapDone = false;
    
    1295
    +    this.clearBootstrapError();
    
    1296
    +  }
    
    1297
    +}
    
    1298
    +
    
    1299
    +// TODO: Stop defining TorProtocolService, make the builder instance the
    
    1300
    +// TorProvider.
    
    1301
    +export const TorProtocolService = new TorProvider();

  • toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs
    1
    +/* This Source Code Form is subject to the terms of the Mozilla Public
    
    2
    + * License, v. 2.0. If a copy of the MPL was not distributed with this
    
    3
    + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
    
    4
    +
    
    5
    +const lazy = {};
    
    6
    +ChromeUtils.defineESModuleGetters(lazy, {
    
    7
    +  TorProtocolService: "resource://gre/modules/TorProtocolService.sys.mjs",
    
    8
    +});
    
    9
    +
    
    10
    +export const TorProviderTopics = Object.freeze({
    
    11
    +  ProcessIsReady: "TorProcessIsReady",
    
    12
    +  ProcessExited: "TorProcessExited",
    
    13
    +  ProcessRestarted: "TorProcessRestarted",
    
    14
    +  BootstrapStatus: "TorBootstrapStatus",
    
    15
    +  BootstrapError: "TorBootstrapError",
    
    16
    +  HasWarnOrErr: "TorLogHasWarnOrErr",
    
    17
    +  BridgeChanged: "TorBridgeChanged",
    
    18
    +  StreamSucceeded: "TorStreamSucceeded",
    
    19
    +});
    
    20
    +
    
    21
    +export class TorProviderBuilder {
    
    22
    +  static async init() {
    
    23
    +    await lazy.TorProtocolService.init();
    
    24
    +  }
    
    25
    +
    
    26
    +  static uninit() {
    
    27
    +    lazy.TorProtocolService.uninit();
    
    28
    +  }
    
    29
    +
    
    30
    +  // TODO: Switch to an async build?
    
    31
    +  static build() {
    
    32
    +    return lazy.TorProtocolService;
    
    33
    +  }
    
    34
    +}

  • toolkit/components/tor-launcher/TorStartupService.sys.mjs
    ... ... @@ -5,8 +5,7 @@ const lazy = {};
    5 5
     ChromeUtils.defineESModuleGetters(lazy, {
    
    6 6
       TorDomainIsolator: "resource://gre/modules/TorDomainIsolator.sys.mjs",
    
    7 7
       TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
    
    8
    -  TorMonitorService: "resource://gre/modules/TorMonitorService.sys.mjs",
    
    9
    -  TorProtocolService: "resource://gre/modules/TorProtocolService.sys.mjs",
    
    8
    +  TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
    
    10 9
     });
    
    11 10
     
    
    12 11
     ChromeUtils.defineModuleGetter(
    
    ... ... @@ -33,24 +32,18 @@ let gInited = false;
    33 32
     // When it observes profile-after-change, it initializes whatever is needed to
    
    34 33
     // launch Tor.
    
    35 34
     export class TorStartupService {
    
    36
    -  _defaultPreferencesAreLoaded = false;
    
    37
    -
    
    38 35
       observe(aSubject, aTopic, aData) {
    
    39 36
         if (aTopic === BrowserTopics.ProfileAfterChange && !gInited) {
    
    40
    -      this._init();
    
    37
    +      this.#init();
    
    41 38
         } else if (aTopic === BrowserTopics.QuitApplicationGranted) {
    
    42
    -      this._uninit();
    
    39
    +      this.#uninit();
    
    43 40
         }
    
    44 41
       }
    
    45 42
     
    
    46
    -  async _init() {
    
    43
    +  async #init() {
    
    47 44
         Services.obs.addObserver(this, BrowserTopics.QuitApplicationGranted);
    
    48 45
     
    
    49
    -    // Starts TorProtocolService first, because it configures the controller
    
    50
    -    // factory, too.
    
    51
    -    await lazy.TorProtocolService.init();
    
    52
    -    lazy.TorMonitorService.init();
    
    53
    -
    
    46
    +    await lazy.TorProviderBuilder.init();
    
    54 47
         lazy.TorSettings.init();
    
    55 48
         lazy.TorConnect.init();
    
    56 49
     
    
    ... ... @@ -59,17 +52,11 @@ export class TorStartupService {
    59 52
         gInited = true;
    
    60 53
       }
    
    61 54
     
    
    62
    -  _uninit() {
    
    55
    +  #uninit() {
    
    63 56
         Services.obs.removeObserver(this, BrowserTopics.QuitApplicationGranted);
    
    64 57
     
    
    65 58
         lazy.TorDomainIsolator.uninit();
    
    66
    -
    
    67
    -    // Close any helper connection first...
    
    68
    -    lazy.TorProtocolService.uninit();
    
    69
    -    // ... and only then closes the event monitor connection, which will cause
    
    70
    -    // Tor to stop.
    
    71
    -    lazy.TorMonitorService.uninit();
    
    72
    -
    
    59
    +    lazy.TorProviderBuilder.uninit();
    
    73 60
         lazy.TorLauncherUtil.cleanupTempDirectories();
    
    74 61
       }
    
    75 62
     }

  • toolkit/components/tor-launcher/moz.build
    ... ... @@ -7,6 +7,7 @@ EXTRA_JS_MODULES += [
    7 7
         "TorParsers.sys.mjs",
    
    8 8
         "TorProcess.sys.mjs",
    
    9 9
         "TorProtocolService.sys.mjs",
    
    10
    +    "TorProviderBuilder.sys.mjs",
    
    10 11
         "TorStartupService.sys.mjs",
    
    11 12
     ]
    
    12 13
     
    

  • _______________________________________________
    tor-commits mailing list
    tor-commits@xxxxxxxxxxxxxxxxxxxx
    https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits