UNPKG

tor-ctrl

Version:

Node.js library for accessing the Tor control port

331 lines (329 loc) 9.64 kB
import "./chunk-OLNOUFRH.mjs"; // src/index.ts import { EventEmitter } from "node:events"; import { connect } from "node:net"; var TorControl = class extends EventEmitter { _connection = null; _config; _state = "disconnected"; _debugger = null; /** * Get the current connection state of the TorControl */ get state() { return this._state; } constructor(config = {}) { super(); if (!Object.prototype.hasOwnProperty.call(config, "host")) { config.host = "localhost"; } if (!Object.prototype.hasOwnProperty.call(config, "port")) { config.port = 9051; } this._config = config; import("./src-R43MIFC6.mjs").then((pkg) => { if (pkg) { this._debugger = pkg.default("tor-ctrl"); } }).catch(() => { }); } _debug(...args) { if (this._debugger) { this._debugger(...args); } } /** * Establish a connection to the TorControl */ connect() { const conn = connect(this._config.port, this._config.host); this._connection = conn; return new Promise((resolve) => { conn.on("error", (err) => { const error = err instanceof Error ? err : new Error(`TorControl connection error: ${err}`); this.emit("error", error); return resolve({ error }); }); conn.once("data", (data) => { const str = data.toString(); this._debug("data:", str); if (str.substring(0, 3) === "250") { this.emit("connect", conn); return resolve({ data: true }); } const error = new Error(`TorControl connection error: ${data}`); this.emit("error", error); return resolve({ error }); }); conn.on("close", () => { this._state = "disconnected"; this.emit("close"); }); if (this._config.password) { this.authenticate(this._config.password).then(({ error }) => { if (error) { this.emit("error", error); return resolve({ error }); } }); } }); } /** * Close the connection to the TorControl */ async disconnect() { if (this._connection) { await this.quit(); this._connection.end(); this._connection = null; } } get disconnected() { return this._connection === null; } /** * Send a command to the TorControl * * Example: * ```typescript * const { data, error } = await torControl.sendCommand(['GETINFO', 'version']); * if (error) { * console.error('Error:', error); * return; * } * console.log('GETINFO:', data); // { code: 250, message: 'version=...' } * ``` * * @link https://spec.torproject.org/control-spec/commands.html * @param command */ async sendCommand(command) { if (!this._connection) { const error = new Error("TorControl not connected"); this.emit("error", error); return { error }; } const conn = this._connection; if (Array.isArray(command)) { command = command.join(" "); } return new Promise((resolve) => { conn.once("data", (data) => { const str = data.toString(); this._debug("sendCommand:data", str, str.split("")); const lines = str.split(/\r?\n/); this._debug("sendCommand:lines", lines); const result = []; for (const line of lines) { if (line !== "") { const message = line.substring(4); const code = Number(line.substring(0, 3)); this._debug("sendCommand:message", message); this.emit("data", message); result.push({ code, message }); } } return resolve({ data: result }); }); this._debug("sendCommand:command", command); conn.write(`${command}\r `); }); } async _solveAndPick(promise, pick = 0) { const { data, error } = await promise; if (error) return { error }; return { data: data[pick] }; } // =============================== // Config // =============================== /** * Authenticate * * @link https://spec.torproject.org/control-spec/commands.html?highlight=AUTHENTICATE#authenticate * @param password */ async authenticate(password) { return this._solveAndPick(this.sendCommand(`AUTHENTICATE "${password}"`)); } /** * Quit * * @link https://spec.torproject.org/control-spec/commands.html?highlight=QUIT#quit */ async quit() { return this._solveAndPick(this.sendCommand("QUIT")); } /** * Example: * * ```typescript * const { data, error } = await torControl.getConfig('SocksPort'); * if (data) { * console.log('SocksPort:', data); // SocksPort: 9050 * } * ``` * * @link https://spec.torproject.org/control-spec/commands.html?highlight=GETCONF#getconf * @param key */ async getConfig(key) { const { data, error } = await this._solveAndPick(this.sendCommand(["GETCONF", key])); if (error) return { error }; return { data: data.message.split("=")[1] }; } /** * @link https://spec.torproject.org/control-spec/commands.html?highlight=SETCONF#setconf * @param key * @param value */ async setConfig(key, value) { return this._solveAndPick(this.sendCommand(["SETCONF", `${key}=${value}`])); } /** * @link https://spec.torproject.org/control-spec/commands.html?highlight=RESETCONF#resetconf * @param key */ async resetConfig(key) { return this._solveAndPick(this.sendCommand(["RESETCONF", key])); } // =============================== // Signals // =============================== async signal(signal) { return this.sendCommand(["SIGNAL", signal]); } async signalReload() { return this._solveAndPick(this.signal("RELOAD")); } async signalShutdown() { return this._solveAndPick(this.signal("SHUTDOWN")); } async signalDump() { return this._solveAndPick(this.signal("DUMP")); } async signalDebug() { return this._solveAndPick(this.signal("DEBUG")); } async signalHalt() { return this._solveAndPick(this.signal("HALT")); } async signalTerm() { return this._solveAndPick(this.signal("TERM")); } async signalNewNym() { return this._solveAndPick(this.signal("NEWNYM")); } async signalClearDnsCache() { return this._solveAndPick(this.signal("CLEARDNSCACHE")); } async signalUsr1() { return this._solveAndPick(this.signal("USR1")); } async signalUsr2() { return this._solveAndPick(this.signal("USR2")); } // =============================== // Misc // =============================== /** * Example: * * ```typescript * const { data, error } = await torControl.getInfo('version'); * if (data) { * console.log('Version:', data); // Version: Tor * } * ``` * * @link https://spec.torproject.org/control-spec/commands.html?highlight=GETINFO#getinfo * @param key */ async getInfo(key) { return this._solveAndPick(this.sendCommand(["GETINFO", key])); } /** * Example: * * ```typescript * const mar = await torControl.mapAddress('1.2.3.4', 'torproject.org'); * console.log('MAPADDRESS:', mar); // { code: 250, message: '1.2.3.4=torproject.org' } * * const gir = await torControl.getInfo('address-mappings/control'); * console.log('GETINFO:', gir); // { code: 250, message: 'address-mappings/control=1.2.3.4 torproject.org NEVER' } * ``` * * @link https://spec.torproject.org/control-spec/commands.html?highlight=MAPADDRESS#mapaddress * @param address * @param target */ async mapAddress(address, target) { return this._solveAndPick(this.sendCommand(["MAPADDRESS", `${address}=${target}`])); } // =============================== // Circuit // =============================== async extendCircuit(circuitId) { return this._solveAndPick(this.sendCommand(["EXTENDCIRCUIT", circuitId])); } /** * @link https://spec.torproject.org/control-spec/commands.html?highlight=SETCIRCUITPURPOSE#setcircuitpurpose * @param circuitId * @param purpose */ async setCircuitPurpose(circuitId, purpose) { return this._solveAndPick(this.sendCommand(["SETCIRCUITPURPOSE", circuitId, purpose])); } /** * @link https://spec.torproject.org/control-spec/commands.html?highlight=SETROUTERPURPOSE#setrouterpurpose * @param nicknameOrKey * @param purpose */ async setRouterPurpose(nicknameOrKey, purpose) { return this._solveAndPick(this.sendCommand(["SETROUTERPURPOSE", nicknameOrKey, purpose])); } /** * @link https://spec.torproject.org/control-spec/commands.html?highlight=CLOSESTREAM#closestream * @param streamId * @param reason */ async setStream(streamId, reason) { return this._solveAndPick(this.sendCommand(["CLOSESTREAM", streamId, reason])); } /** * @link https://spec.torproject.org/control-spec/commands.html?highlight=CLOSECIRCUIT#closecircuit * @param circuitId */ async closeCircuit(circuitId) { return this._solveAndPick(this.sendCommand(["CLOSECIRCUIT", circuitId])); } /** * @link https://spec.torproject.org/control-spec/commands.html?highlight=ATTACHSTREAM#attachstream * @param streamId * @param circuitId * @param hop */ async attachStream(streamId, circuitId, hop) { return this._solveAndPick( this.sendCommand(["ATTACHSTREAM", streamId, circuitId, hop ? String(hop) : ""]) ); } // =============================== // Alias // =============================== /** * Alias for `signalNewNym` */ async getNewIdentity() { return this.signalNewNym(); } }; export { TorControl };