1 // A module for TorBrowser that provides an asynchronous controller for
2 // Tor, through its ControlPort.
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.
8 // To import the module, use
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 */
26 // ### Import Mozilla Services
27 const { Services
} = ChromeUtils
.import("resource://gre/modules/Services.jsm");
29 ChromeUtils
.defineModuleGetter(
32 "resource://gre/modules/TorMonitorService.jsm"
35 // tor-launcher observer topics
36 const TorTopics
= Object
.freeze({
37 ProcessIsReady
: "TorProcessIsReady",
42 let logger
= Cc
["@torproject.org/torbutton-logger;1"].getService(Ci
.nsISupports
)
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");
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
);
75 // asynchronously write string to underlying socket and return number of bytes written
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
: () => {
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
104 // finally resolve promise
105 resolve(bytesWritten
);
107 // reject promise on error
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) {
121 // asynchronously read string from underlying socket and return it
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
=> {
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
149 // finally resolve promise
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) {
166 this.outputStream
.close();
167 this.inputStream
.close();
171 class ControlSocket
{
172 constructor(asyncSocket
) {
173 this.socket
= asyncSocket
;
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(
183 this._handleNotification
.bind(this)
185 // callback for handling responses and errors
186 this.mainDispatcher
.addCallback(
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
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;
224 const line
= await
this._readLine();
227 if (handlingMultlineValue
) {
228 // look for end of multiline
229 if (line
.match(/^\.$/)) {
230 handlingMultlineValue
= false;
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;
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() {
263 let message
= await
this._readMessage();
264 log("controlPort >> " + message
);
265 this.mainDispatcher
.pushMessage(message
);
268 this._isOpen
= false;
269 for (const cmd
of this.commandQueue
) {
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
) => {
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(" ");
312 myErr
.torStatusCode
= message
.substring(0, idx
);
313 myErr
.torMessage
= message
.substring(idx
);
315 myErr
.torStatusCode
= message
;
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
);
338 this._isOpen
= false;
341 addNotificationCallback(regex
, callback
) {
342 this.notificationDispatcher
.addCallback(regex
, callback
);
351 // I/O utilities namespace
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
) {
371 callbackPairs
.push([regex
, callback
]);
374 removeCallback(callback
);
377 pushMessage = function(message
) {
378 for (let [regex
, callback
] of callbackPairs
) {
379 if (message
.match(regex
)) {
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
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
;
418 // A namespace for utility functions
421 // __utils.identity(x)__.
422 // Returns its argument unchanged.
423 utils
.identity = function(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
) {
438 // Special trick to use string.replace for capturing multiple matches.
439 string
.replace(regex
, function(a
, captured
) {
440 matches
.push(captured
);
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
);
472 string
.substring(0, match
.index
),
473 string
.substring(match
.index
+ match
[0].length
),
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.
486 utils
.mergeObjects = function(arrayOfObjects
) {
488 for (let obj
of arrayOfObjects
) {
489 for (let key
in obj
) {
490 result
[key
] = obj
[key
];
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
),
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
]);
518 dataMap
[key
] = value
;
524 // __utils.rejectPromise(errorMessage)__.
525 // Returns a rejected promise with the given error message.
526 utils
.rejectPromise
= errorMessage
=> Promise
.reject(new Error(errorMessage
));
529 // A namespace for functions related to tor's GETINFO and GETCONF command.
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"
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
),
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.
570 utils
.listMapData(data
, [
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(",") }),
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.
600 data
.circuit
= circuit
.split(",").map(function(x
) {
601 return x
.split(/~|=/);
607 // __info.streamStatusParser(line)__.
608 // Parse the output of a stream status line.
609 info
.streamStatusParser = function(text
) {
610 return utils
.listMapData(text
, [
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
) {
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:
632 result
.type
= tokens
[0];
643 ].includes(result
.type
)
645 [result
.address
, result
.ID
] = tokens
.slice(1);
648 return result
.type
? result
: null;
652 // A map of GETINFO and GETCONF keys to parsing function, which convert
653 // result strings to JavaScript data.
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
) {
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;
686 // matchResult finds a single-line result for `250-` or `250 `,
687 // or a multi-line one for `250+`.
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
) {
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
);
738 // A namespace for functions related to tor's ONION_CLIENT_AUTH_* commands.
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:
756 // Flags (e.g., "Permanent")
757 onionAuth
.viewKeys = function(aControlSocket
) {
758 let cmd
= "onion_client_auth_view";
759 return aControlSocket
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(
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}`;
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
);
802 // Handlers for events
806 // __event.parsers__.
807 // A map of EVENT keys to parsing functions, which convert result strings to JavaScript
810 stream
: info
.streamStatusParser
,
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
)
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
),
834 let data
= event
.messageToData(type
, message
);
835 if (filter
=== null || filter(data
)) {
847 // Things related to the main controller.
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
858 tor
.controller
= async
function(ipcFile
, host
, port
, password
) {
859 let socket
= await io
.controlSocket(ipcFile
, host
, port
, password
);
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(),
873 sendCommand
: cmd
=> socket
.sendCommand(cmd
),
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.
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
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
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
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
) => {
962 observe
: async (subject
, topic
, data
) => {
963 if (topic
=== TorTopics
.ProcessIsReady
) {
965 resolve(await
controller(avoidCache
));
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",
981 "wait_for_controller",