Loading toolkit/components/tor-launcher/TorControlPort.sys.mjs +74 −9 Original line number Diff line number Diff line Loading @@ -246,12 +246,38 @@ class AsyncSocket { * @property {Function} reject The function to reject the promise associated to * the command */ /** * The ID of a circuit. * From control-spec.txt: * CircuitID = 1*16 IDChar * IDChar = ALPHA / DIGIT * Currently, Tor only uses digits, but this may change. * * @typedef {string} CircuitID */ /** * The ID of a stream. * From control-spec.txt: * CircuitID = 1*16 IDChar * IDChar = ALPHA / DIGIT * Currently, Tor only uses digits, but this may change. * * @typedef {string} StreamID */ /** * The fingerprint of a node. * From control-spec.txt: * Fingerprint = "$" 40*HEXDIG * However, we do not keep the $ in our structures. * * @typedef {string} NodeFingerprint */ /** * @typedef {object} Bridge * @property {string} transport The transport of the bridge, or vanilla if not * specified. * @property {string} addr The IP address and port of the bridge * @property {string} id The fingerprint of the bridge * @property {NodeFingerprint} id The fingerprint of the bridge * @property {string} args Optional arguments passed to the bridge */ /** Loading Loading @@ -990,7 +1016,7 @@ export class TorController { status = this.#parseBootstrapStatus(data.groups.data); } catch (e) { // Probably, a non bootstrap client status logger.debug(`Failed to parse STATUS_CLIENT: ${data.groups.data}`); logger.debug(`Failed to parse STATUS_CLIENT: ${data.groups.data}`, e); break; } this.#eventHandler.onBootstrapStatus(status); Loading Loading @@ -1123,14 +1149,53 @@ export class TorController { } /** * @typedef {object} TorEventHandler * The event handler interface. * The controller owner can implement this methods to receive asynchronous * notifications from the controller. * * @property {OnBootstrapStatus} onBootstrapStatus Called when a bootstrap * status is received (i.e., a STATUS_CLIENT event with a BOOTSTRAP action) * @property {OnLogMessage} onLogMessage Called when a log message is received * (i.e., a NOTICE, WARN or ERR notification) * @property {OnCircuitBuilt} onCircuitBuilt Called when a circuit is built * (i.e., a CIRC event with a BUILT status) * @property {OnCircuitClosed} onCircuitClosed Called when a circuit is closed * (i.e., a CIRC event with a CLOSED status) * @property {OnStreamSucceeded} onStreamSucceeded Called when a stream receives * a reply (i.e., a STREAM event with a SUCCEEDED status) */ /** * @callback OnBootstrapStatus * * @param {object} status An object with the bootstrap information. Its keys * depend on what the arguments sent by the tor daemon */ /** * @callback OnLogMessage * * @param {string} type The type of message (NOTICE, WARNING, ERR, etc...) * @param {string} message The actual log message */ /** * @callback OnCircuitBuilt * * @param {CircuitID} id The id of the circuit that has been built * @param {NodeFingerprint[]} nodes The onion routers composing the circuit */ /** * @callback OnCircuitClosed * * @param {CircuitID} id The id of the circuit that has been closed */ /** * @callback OnStreamSucceeded * * @param {StreamID} streamId The id of the stream that switched to the succeeded * state * @param {CircuitID} circuitId The id of the circuit the stream is using * @param {string?} username The SOCKS username associated to the stream, or * null if not available * @param {string?} username The SOCKS password associated to the stream, or * null if not available */ export class TorEventHandler { onBootstrapStatus(status) {} onLogMessage(message) {} onCircuitBuilt(id, nodes) {} onCircuitClosed(id) {} onStreamSucceeded(streamId, circuitId, username, password) {} } toolkit/components/tor-launcher/TorLauncherUtil.sys.mjs +4 −4 Original line number Diff line number Diff line Loading @@ -503,10 +503,6 @@ export const TorLauncherUtil = Object.freeze({ */ getPreferredSocksConfiguration() { if (Services.env.exists("TOR_TRANSPROXY")) { Services.prefs.setBoolPref("network.proxy.socks_remote_dns", false); Services.prefs.setIntPref("network.proxy.type", 0); Services.prefs.setIntPref("network.proxy.socks_port", 0); Services.prefs.setCharPref("network.proxy.socks", ""); return { transproxy: true }; } Loading Loading @@ -576,6 +572,10 @@ export const TorLauncherUtil = Object.freeze({ setProxyConfiguration(socksPortInfo) { if (socksPortInfo.transproxy) { Services.prefs.setBoolPref("network.proxy.socks_remote_dns", false); Services.prefs.setIntPref("network.proxy.type", 0); Services.prefs.setIntPref("network.proxy.socks_port", 0); Services.prefs.setCharPref("network.proxy.socks", ""); return; } Loading toolkit/components/tor-launcher/TorProcess.sys.mjs +1 −1 Original line number Diff line number Diff line Loading @@ -361,7 +361,7 @@ export class TorProcess { * Hash a password to then pass it to Tor as a command line argument. * Based on Vidalia's TorSettings::hashPassword(). * * @param {Array|Uint8Array} password The password, as an array of bytes * @param {Uint8Array} password The password, as an array of bytes */ #hashPassword(password) { // The password has already been checked by the caller. Loading toolkit/components/tor-launcher/TorProvider.sys.mjs +18 −40 Original line number Diff line number Diff line Loading @@ -26,7 +26,7 @@ const logger = new ConsoleAPI({ * @typedef {object} ControlPortSettings An object with the settings to use for * the control port. All the entries are optional, but an authentication * mechanism and a communication method must be specified. * @property {Array|Uint8Array=} password The clear text password as an array of * @property {Uint8Array=} password The clear text password as an array of * bytes. It must always be defined, unless cookieFilePath is * @property {string=} cookieFilePath The path to the cookie file to use for * authentication Loading @@ -41,22 +41,6 @@ const logger = new ConsoleAPI({ * @property {string} type The message level * @property {string} msg The message */ /** * From control-spec.txt: * CircuitID = 1*16 IDChar * IDChar = ALPHA / DIGIT * Currently, Tor only uses digits, but this may change. * * @typedef {string} CircuitID */ /** * The fingerprint of a node. * From control-spec.txt: * Fingerprint = "$" 40*HEXDIG * However, we do not keep the $ in our structures. * * @typedef {string} NodeFingerprint */ /** * Stores the data associated with a circuit node. * Loading Loading @@ -189,7 +173,7 @@ export class TorProvider { if (socksSettings.transproxy) { logger.info("Transparent proxy required, not starting a Tor daemon."); } else if (TorLauncherUtil.shouldStartAndOwnTor) { } else if (this.ownsTorDaemon) { try { await this.#startDaemon(socksSettings); } catch (e) { Loading Loading @@ -228,8 +212,6 @@ export class TorProvider { logger.debug("Uninitializing the Tor provider."); this.#forgetProcess(); this.#closeConnection(); this.#isBootstrapDone = false; this.#lastWarning = {}; } // Provider API Loading Loading @@ -439,16 +421,12 @@ export class TorProvider { this.#torProcess.onExit = exitCode => { logger.info(`The tor process exited with code ${exitCode}`); this.#forgetProcess(); this.#isBootstrapDone = false; this.#lastWarning = {}; Services.obs.notifyObservers(null, TorProviderTopics.ProcessExited); this.#closeConnection(); Services.obs.notifyObservers(null, TorProviderTopics.ProcessExited); }; this.#torProcess.onRestart = async () => { logger.info("Restarting the tor process"); this.#closeConnection(); this.#isBootstrapDone = false; this.#lastWarning = {}; this.#circuits.clear(); try { await this.#firstConnection(); Loading @@ -469,8 +447,8 @@ export class TorProvider { if (this.#torProcess) { logger.trace('"Forgetting" the tor process.'); this.#torProcess.forget(); this.#torProcess.onExit = null; this.#torProcess.onRestart = null; this.#torProcess.onExit = () => {}; this.#torProcess.onRestart = () => {}; this.#torProcess = null; } } Loading Loading @@ -542,9 +520,9 @@ export class TorProvider { const encoder = new TextEncoder(); settings.password = encoder.encode(TorParsers.unescapeString(password)); } else if (/^([0-9a-fA-F]{2})+$/.test(password)) { settings.password = []; for (let i = 0; i < password.length; i += 2) { settings.password.push(parseInt(password.substring(i, i + 2), 16)); settings.password = new Uint8Array(password.length / 2); for (let i = 0, j = 0; i < settings.password.length; i++, j += 2) { settings.password[i] = parseInt(password.substring(j, j + 2), 16); } } if (password && !settings.password?.length) { Loading @@ -562,7 +540,7 @@ export class TorProvider { } } if ( TorLauncherUtil.shouldStartAndOwnTor && this.ownsTorDaemon && !settings.password?.length && !settings.cookieFilePath ) { Loading @@ -574,17 +552,19 @@ export class TorProvider { async #firstConnection() { // FIXME: No way to cancel this connection! Do we need one? const initialDelay = 5; const maxDelay = 10_000; let delay = initialDelay; let delay = 5; logger.debug("Connecting to the control port for the first time."); await new Promise((resolve, reject) => { const tryConnect = () => { this.#openControlPort() .then(resolve) .then(() => { this.#torProcess?.connectionWorked(); resolve(); }) .catch(e => { if (delay < maxDelay) { logger.error( logger.info( `Failed to connect to the control port. Trying again in ${delay}ms.`, e ); Loading Loading @@ -707,6 +687,8 @@ export class TorProvider { "Requested to close an already closed control port connection" ); } this.#isBootstrapDone = false; this.#lastWarning = {}; } // Authentication Loading @@ -716,17 +698,13 @@ export class TorProvider { } /** * @returns {string} A random 16-byte password. * @returns {Uint8Array} A random 16-byte password. */ #generateRandomPassword() { const kPasswordLen = 16; return crypto.getRandomValues(new Uint8Array(kPasswordLen)); } #toHex(aValue, aMinLen) { return aValue.toString(16).padStart(aMinLen, "0"); } // Notification handlers onBootstrapStatus(status) { Loading Loading
toolkit/components/tor-launcher/TorControlPort.sys.mjs +74 −9 Original line number Diff line number Diff line Loading @@ -246,12 +246,38 @@ class AsyncSocket { * @property {Function} reject The function to reject the promise associated to * the command */ /** * The ID of a circuit. * From control-spec.txt: * CircuitID = 1*16 IDChar * IDChar = ALPHA / DIGIT * Currently, Tor only uses digits, but this may change. * * @typedef {string} CircuitID */ /** * The ID of a stream. * From control-spec.txt: * CircuitID = 1*16 IDChar * IDChar = ALPHA / DIGIT * Currently, Tor only uses digits, but this may change. * * @typedef {string} StreamID */ /** * The fingerprint of a node. * From control-spec.txt: * Fingerprint = "$" 40*HEXDIG * However, we do not keep the $ in our structures. * * @typedef {string} NodeFingerprint */ /** * @typedef {object} Bridge * @property {string} transport The transport of the bridge, or vanilla if not * specified. * @property {string} addr The IP address and port of the bridge * @property {string} id The fingerprint of the bridge * @property {NodeFingerprint} id The fingerprint of the bridge * @property {string} args Optional arguments passed to the bridge */ /** Loading Loading @@ -990,7 +1016,7 @@ export class TorController { status = this.#parseBootstrapStatus(data.groups.data); } catch (e) { // Probably, a non bootstrap client status logger.debug(`Failed to parse STATUS_CLIENT: ${data.groups.data}`); logger.debug(`Failed to parse STATUS_CLIENT: ${data.groups.data}`, e); break; } this.#eventHandler.onBootstrapStatus(status); Loading Loading @@ -1123,14 +1149,53 @@ export class TorController { } /** * @typedef {object} TorEventHandler * The event handler interface. * The controller owner can implement this methods to receive asynchronous * notifications from the controller. * * @property {OnBootstrapStatus} onBootstrapStatus Called when a bootstrap * status is received (i.e., a STATUS_CLIENT event with a BOOTSTRAP action) * @property {OnLogMessage} onLogMessage Called when a log message is received * (i.e., a NOTICE, WARN or ERR notification) * @property {OnCircuitBuilt} onCircuitBuilt Called when a circuit is built * (i.e., a CIRC event with a BUILT status) * @property {OnCircuitClosed} onCircuitClosed Called when a circuit is closed * (i.e., a CIRC event with a CLOSED status) * @property {OnStreamSucceeded} onStreamSucceeded Called when a stream receives * a reply (i.e., a STREAM event with a SUCCEEDED status) */ /** * @callback OnBootstrapStatus * * @param {object} status An object with the bootstrap information. Its keys * depend on what the arguments sent by the tor daemon */ /** * @callback OnLogMessage * * @param {string} type The type of message (NOTICE, WARNING, ERR, etc...) * @param {string} message The actual log message */ /** * @callback OnCircuitBuilt * * @param {CircuitID} id The id of the circuit that has been built * @param {NodeFingerprint[]} nodes The onion routers composing the circuit */ /** * @callback OnCircuitClosed * * @param {CircuitID} id The id of the circuit that has been closed */ /** * @callback OnStreamSucceeded * * @param {StreamID} streamId The id of the stream that switched to the succeeded * state * @param {CircuitID} circuitId The id of the circuit the stream is using * @param {string?} username The SOCKS username associated to the stream, or * null if not available * @param {string?} username The SOCKS password associated to the stream, or * null if not available */ export class TorEventHandler { onBootstrapStatus(status) {} onLogMessage(message) {} onCircuitBuilt(id, nodes) {} onCircuitClosed(id) {} onStreamSucceeded(streamId, circuitId, username, password) {} }
toolkit/components/tor-launcher/TorLauncherUtil.sys.mjs +4 −4 Original line number Diff line number Diff line Loading @@ -503,10 +503,6 @@ export const TorLauncherUtil = Object.freeze({ */ getPreferredSocksConfiguration() { if (Services.env.exists("TOR_TRANSPROXY")) { Services.prefs.setBoolPref("network.proxy.socks_remote_dns", false); Services.prefs.setIntPref("network.proxy.type", 0); Services.prefs.setIntPref("network.proxy.socks_port", 0); Services.prefs.setCharPref("network.proxy.socks", ""); return { transproxy: true }; } Loading Loading @@ -576,6 +572,10 @@ export const TorLauncherUtil = Object.freeze({ setProxyConfiguration(socksPortInfo) { if (socksPortInfo.transproxy) { Services.prefs.setBoolPref("network.proxy.socks_remote_dns", false); Services.prefs.setIntPref("network.proxy.type", 0); Services.prefs.setIntPref("network.proxy.socks_port", 0); Services.prefs.setCharPref("network.proxy.socks", ""); return; } Loading
toolkit/components/tor-launcher/TorProcess.sys.mjs +1 −1 Original line number Diff line number Diff line Loading @@ -361,7 +361,7 @@ export class TorProcess { * Hash a password to then pass it to Tor as a command line argument. * Based on Vidalia's TorSettings::hashPassword(). * * @param {Array|Uint8Array} password The password, as an array of bytes * @param {Uint8Array} password The password, as an array of bytes */ #hashPassword(password) { // The password has already been checked by the caller. Loading
toolkit/components/tor-launcher/TorProvider.sys.mjs +18 −40 Original line number Diff line number Diff line Loading @@ -26,7 +26,7 @@ const logger = new ConsoleAPI({ * @typedef {object} ControlPortSettings An object with the settings to use for * the control port. All the entries are optional, but an authentication * mechanism and a communication method must be specified. * @property {Array|Uint8Array=} password The clear text password as an array of * @property {Uint8Array=} password The clear text password as an array of * bytes. It must always be defined, unless cookieFilePath is * @property {string=} cookieFilePath The path to the cookie file to use for * authentication Loading @@ -41,22 +41,6 @@ const logger = new ConsoleAPI({ * @property {string} type The message level * @property {string} msg The message */ /** * From control-spec.txt: * CircuitID = 1*16 IDChar * IDChar = ALPHA / DIGIT * Currently, Tor only uses digits, but this may change. * * @typedef {string} CircuitID */ /** * The fingerprint of a node. * From control-spec.txt: * Fingerprint = "$" 40*HEXDIG * However, we do not keep the $ in our structures. * * @typedef {string} NodeFingerprint */ /** * Stores the data associated with a circuit node. * Loading Loading @@ -189,7 +173,7 @@ export class TorProvider { if (socksSettings.transproxy) { logger.info("Transparent proxy required, not starting a Tor daemon."); } else if (TorLauncherUtil.shouldStartAndOwnTor) { } else if (this.ownsTorDaemon) { try { await this.#startDaemon(socksSettings); } catch (e) { Loading Loading @@ -228,8 +212,6 @@ export class TorProvider { logger.debug("Uninitializing the Tor provider."); this.#forgetProcess(); this.#closeConnection(); this.#isBootstrapDone = false; this.#lastWarning = {}; } // Provider API Loading Loading @@ -439,16 +421,12 @@ export class TorProvider { this.#torProcess.onExit = exitCode => { logger.info(`The tor process exited with code ${exitCode}`); this.#forgetProcess(); this.#isBootstrapDone = false; this.#lastWarning = {}; Services.obs.notifyObservers(null, TorProviderTopics.ProcessExited); this.#closeConnection(); Services.obs.notifyObservers(null, TorProviderTopics.ProcessExited); }; this.#torProcess.onRestart = async () => { logger.info("Restarting the tor process"); this.#closeConnection(); this.#isBootstrapDone = false; this.#lastWarning = {}; this.#circuits.clear(); try { await this.#firstConnection(); Loading @@ -469,8 +447,8 @@ export class TorProvider { if (this.#torProcess) { logger.trace('"Forgetting" the tor process.'); this.#torProcess.forget(); this.#torProcess.onExit = null; this.#torProcess.onRestart = null; this.#torProcess.onExit = () => {}; this.#torProcess.onRestart = () => {}; this.#torProcess = null; } } Loading Loading @@ -542,9 +520,9 @@ export class TorProvider { const encoder = new TextEncoder(); settings.password = encoder.encode(TorParsers.unescapeString(password)); } else if (/^([0-9a-fA-F]{2})+$/.test(password)) { settings.password = []; for (let i = 0; i < password.length; i += 2) { settings.password.push(parseInt(password.substring(i, i + 2), 16)); settings.password = new Uint8Array(password.length / 2); for (let i = 0, j = 0; i < settings.password.length; i++, j += 2) { settings.password[i] = parseInt(password.substring(j, j + 2), 16); } } if (password && !settings.password?.length) { Loading @@ -562,7 +540,7 @@ export class TorProvider { } } if ( TorLauncherUtil.shouldStartAndOwnTor && this.ownsTorDaemon && !settings.password?.length && !settings.cookieFilePath ) { Loading @@ -574,17 +552,19 @@ export class TorProvider { async #firstConnection() { // FIXME: No way to cancel this connection! Do we need one? const initialDelay = 5; const maxDelay = 10_000; let delay = initialDelay; let delay = 5; logger.debug("Connecting to the control port for the first time."); await new Promise((resolve, reject) => { const tryConnect = () => { this.#openControlPort() .then(resolve) .then(() => { this.#torProcess?.connectionWorked(); resolve(); }) .catch(e => { if (delay < maxDelay) { logger.error( logger.info( `Failed to connect to the control port. Trying again in ${delay}ms.`, e ); Loading Loading @@ -707,6 +687,8 @@ export class TorProvider { "Requested to close an already closed control port connection" ); } this.#isBootstrapDone = false; this.#lastWarning = {}; } // Authentication Loading @@ -716,17 +698,13 @@ export class TorProvider { } /** * @returns {string} A random 16-byte password. * @returns {Uint8Array} A random 16-byte password. */ #generateRandomPassword() { const kPasswordLen = 16; return crypto.getRandomValues(new Uint8Array(kPasswordLen)); } #toHex(aValue, aMinLen) { return aValue.toString(16).padStart(aMinLen, "0"); } // Notification handlers onBootstrapStatus(status) { Loading