Bug 41353 (Update DnD filter)
[torbutton.git] / modules / tor-control-port.js
blob374ff5fd1bfdee7b7a2d648735ad0ab478b4623d
1 // A module for TorBrowser that provides an asynchronous controller for
2 // Tor, through its ControlPort.
3 //
4 // This file is written in call stack order (later functions
5 // call earlier functions). The file can be processed
6 // with docco.js to produce pretty documentation.
7 //
8 // To import the module, use
9 //
10 // let { configureControlPortModule, controller, wait_for_controller } =
11 // Components.utils.import("path/to/tor-control-port.js", {});
13 // See the third-to-last function defined in this file:
14 // configureControlPortModule(ipcFile, host, port, password)
15 // for usage of the configureControlPortModule function.
17 // See the last functions defined in this file:
18 // controller(avoidCache), wait_for_controller(avoidCache)
19 // for usage of the controller functions.
21 /* jshint esnext: true */
22 /* jshint -W097 */
23 /* global console */
24 "use strict";
26 // ### Import Mozilla Services
27 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
29 ChromeUtils.defineModuleGetter(
30 this,
31 "TorMonitorService",
32 "resource://gre/modules/TorMonitorService.jsm"
35 // tor-launcher observer topics
36 const TorTopics = Object.freeze({
37 ProcessIsReady: "TorProcessIsReady",
38 });
40 // __log__.
41 // Logging function
42 let logger = Cc["@torproject.org/torbutton-logger;1"].getService(Ci.nsISupports)
43 .wrappedJSObject;
44 let log = x => logger.eclog(3, x.trimRight().replace(/\r\n/g, "\n"));
46 // ### announce this file
47 log("Loading tor-control-port.js\n");
49 class AsyncSocket {
50 constructor(ipcFile, host, port) {
51 let sts = Cc["@mozilla.org/network/socket-transport-service;1"].getService(
52 Ci.nsISocketTransportService
54 const OPEN_UNBUFFERED = Ci.nsITransport.OPEN_UNBUFFERED;
56 let socketTransport = ipcFile
57 ? sts.createUnixDomainTransport(ipcFile)
58 : sts.createTransport([], host, port, null, null);
60 this.outputStream = socketTransport
61 .openOutputStream(OPEN_UNBUFFERED, 1, 1)
62 .QueryInterface(Ci.nsIAsyncOutputStream);
63 this.outputQueue = [];
65 this.inputStream = socketTransport
66 .openInputStream(OPEN_UNBUFFERED, 1, 1)
67 .QueryInterface(Ci.nsIAsyncInputStream);
68 this.scriptableInputStream = Cc[
69 "@mozilla.org/scriptableinputstream;1"
70 ].createInstance(Ci.nsIScriptableInputStream);
71 this.scriptableInputStream.init(this.inputStream);
72 this.inputQueue = [];
75 // asynchronously write string to underlying socket and return number of bytes written
76 async write(str) {
77 return new Promise((resolve, reject) => {
78 // asyncWait next write request
79 const tryAsyncWait = () => {
80 if (this.outputQueue.length) {
81 this.outputStream.asyncWait(
82 this.outputQueue.at(0), // next request
85 Services.tm.currentThread
90 // output stream can only have 1 registered callback at a time, so multiple writes
91 // need to be queued up (see nsIAsyncOutputStream.idl)
92 this.outputQueue.push({
93 // Implement an nsIOutputStreamCallback:
94 onOutputStreamReady: () => {
95 try {
96 let bytesWritten = this.outputStream.write(str, str.length);
98 // remove this callback object from queue as it is now completed
99 this.outputQueue.shift();
101 // request next wait if there is one
102 tryAsyncWait();
104 // finally resolve promise
105 resolve(bytesWritten);
106 } catch (err) {
107 // reject promise on error
108 reject(err);
113 // length 1 imples that there is no in-flight asyncWait, so we may immediately
114 // follow through on this write
115 if (this.outputQueue.length == 1) {
116 tryAsyncWait();
121 // asynchronously read string from underlying socket and return it
122 async read() {
123 return new Promise((resolve, reject) => {
124 const tryAsyncWait = () => {
125 if (this.inputQueue.length) {
126 this.inputStream.asyncWait(
127 this.inputQueue.at(0), // next input request
130 Services.tm.currentThread
135 this.inputQueue.push({
136 onInputStreamReady: stream => {
137 try {
138 // read our string from input stream
139 let str = this.scriptableInputStream.read(
140 this.scriptableInputStream.available()
143 // remove this callback object from queue now that we have read
144 this.inputQueue.shift();
146 // request next wait if there is one
147 tryAsyncWait();
149 // finally resolve promise
150 resolve(str);
151 } catch (err) {
152 reject(err);
157 // length 1 imples that there is no in-flight asyncWait, so we may immediately
158 // follow through on this read
159 if (this.inputQueue.length == 1) {
160 tryAsyncWait();
165 close() {
166 this.outputStream.close();
167 this.inputStream.close();
171 class ControlSocket {
172 constructor(asyncSocket) {
173 this.socket = asyncSocket;
174 this._isOpen = true;
175 this.pendingData = "";
176 this.pendingLines = [];
178 this.mainDispatcher = io.callbackDispatcher();
179 this.notificationDispatcher = io.callbackDispatcher();
180 // mainDispatcher pushes only async notifications (650) to notificationDispatcher
181 this.mainDispatcher.addCallback(
182 /^650/,
183 this._handleNotification.bind(this)
185 // callback for handling responses and errors
186 this.mainDispatcher.addCallback(
187 /^[245]\d\d/,
188 this._handleCommandReply.bind(this)
191 this.commandQueue = [];
193 this._startMessagePump();
196 // blocks until an entire line is read and returns it
197 // immediately returns next line in queue (pendingLines) if present
198 async _readLine() {
199 // keep reading from socket until we have a full line to return
200 while (!this.pendingLines.length) {
201 // read data from our socket and spit on newline tokens
202 this.pendingData += await this.socket.read();
203 let lines = this.pendingData.split("\r\n");
205 // the last line will either be empty string, or a partial read of a response/event
206 // so save it off for the next socket read
207 this.pendingData = lines.pop();
209 // copy remaining full lines to our pendingLines list
210 this.pendingLines = this.pendingLines.concat(lines);
212 return this.pendingLines.shift();
215 // blocks until an entire message is ready and returns it
216 async _readMessage() {
217 // whether we are searching for the end of a multi-line values
218 // See control-spec section 3.9
219 let handlingMultlineValue = false;
220 let endOfMessageFound = false;
221 const message = [];
223 do {
224 const line = await this._readLine();
225 message.push(line);
227 if (handlingMultlineValue) {
228 // look for end of multiline
229 if (line.match(/^\.$/)) {
230 handlingMultlineValue = false;
232 } else {
233 // 'Multiline values' are possible. We avoid interrupting one by detecting it
234 // and waiting for a terminating "." on its own line.
235 // (See control-spec section 3.9 and https://trac.torproject.org/16990#comment:28
236 // Ensure this is the first line of a new message
237 // eslint-disable-next-line no-lonely-if
238 if (message.length === 1 && line.match(/^\d\d\d\+.+?=$/)) {
239 handlingMultlineValue = true;
241 // look for end of message (note the space character at end of the regex)
242 else if (line.match(/^\d\d\d /)) {
243 if (message.length == 1) {
244 endOfMessageFound = true;
245 } else {
246 let firstReplyCode = message[0].substring(0, 3);
247 let lastReplyCode = line.substring(0, 3);
248 if (firstReplyCode == lastReplyCode) {
249 endOfMessageFound = true;
254 } while (!endOfMessageFound);
256 // join our lines back together to form one message
257 return message.join("\r\n");
260 async _startMessagePump() {
261 try {
262 while (true) {
263 let message = await this._readMessage();
264 log("controlPort >> " + message);
265 this.mainDispatcher.pushMessage(message);
267 } catch (err) {
268 this._isOpen = false;
269 for (const cmd of this.commandQueue) {
270 cmd.reject(err);
272 this.commandQueue = [];
276 _writeNextCommand() {
277 let cmd = this.commandQueue[0];
278 log("controlPort << " + cmd.commandString);
279 this.socket.write(`${cmd.commandString}\r\n`).catch(cmd.reject);
282 async sendCommand(commandString) {
283 if (!this.isOpen()) {
284 throw new Error("ControlSocket not open");
287 // this promise is resolved either in _handleCommandReply, or
288 // in _startMessagePump (on stream error)
289 return new Promise((resolve, reject) => {
290 let command = {
291 commandString,
292 resolve,
293 reject,
296 this.commandQueue.push(command);
297 if (this.commandQueue.length == 1) {
298 this._writeNextCommand();
303 _handleCommandReply(message) {
304 let cmd = this.commandQueue.shift();
305 if (message.match(/^2/)) {
306 cmd.resolve(message);
307 } else if (message.match(/^[45]/)) {
308 let myErr = new Error(cmd.commandString + " -> " + message);
309 // Add Tor-specific information to the Error object.
310 let idx = message.indexOf(" ");
311 if (idx > 0) {
312 myErr.torStatusCode = message.substring(0, idx);
313 myErr.torMessage = message.substring(idx);
314 } else {
315 myErr.torStatusCode = message;
317 cmd.reject(myErr);
318 } else {
319 cmd.reject(
320 new Error(
321 `ControlSocket::_handleCommandReply received unexpected message:\n----\n${message}\n----`
326 // send next command if one is available
327 if (this.commandQueue.length) {
328 this._writeNextCommand();
332 _handleNotification(message) {
333 this.notificationDispatcher.pushMessage(message);
336 close() {
337 this.socket.close();
338 this._isOpen = false;
341 addNotificationCallback(regex, callback) {
342 this.notificationDispatcher.addCallback(regex, callback);
345 isOpen() {
346 return this._isOpen;
350 // ## io
351 // I/O utilities namespace
353 let io = {};
355 // __io.callbackDispatcher()__.
356 // Returns dispatcher object with three member functions:
357 // dispatcher.addCallback(regex, callback), dispatcher.removeCallback(callback),
358 // and dispatcher.pushMessage(message).
359 // Pass pushMessage to another function that needs a callback with a single string
360 // argument. Whenever dispatcher.pushMessage receives a string, the dispatcher will
361 // check for any regex matches and pass the string on to the corresponding callback(s).
362 io.callbackDispatcher = function() {
363 let callbackPairs = [],
364 removeCallback = function(aCallback) {
365 callbackPairs = callbackPairs.filter(function([regex, callback]) {
366 return callback !== aCallback;
369 addCallback = function(regex, callback) {
370 if (callback) {
371 callbackPairs.push([regex, callback]);
373 return function() {
374 removeCallback(callback);
377 pushMessage = function(message) {
378 for (let [regex, callback] of callbackPairs) {
379 if (message.match(regex)) {
380 callback(message);
384 return {
385 pushMessage,
386 removeCallback,
387 addCallback,
391 // __io.controlSocket(ipcFile, host, port, password)__.
392 // Instantiates and returns a socket to a tor ControlPort at ipcFile or
393 // host:port, authenticating with the given password. Example:
395 // // Open the socket
396 // let socket = await io.controlSocket(undefined, "127.0.0.1", 9151, "MyPassw0rd");
397 // // Send command and receive "250" response reply or error is thrown
398 // await socket.sendCommand(commandText);
399 // // Register or deregister for "650" notifications
400 // // that match regex
401 // socket.addNotificationCallback(regex, callback);
402 // socket.removeNotificationCallback(callback);
403 // // Close the socket permanently
404 // socket.close();
405 io.controlSocket = async function(ipcFile, host, port, password) {
406 let socket = new AsyncSocket(ipcFile, host, port);
407 let controlSocket = new ControlSocket(socket);
409 // Log in to control port.
410 await controlSocket.sendCommand("authenticate " + (password || ""));
411 // Activate needed events.
412 await controlSocket.sendCommand("setevents stream");
414 return controlSocket;
417 // ## utils
418 // A namespace for utility functions
419 let utils = {};
421 // __utils.identity(x)__.
422 // Returns its argument unchanged.
423 utils.identity = function(x) {
424 return x;
427 // __utils.isString(x)__.
428 // Returns true iff x is a string.
429 utils.isString = function(x) {
430 return typeof x === "string" || x instanceof String;
433 // __utils.capture(string, regex)__.
434 // Takes a string and returns an array of capture items, where regex must have a single
435 // capturing group and use the suffix /.../g to specify a global search.
436 utils.capture = function(string, regex) {
437 let matches = [];
438 // Special trick to use string.replace for capturing multiple matches.
439 string.replace(regex, function(a, captured) {
440 matches.push(captured);
442 return matches;
445 // __utils.extractor(regex)__.
446 // Returns a function that takes a string and returns an array of regex matches. The
447 // regex must use the suffix /.../g to specify a global search.
448 utils.extractor = function(regex) {
449 return function(text) {
450 return utils.capture(text, regex);
454 // __utils.splitLines(string)__.
455 // Splits a string into an array of strings, each corresponding to a line.
456 utils.splitLines = function(string) {
457 return string.split(/\r?\n/);
460 // __utils.splitAtSpaces(string)__.
461 // Splits a string into chunks between spaces. Does not split at spaces
462 // inside pairs of quotation marks.
463 utils.splitAtSpaces = utils.extractor(/((\S*?"(.*?)")+\S*|\S+)/g);
465 // __utils.splitAtFirst(string, regex)__.
466 // Splits a string at the first instance of regex match. If no match is
467 // found, returns the whole string.
468 utils.splitAtFirst = function(string, regex) {
469 let match = string.match(regex);
470 return match
472 string.substring(0, match.index),
473 string.substring(match.index + match[0].length),
475 : string;
478 // __utils.splitAtEquals(string)__.
479 // Splits a string into chunks between equals. Does not split at equals
480 // inside pairs of quotation marks.
481 utils.splitAtEquals = utils.extractor(/(([^=]*?"(.*?)")+[^=]*|[^=]+)/g);
483 // __utils.mergeObjects(arrayOfObjects)__.
484 // Takes an array of objects like [{"a":"b"},{"c":"d"}] and merges to a single object.
485 // Pure function.
486 utils.mergeObjects = function(arrayOfObjects) {
487 let result = {};
488 for (let obj of arrayOfObjects) {
489 for (let key in obj) {
490 result[key] = obj[key];
493 return result;
496 // __utils.listMapData(parameterString, listNames)__.
497 // Takes a list of parameters separated by spaces, of which the first several are
498 // unnamed, and the remainder are named, in the form `NAME=VALUE`. Apply listNames
499 // to the unnamed parameters, and combine them in a map with the named parameters.
500 // Example: `40 FAILED 0 95.78.59.36:80 REASON=CANT_ATTACH`
502 // utils.listMapData("40 FAILED 0 95.78.59.36:80 REASON=CANT_ATTACH",
503 // ["streamID", "event", "circuitID", "IP"])
504 // // --> {"streamID" : "40", "event" : "FAILED", "circuitID" : "0",
505 // // "address" : "95.78.59.36:80", "REASON" : "CANT_ATTACH"}"
506 utils.listMapData = function(parameterString, listNames) {
507 // Split out the space-delimited parameters.
508 let parameters = utils.splitAtSpaces(parameterString),
509 dataMap = {};
510 // Assign listNames to the first n = listNames.length parameters.
511 for (let i = 0; i < listNames.length; ++i) {
512 dataMap[listNames[i]] = parameters[i];
514 // Read key-value pairs and copy these to the dataMap.
515 for (let i = listNames.length; i < parameters.length; ++i) {
516 let [key, value] = utils.splitAtEquals(parameters[i]);
517 if (key && value) {
518 dataMap[key] = value;
521 return dataMap;
524 // __utils.rejectPromise(errorMessage)__.
525 // Returns a rejected promise with the given error message.
526 utils.rejectPromise = errorMessage => Promise.reject(new Error(errorMessage));
528 // ## info
529 // A namespace for functions related to tor's GETINFO and GETCONF command.
530 let info = {};
532 // __info.keyValueStringsFromMessage(messageText)__.
533 // Takes a message (text) response to GETINFO or GETCONF and provides
534 // a series of key-value strings, which are either multiline (with a `250+` prefix):
536 // 250+config/defaults=
537 // AccountingMax "0 bytes"
538 // AllowDotExit "0"
539 // .
541 // or single-line (with a `250-` or `250 ` prefix):
543 // 250-version=0.2.6.0-alpha-dev (git-b408125288ad6943)
544 info.keyValueStringsFromMessage = utils.extractor(
545 /^(250\+[\s\S]+?^\.|250[- ].+?)$/gim
548 // __info.applyPerLine(transformFunction)__.
549 // Returns a function that splits text into lines,
550 // and applies transformFunction to each line.
551 info.applyPerLine = function(transformFunction) {
552 return function(text) {
553 return utils.splitLines(text.trim()).map(transformFunction);
557 // __info.routerStatusParser(valueString)__.
558 // Parses a router status entry as, described in
559 // https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt
560 // (search for "router status entry")
561 info.routerStatusParser = function(valueString) {
562 let lines = utils.splitLines(valueString),
563 objects = [];
564 for (let line of lines) {
565 // Drop first character and grab data following it.
566 let myData = line.substring(2),
567 // Accumulate more maps with data, depending on the first character in the line.
568 dataFun = {
569 r: data =>
570 utils.listMapData(data, [
571 "nickname",
572 "identity",
573 "digest",
574 "publicationDate",
575 "publicationTime",
576 "IP",
577 "ORPort",
578 "DirPort",
580 a: data => ({ IPv6: data }),
581 s: data => ({ statusFlags: utils.splitAtSpaces(data) }),
582 v: data => ({ version: data }),
583 w: data => utils.listMapData(data, []),
584 p: data => ({ portList: data.split(",") }),
585 }[line.charAt(0)];
586 if (dataFun !== undefined) {
587 objects.push(dataFun(myData));
590 return utils.mergeObjects(objects);
593 // __info.circuitStatusParser(line)__.
594 // Parse the output of a circuit status line.
595 info.circuitStatusParser = function(line) {
596 let data = utils.listMapData(line, ["id", "status", "circuit"]),
597 circuit = data.circuit;
598 // Parse out the individual circuit IDs and names.
599 if (circuit) {
600 data.circuit = circuit.split(",").map(function(x) {
601 return x.split(/~|=/);
604 return data;
607 // __info.streamStatusParser(line)__.
608 // Parse the output of a stream status line.
609 info.streamStatusParser = function(text) {
610 return utils.listMapData(text, [
611 "StreamID",
612 "StreamStatus",
613 "CircuitID",
614 "Target",
618 // TODO: fix this parsing logic to handle bridgeLine correctly
619 // fingerprint/id is an optional parameter
620 // __info.bridgeParser(bridgeLine)__.
621 // Takes a single line from a `getconf bridge` result and returns
622 // a map containing the bridge's type, address, and ID.
623 info.bridgeParser = function(bridgeLine) {
624 let result = {},
625 tokens = bridgeLine.split(/\s+/);
626 // First check if we have a "vanilla" bridge:
627 if (tokens[0].match(/^\d+\.\d+\.\d+\.\d+/)) {
628 result.type = "vanilla";
629 [result.address, result.ID] = tokens;
630 // Several bridge types have a similar format:
631 } else {
632 result.type = tokens[0];
633 if (
635 "flashproxy",
636 "fte",
637 "meek",
638 "meek_lite",
639 "obfs3",
640 "obfs4",
641 "scramblesuit",
642 "snowflake",
643 ].includes(result.type)
645 [result.address, result.ID] = tokens.slice(1);
648 return result.type ? result : null;
651 // __info.parsers__.
652 // A map of GETINFO and GETCONF keys to parsing function, which convert
653 // result strings to JavaScript data.
654 info.parsers = {
655 "ns/id/": info.routerStatusParser,
656 "ip-to-country/": utils.identity,
657 "circuit-status": info.applyPerLine(info.circuitStatusParser),
658 bridge: info.bridgeParser,
659 // Currently unused parsers:
660 // "ns/name/" : info.routerStatusParser,
661 // "stream-status" : info.applyPerLine(info.streamStatusParser),
662 // "version" : utils.identity,
663 // "config-file" : utils.identity,
666 // __info.getParser(key)__.
667 // Takes a key and determines the parser function that should be used to
668 // convert its corresponding valueString to JavaScript data.
669 info.getParser = function(key) {
670 return (
671 info.parsers[key] ||
672 info.parsers[key.substring(0, key.lastIndexOf("/") + 1)]
676 // __info.stringToValue(string)__.
677 // Converts a key-value string as from GETINFO or GETCONF to a value.
678 info.stringToValue = function(string) {
679 // key should look something like `250+circuit-status=` or `250-circuit-status=...`
680 // or `250 circuit-status=...`
681 let matchForKey = string.match(/^250[ +-](.+?)=/),
682 key = matchForKey ? matchForKey[1] : null;
683 if (key === null) {
684 return null;
686 // matchResult finds a single-line result for `250-` or `250 `,
687 // or a multi-line one for `250+`.
688 let matchResult =
689 string.match(/^250[ -].+?=(.*)$/) ||
690 string.match(/^250\+.+?=([\s\S]*?)^\.$/m),
691 // Retrieve the captured group (the text of the value in the key-value pair)
692 valueString = matchResult ? matchResult[1] : null,
693 // Get the parser function for the key found.
694 parse = info.getParser(key.toLowerCase());
695 if (parse === undefined) {
696 throw new Error("No parser found for '" + key + "'");
698 // Return value produced by the parser.
699 return parse(valueString);
702 // __info.getMultipleResponseValues(message)__.
703 // Process multiple responses to a GETINFO or GETCONF request.
704 info.getMultipleResponseValues = function(message) {
705 return info
706 .keyValueStringsFromMessage(message)
707 .map(info.stringToValue)
708 .filter(utils.identity);
711 // __info.getInfo(controlSocket, key)__.
712 // Sends GETINFO for a single key. Returns a promise with the result.
713 info.getInfo = function(aControlSocket, key) {
714 if (!utils.isString(key)) {
715 return utils.rejectPromise("key argument should be a string");
717 return aControlSocket
718 .sendCommand("getinfo " + key)
719 .then(response => info.getMultipleResponseValues(response)[0]);
722 // __info.getConf(aControlSocket, key)__.
723 // Sends GETCONF for a single key. Returns a promise with the result.
724 info.getConf = function(aControlSocket, key) {
725 // GETCONF with a single argument returns results with
726 // one or more lines that look like `250[- ]key=value`.
727 // Any GETCONF lines that contain a single keyword only are currently dropped.
728 // So we can use similar parsing to that for getInfo.
729 if (!utils.isString(key)) {
730 return utils.rejectPromise("key argument should be a string");
732 return aControlSocket
733 .sendCommand("getconf " + key)
734 .then(info.getMultipleResponseValues);
737 // ## onionAuth
738 // A namespace for functions related to tor's ONION_CLIENT_AUTH_* commands.
739 let onionAuth = {};
741 onionAuth.keyInfoStringsFromMessage = utils.extractor(/^250-CLIENT\s+(.+)$/gim);
743 onionAuth.keyInfoObjectsFromMessage = function(message) {
744 let keyInfoStrings = onionAuth.keyInfoStringsFromMessage(message);
745 return keyInfoStrings.map(infoStr =>
746 utils.listMapData(infoStr, ["hsAddress", "typeAndKey"])
750 // __onionAuth.viewKeys()__.
751 // Sends a ONION_CLIENT_AUTH_VIEW command to retrieve the list of private keys.
752 // Returns a promise that is fulfilled with an array of key info objects which
753 // contain the following properties:
754 // hsAddress
755 // typeAndKey
756 // Flags (e.g., "Permanent")
757 onionAuth.viewKeys = function(aControlSocket) {
758 let cmd = "onion_client_auth_view";
759 return aControlSocket
760 .sendCommand(cmd)
761 .then(onionAuth.keyInfoObjectsFromMessage);
764 // __onionAuth.add(controlSocket, hsAddress, b64PrivateKey, isPermanent)__.
765 // Sends a ONION_CLIENT_AUTH_ADD command to add a private key to the
766 // Tor configuration.
767 onionAuth.add = function(
768 aControlSocket,
769 hsAddress,
770 b64PrivateKey,
771 isPermanent
773 if (!utils.isString(hsAddress)) {
774 return utils.rejectPromise("hsAddress argument should be a string");
777 if (!utils.isString(b64PrivateKey)) {
778 return utils.rejectPromise("b64PrivateKey argument should be a string");
781 const keyType = "x25519";
782 let cmd = `onion_client_auth_add ${hsAddress} ${keyType}:${b64PrivateKey}`;
783 if (isPermanent) {
784 cmd += " Flags=Permanent";
786 return aControlSocket.sendCommand(cmd);
789 // __onionAuth.remove(controlSocket, hsAddress)__.
790 // Sends a ONION_CLIENT_AUTH_REMOVE command to remove a private key from the
791 // Tor configuration.
792 onionAuth.remove = function(aControlSocket, hsAddress) {
793 if (!utils.isString(hsAddress)) {
794 return utils.rejectPromise("hsAddress argument should be a string");
797 let cmd = `onion_client_auth_remove ${hsAddress}`;
798 return aControlSocket.sendCommand(cmd);
801 // ## event
802 // Handlers for events
804 let event = {};
806 // __event.parsers__.
807 // A map of EVENT keys to parsing functions, which convert result strings to JavaScript
808 // data.
809 event.parsers = {
810 stream: info.streamStatusParser,
811 // Currently unused:
812 // "circ" : info.circuitStatusParser,
815 // __event.messageToData(type, message)__.
816 // Extract the data from an event. Note, at present
817 // we only extract streams that look like `"650" SP...`
818 event.messageToData = function(type, message) {
819 let dataText = message.match(/^650 \S+?\s(.*)/m)[1];
820 return dataText && type.toLowerCase() in event.parsers
821 ? event.parsers[type.toLowerCase()](dataText)
822 : null;
825 // __event.watchEvent(controlSocket, type, filter, onData)__.
826 // Watches for a particular type of event. If filter(data) returns true, the event's
827 // data is passed to the onData callback. Returns a zero arg function that
828 // stops watching the event. Note: we only observe `"650" SP...` events
829 // currently (no `650+...` or `650-...` events).
830 event.watchEvent = function(controlSocket, type, filter, onData, raw = false) {
831 return controlSocket.addNotificationCallback(
832 new RegExp("^650 " + type),
833 function(message) {
834 let data = event.messageToData(type, message);
835 if (filter === null || filter(data)) {
836 if (raw || !data) {
837 onData(message);
838 return;
840 onData(data);
846 // ## tor
847 // Things related to the main controller.
848 let tor = {};
850 // __tor.controllerCache__.
851 // A map from "unix:socketpath" or "host:port" to controller objects. Prevents
852 // redundant instantiation of control sockets.
853 tor.controllerCache = new Map();
855 // __tor.controller(ipcFile, host, port, password)__.
856 // Creates a tor controller at the given ipcFile or host and port, with the
857 // given password.
858 tor.controller = async function(ipcFile, host, port, password) {
859 let socket = await io.controlSocket(ipcFile, host, port, password);
860 return {
861 getInfo: key => info.getInfo(socket, key),
862 getConf: key => info.getConf(socket, key),
863 onionAuthViewKeys: () => onionAuth.viewKeys(socket),
864 onionAuthAdd: (hsAddress, b64PrivateKey, isPermanent) =>
865 onionAuth.add(socket, hsAddress, b64PrivateKey, isPermanent),
866 onionAuthRemove: hsAddress => onionAuth.remove(socket, hsAddress),
867 watchEvent: (type, filter, onData, raw = false) =>
868 event.watchEvent(socket, type, filter, onData, raw),
869 isOpen: () => socket.isOpen(),
870 close: () => {
871 socket.close();
873 sendCommand: cmd => socket.sendCommand(cmd),
877 // ## Export
879 let controlPortInfo = {};
881 // __configureControlPortModule(ipcFile, host, port, password)__.
882 // Sets Tor control port connection parameters to be used in future calls to
883 // the controller() function. Example:
884 // configureControlPortModule(undefined, "127.0.0.1", 9151, "MyPassw0rd");
885 var configureControlPortModule = function(ipcFile, host, port, password) {
886 controlPortInfo.ipcFile = ipcFile;
887 controlPortInfo.host = host;
888 controlPortInfo.port = port || 9151;
889 controlPortInfo.password = password;
892 // __controller(avoidCache)__.
893 // Instantiates and returns a controller object that is connected and
894 // authenticated to a Tor ControlPort using the connection parameters
895 // provided in the most recent call to configureControlPortModule(), if
896 // the controller doesn't yet exist. Otherwise returns the existing
897 // controller to the given ipcFile or host:port. Throws on error.
899 // Example:
901 // // Get a new controller
902 // const avoidCache = true;
903 // let c = controller(avoidCache);
904 // // Send command and receive `250` reply or error message in a promise:
905 // let replyPromise = c.getInfo("ip-to-country/16.16.16.16");
906 // // Close the controller permanently
907 // c.close();
908 var controller = async function(avoidCache) {
909 if (!controlPortInfo.ipcFile && !controlPortInfo.host) {
910 throw new Error("Please call configureControlPortModule first");
913 const dest = controlPortInfo.ipcFile
914 ? `unix:${controlPortInfo.ipcFile.path}`
915 : `${controlPortInfo.host}:${controlPortInfo.port}`;
917 // constructor shorthand
918 const newTorController = async () => {
919 return tor.controller(
920 controlPortInfo.ipcFile,
921 controlPortInfo.host,
922 controlPortInfo.port,
923 controlPortInfo.password
927 // avoid cache so always return a new controller
928 if (avoidCache) {
929 return newTorController();
932 // first check our cache and see if we already have one
933 let cachedController = tor.controllerCache.get(dest);
934 if (cachedController && cachedController.isOpen()) {
935 return cachedController;
938 // create a new one and store in the map
939 cachedController = await newTorController();
940 // overwrite the close() function to prevent consumers from closing a shared/cached controller
941 cachedController.close = () => {
942 throw new Error("May not close cached Tor Controller as it may be in use");
945 tor.controllerCache.set(dest, cachedController);
946 return cachedController;
949 // __wait_for_controller(avoidCache)
950 // Same as controller() function, but explicitly waits until there is a tor daemon
951 // to connect to (either launched by tor-launcher, or if we have an existing system
952 // tor daemon)
953 var wait_for_controller = function(avoidCache) {
954 // if tor process is running (either ours or system) immediately return controller
955 if (!TorMonitorService.ownsTorDaemon || TorMonitorService.isRunning) {
956 return controller(avoidCache);
959 // otherwise we must wait for tor to finish launching before resolving
960 return new Promise((resolve, reject) => {
961 let observer = {
962 observe: async (subject, topic, data) => {
963 if (topic === TorTopics.ProcessIsReady) {
964 try {
965 resolve(await controller(avoidCache));
966 } catch (err) {
967 reject(err);
969 Services.obs.removeObserver(observer, TorTopics.ProcessIsReady);
973 Services.obs.addObserver(observer, TorTopics.ProcessIsReady);
977 // Export functions for external use.
978 var EXPORTED_SYMBOLS = [
979 "configureControlPortModule",
980 "controller",
981 "wait_for_controller",