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 { controller } = Components.utils.import("path/to/tor-control-port.js");
12 // See the last function defined in this file:
13 // controller(socketFile, host, port, password, onError)
14 // for usage of the controller function.
16 /* jshint esnext: true */
18 /* global Components, console, Services */
21 // ### Mozilla Abbreviations
22 let {classes: Cc, interfaces: Ci, results: Cr, Constructor: CC, utils: Cu } = Components;
24 // ### Import Mozilla Services
25 Cu.import("resource://gre/modules/Services.jsm");
30 if ((typeof console) !== "undefined") {
31 log = x => console.log(typeof(x) === "string" ? x.trimRight().replace(/\r\n/g, "\n") : JSON.stringify(x));
33 let logger = Cc["@torproject.org/torbutton-logger;1"]
34 .getService(Components.interfaces.nsISupports).wrappedJSObject;
35 log = x => logger.eclog(3, x.trimRight().replace(/\r\n/g, "\n"));
38 // ### announce this file
39 log("Loading tor-control-port.js\n");
42 // I/O utilities namespace
45 // __io.asyncSocketStreams(socketFile, host, port)__.
46 // Creates a pair of asynchronous input and output streams for a socket at the
47 // given socketFile or host and port.
48 io.asyncSocketStreams = function (socketFile, host, port) {
49 let sts = Cc["@mozilla.org/network/socket-transport-service;1"]
50 .getService(Components.interfaces.nsISocketTransportService),
51 UNBUFFERED = Ci.nsITransport.OPEN_UNBUFFERED;
53 // Create an instance of a socket transport.
56 socketTransport = sts.createUnixDomainTransport(socketFile);
58 socketTransport = sts.createTransport(null, 0, host, port, null);
61 // Open unbuffered asynchronous outputStream.
62 let outputStream = socketTransport.openOutputStream(UNBUFFERED, 1, 1)
63 .QueryInterface(Ci.nsIAsyncOutputStream),
64 // Open unbuffered asynchronous inputStream.
65 inputStream = socketTransport.openInputStream(UNBUFFERED, 1, 1)
66 .QueryInterface(Ci.nsIAsyncInputStream);
67 return [inputStream, outputStream];
70 // __io.pumpInputStream(scriptableInputStream, onInputData, onError)__.
71 // Run an "input stream pump" that takes an input stream and
72 // asynchronously pumps incoming data to the onInputData callback.
73 io.pumpInputStream = function (inputStream, onInputData, onError) {
74 // Wrap raw inputStream with a "ScriptableInputStream" so we can read incoming data.
75 let ScriptableInputStream = Components.Constructor(
76 "@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init"),
77 scriptableInputStream = new ScriptableInputStream(inputStream),
78 // A private method to read all data available on the input stream.
79 readAll = function() {
80 return scriptableInputStream.read(scriptableInputStream.available());
82 pump = Cc["@mozilla.org/network/input-stream-pump;1"]
83 .createInstance(Components.interfaces.nsIInputStreamPump);
85 pump.init(inputStream, -1, -1, 0, 0, true);
86 // Tell the pump to read all data whenever it is available, and pass the data
87 // to the onInputData callback. The first argument to asyncRead implements
89 pump.asyncRead({ onStartRequest: function (request, context) { },
90 onStopRequest: function (request, context, code) { },
91 onDataAvailable : function (request, context, stream, offset, count) {
93 onInputData(readAll());
95 // readAll() or onInputData(...) has thrown an error.
96 // Notify calling code through onError.
102 // __io.asyncSocket(socketFile, host, port, onInputData, onError)__.
103 // Creates an asynchronous, text-oriented UNIX domain socket (if socketFile
104 // is defined) or TCP socket at host:port.
105 // The onInputData callback should accept a single argument, which will be called
106 // repeatedly, whenever incoming text arrives. Returns a socket object with two methods:
107 // socket.write(text) and socket.close(). onError will be passed the error object
108 // whenever a write fails.
109 io.asyncSocket = function (socketFile, host, port, onInputData, onError) {
110 let [inputStream, outputStream] = io.asyncSocketStreams(socketFile, host,
113 // Run an input stream pump to send incoming data to the onInputData callback.
114 io.pumpInputStream(inputStream, onInputData, onError);
115 // Return the "socket object" as described.
117 // Write a message to the socket.
118 write : function(aString) {
119 pendingWrites.push(aString);
120 outputStream.asyncWait(
121 // Implement an nsIOutputStreamCallback:
122 { onOutputStreamReady : function () {
123 let totalString = pendingWrites.join("");
125 outputStream.write(totalString, totalString.length);
126 log("controlPort << " + totalString);
132 0, 0, Services.tm.currentThread);
135 close : function () {
136 // Close stream objects.
138 outputStream.close();
143 // __io.onDataFromOnLine(onLine)__.
144 // Converts a callback that expects incoming individual lines of text to a callback that
145 // expects incoming raw socket string data.
146 io.onDataFromOnLine = function (onLine) {
147 // A private variable that stores the last unfinished line.
148 let pendingData = "";
149 // Return a callback to be passed to io.asyncSocket. First, splits data into lines of
150 // text. If the incoming data is not terminated by CRLF, then the last
151 // unfinished line will be stored in pendingData, to be prepended to the data in the
152 // next call to onData. The already complete lines of text are then passed in sequence
154 return function (data) {
155 let totalData = pendingData + data,
156 lines = totalData.split("\r\n"),
158 pendingData = lines[n - 1];
159 // Call onLine for all completed lines.
160 lines.slice(0,-1).map(onLine);
164 // __io.onLineFromOnMessage(onMessage)__.
165 // Converts a callback that expects incoming control port multiline message strings to a
166 // callback that expects individual lines.
167 io.onLineFromOnMessage = function (onMessage) {
168 // A private variable that stores the last unfinished line.
169 let pendingLines = [],
170 // A private variable to monitor whether we are receiving a multiline
171 // value, beginning with ###+ and ending with a single ".".
172 multilineValueInProgress = false;
173 // Return a callback that expects individual lines.
174 return function (line) {
175 // Add to the list of pending lines.
176 pendingLines.push(line);
177 // 'Multiline values' are possible. We avoid interrupting one by detecting it
178 // and waiting for a terminating "." on its own line.
179 // (See control-spec section 3.9 and https://trac.torproject.org/16990#comment:28
180 if (line.match(/^\d\d\d\+.+?=$/) && pendingLines.length === 1) {
181 multilineValueInProgress = true;
183 if (multilineValueInProgress && line.match(/^\.$/)) {
184 multilineValueInProgress = false;
186 // If line is the last in a message, then pass on the full multiline message.
187 if (!multilineValueInProgress &&
188 line.match(/^\d\d\d /) &&
189 (pendingLines.length === 1 ||
190 pendingLines[0].substring(0,3) === line.substring(0,3))) {
191 // Combine pending lines to form message.
192 let message = pendingLines.join("\r\n");
193 log("controlPort >> " + message);
194 // Wipe pendingLines before we call onMessage, in case onMessage throws an error.
196 // Pass multiline message to onMessage.
202 // __io.callbackDispatcher()__.
203 // Returns dispatcher object with three member functions:
204 // dispatcher.addCallback(regex, callback), dispatcher.removeCallback(callback),
205 // and dispatcher.pushMessage(message).
206 // Pass pushMessage to another function that needs a callback with a single string
207 // argument. Whenever dispatcher.pushMessage receives a string, the dispatcher will
208 // check for any regex matches and pass the string on to the corresponding callback(s).
209 io.callbackDispatcher = function () {
210 let callbackPairs = [],
211 removeCallback = function (aCallback) {
212 callbackPairs = callbackPairs.filter(function ([regex, callback]) {
213 return callback !== aCallback;
216 addCallback = function (regex, callback) {
218 callbackPairs.push([regex, callback]);
220 return function () { removeCallback(callback); };
222 pushMessage = function (message) {
223 for (let [regex, callback] of callbackPairs) {
224 if (message.match(regex)) {
229 return { pushMessage : pushMessage, removeCallback : removeCallback,
230 addCallback : addCallback };
233 // __io.matchRepliesToCommands(asyncSend, dispatcher)__.
234 // Takes asyncSend(message), an asynchronous send function, and the callback
235 // dispatcher, and returns a function Promise<response> sendCommand(command).
236 io.matchRepliesToCommands = function (asyncSend, dispatcher) {
237 let commandQueue = [],
238 sendCommand = function (command, replyCallback, errorCallback) {
239 commandQueue.push([command, replyCallback, errorCallback]);
242 // Watch for responses (replies or error messages)
243 dispatcher.addCallback(/^[245]\d\d/, function (message) {
244 let [command, replyCallback, errorCallback] = commandQueue.shift();
245 if (message.match(/^2/) && replyCallback) replyCallback(message);
246 if (message.match(/^[45]/) && errorCallback) {
247 errorCallback(new Error(command + " -> " + message));
250 // Create and return a version of sendCommand that returns a Promise.
251 return command => new Promise(function (replyCallback, errorCallback) {
252 sendCommand(command, replyCallback, errorCallback);
256 // __io.controlSocket(socketFile, host, port, password, onError)__.
257 // Instantiates and returns a socket to a tor ControlPort at socketFile or
258 // host:port, authenticating with the given password. onError is called with an
259 // error object as its single argument whenever an error occurs. Example:
261 // // Open the socket
262 // let socket = controlSocket(undefined, "127.0.0.1", 9151, "MyPassw0rd",
263 // function (error) { console.log(error.message || error); });
264 // // Send command and receive "250" reply or error message
265 // socket.sendCommand(commandText, replyCallback, errorCallback);
266 // // Register or deregister for "650" notifications
267 // // that match regex
268 // socket.addNotificationCallback(regex, callback);
269 // socket.removeNotificationCallback(callback);
270 // // Close the socket permanently
272 io.controlSocket = function (socketFile, host, port, password, onError) {
273 // Produce a callback dispatcher for Tor messages.
274 let mainDispatcher = io.callbackDispatcher(),
275 // Open the socket and convert format to Tor messages.
276 socket = io.asyncSocket(socketFile, host, port,
278 io.onLineFromOnMessage(mainDispatcher.pushMessage)),
280 // Controllers should send commands terminated by CRLF.
281 writeLine = function (text) { socket.write(text + "\r\n"); },
282 // Create a sendCommand method from writeLine.
283 sendCommand = io.matchRepliesToCommands(writeLine, mainDispatcher),
284 // Create a secondary callback dispatcher for Tor notification messages.
285 notificationDispatcher = io.callbackDispatcher();
286 // Pass asynchronous notifications to notification dispatcher.
287 mainDispatcher.addCallback(/^650/, notificationDispatcher.pushMessage);
288 // Log in to control port.
289 sendCommand("authenticate " + (password || ""));
290 // Activate needed events.
291 sendCommand("setevents stream");
292 return { close : socket.close, sendCommand : sendCommand,
293 addNotificationCallback : notificationDispatcher.addCallback,
294 removeNotificationCallback : notificationDispatcher.removeCallback };
298 // A namespace for utility functions
301 // __utils.identity(x)__.
302 // Returns its argument unchanged.
303 utils.identity = function (x) { return x; };
305 // __utils.isString(x)__.
306 // Returns true iff x is a string.
307 utils.isString = function (x) {
308 return typeof(x) === 'string' || x instanceof String;
311 // __utils.capture(string, regex)__.
312 // Takes a string and returns an array of capture items, where regex must have a single
313 // capturing group and use the suffix /.../g to specify a global search.
314 utils.capture = function (string, regex) {
316 // Special trick to use string.replace for capturing multiple matches.
317 string.replace(regex, function (a, captured) {
318 matches.push(captured);
323 // __utils.extractor(regex)__.
324 // Returns a function that takes a string and returns an array of regex matches. The
325 // regex must use the suffix /.../g to specify a global search.
326 utils.extractor = function (regex) {
327 return function (text) {
328 return utils.capture(text, regex);
332 // __utils.splitLines(string)__.
333 // Splits a string into an array of strings, each corresponding to a line.
334 utils.splitLines = function (string) { return string.split(/\r?\n/); };
336 // __utils.splitAtSpaces(string)__.
337 // Splits a string into chunks between spaces. Does not split at spaces
338 // inside pairs of quotation marks.
339 utils.splitAtSpaces = utils.extractor(/((\S*?"(.*?)")+\S*|\S+)/g);
341 // __utils.splitAtFirst(string, regex)__.
342 // Splits a string at the first instance of regex match. If no match is
343 // found, returns the whole string.
344 utils.splitAtFirst = function (string, regex) {
345 let match = string.match(regex);
346 return match ? [ string.substring(0, match.index),
347 string.substring(match.index + match[0].length) ]
351 // __utils.splitAtEquals(string)__.
352 // Splits a string into chunks between equals. Does not split at equals
353 // inside pairs of quotation marks.
354 utils.splitAtEquals = utils.extractor(/(([^=]*?"(.*?)")+[^=]*|[^=]+)/g);
356 // __utils.mergeObjects(arrayOfObjects)__.
357 // Takes an array of objects like [{"a":"b"},{"c":"d"}] and merges to a single object.
359 utils.mergeObjects = function (arrayOfObjects) {
361 for (let obj of arrayOfObjects) {
362 for (let key in obj) {
363 result[key] = obj[key];
369 // __utils.listMapData(parameterString, listNames)__.
370 // Takes a list of parameters separated by spaces, of which the first several are
371 // unnamed, and the remainder are named, in the form `NAME=VALUE`. Apply listNames
372 // to the unnamed parameters, and combine them in a map with the named parameters.
373 // Example: `40 FAILED 0 95.78.59.36:80 REASON=CANT_ATTACH`
375 // utils.listMapData("40 FAILED 0 95.78.59.36:80 REASON=CANT_ATTACH",
376 // ["streamID", "event", "circuitID", "IP"])
377 // // --> {"streamID" : "40", "event" : "FAILED", "circuitID" : "0",
378 // // "address" : "95.78.59.36:80", "REASON" : "CANT_ATTACH"}"
379 utils.listMapData = function (parameterString, listNames) {
380 // Split out the space-delimited parameters.
381 let parameters = utils.splitAtSpaces(parameterString),
383 // Assign listNames to the first n = listNames.length parameters.
384 for (let i = 0; i < listNames.length; ++i) {
385 dataMap[listNames[i]] = parameters[i];
387 // Read key-value pairs and copy these to the dataMap.
388 for (let i = listNames.length; i < parameters.length; ++i) {
389 let [key, value] = utils.splitAtEquals(parameters[i]);
391 dataMap[key] = value;
397 // __utils.rejectPromise(errorMessage)__.
398 // Returns a rejected promise with the given error message.
399 utils.rejectPromise = errorMessage => Promise.reject(new Error(errorMessage));
402 // A namespace for functions related to tor's GETINFO and GETCONF command.
405 // __info.keyValueStringsFromMessage(messageText)__.
406 // Takes a message (text) response to GETINFO or GETCONF and provides
407 // a series of key-value strings, which are either multiline (with a `250+` prefix):
409 // 250+config/defaults=
410 // AccountingMax "0 bytes"
414 // or single-line (with a `250-` or `250 ` prefix):
416 // 250-version=0.2.6.0-alpha-dev (git-b408125288ad6943)
417 info.keyValueStringsFromMessage = utils.extractor(/^(250\+[\s\S]+?^\.|250[- ].+?)$/gmi);
419 // __info.applyPerLine(transformFunction)__.
420 // Returns a function that splits text into lines,
421 // and applies transformFunction to each line.
422 info.applyPerLine = function (transformFunction) {
423 return function (text) {
424 return utils.splitLines(text.trim()).map(transformFunction);
428 // __info.routerStatusParser(valueString)__.
429 // Parses a router status entry as, described in
430 // https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt
431 // (search for "router status entry")
432 info.routerStatusParser = function (valueString) {
433 let lines = utils.splitLines(valueString),
435 for (let line of lines) {
436 // Drop first character and grab data following it.
437 let myData = line.substring(2),
438 // Accumulate more maps with data, depending on the first character in the line.
440 "r" : data => utils.listMapData(data, ["nickname", "identity", "digest",
441 "publicationDate", "publicationTime",
442 "IP", "ORPort", "DirPort"]),
443 "a" : data => ({ "IPv6" : data }),
444 "s" : data => ({ "statusFlags" : utils.splitAtSpaces(data) }),
445 "v" : data => ({ "version" : data }),
446 "w" : data => utils.listMapData(data, []),
447 "p" : data => ({ "portList" : data.split(",") }),
449 if (dataFun !== undefined) {
450 objects.push(dataFun(myData));
453 return utils.mergeObjects(objects);
456 // __info.circuitStatusParser(line)__.
457 // Parse the output of a circuit status line.
458 info.circuitStatusParser = function (line) {
459 let data = utils.listMapData(line, ["id","status","circuit"]),
460 circuit = data.circuit;
461 // Parse out the individual circuit IDs and names.
463 data.circuit = circuit.split(",").map(function (x) {
464 return x.split(/~|=/);
470 // __info.streamStatusParser(line)__.
471 // Parse the output of a stream status line.
472 info.streamStatusParser = function (text) {
473 return utils.listMapData(text, ["StreamID", "StreamStatus",
474 "CircuitID", "Target"]);
477 // __info.bridgeParser(bridgeLine)__.
478 // Takes a single line from a `getconf bridge` result and returns
479 // a map containing the bridge's type, address, and ID.
480 info.bridgeParser = function(bridgeLine) {
482 tokens = bridgeLine.split(/\s+/);
483 // First check if we have a "vanilla" bridge:
484 if (tokens[0].match(/^\d+\.\d+\.\d+\.\d+/)) {
485 result.type = "vanilla";
486 [result.address, result.ID] = tokens;
487 // Several bridge types have a similar format:
489 result.type = tokens[0];
490 if (["flashproxy", "fte", "meek", "obfs3", "obfs4", "scramblesuit"]
491 .indexOf(result.type) >= 0) {
492 [result.address, result.ID] = tokens.slice(1);
495 return result.type ? result : null;
499 // A map of GETINFO and GETCONF keys to parsing function, which convert
500 // result strings to JavaScript data.
502 "ns/id/" : info.routerStatusParser,
503 "ip-to-country/" : utils.identity,
504 "circuit-status" : info.applyPerLine(info.circuitStatusParser),
505 "bridge" : info.bridgeParser,
506 // Currently unused parsers:
507 // "ns/name/" : info.routerStatusParser,
508 // "stream-status" : info.applyPerLine(info.streamStatusParser),
509 // "version" : utils.identity,
510 // "config-file" : utils.identity,
513 // __info.getParser(key)__.
514 // Takes a key and determines the parser function that should be used to
515 // convert its corresponding valueString to JavaScript data.
516 info.getParser = function(key) {
517 return info.parsers[key] ||
518 info.parsers[key.substring(0, key.lastIndexOf("/") + 1)];
521 // __info.stringToValue(string)__.
522 // Converts a key-value string as from GETINFO or GETCONF to a value.
523 info.stringToValue = function (string) {
524 // key should look something like `250+circuit-status=` or `250-circuit-status=...`
525 // or `250 circuit-status=...`
526 let matchForKey = string.match(/^250[ +-](.+?)=/),
527 key = matchForKey ? matchForKey[1] : null;
528 if (key === null) return null;
529 // matchResult finds a single-line result for `250-` or `250 `,
530 // or a multi-line one for `250+`.
531 let matchResult = string.match(/^250[ -].+?=(.*)$/) ||
532 string.match(/^250\+.+?=([\s\S]*?)^\.$/m),
533 // Retrieve the captured group (the text of the value in the key-value pair)
534 valueString = matchResult ? matchResult[1] : null,
535 // Get the parser function for the key found.
536 parse = info.getParser(key.toLowerCase());
537 if (parse === undefined) {
538 throw new Error("No parser found for '" + key + "'");
540 // Return value produced by the parser.
541 return parse(valueString);
544 // __info.getMultipleResponseValues(message)__.
545 // Process multiple responses to a GETINFO or GETCONF request.
546 info.getMultipleResponseValues = function (message) {
547 return info.keyValueStringsFromMessage(message)
548 .map(info.stringToValue)
549 .filter(utils.identity);
552 // __info.getInfo(controlSocket, key)__.
553 // Sends GETINFO for a single key. Returns a promise with the result.
554 info.getInfo = function (aControlSocket, key) {
555 if (!utils.isString(key)) {
556 return utils.rejectPromise("key argument should be a string");
558 return aControlSocket
559 .sendCommand("getinfo " + key)
560 .then(response => info.getMultipleResponseValues(response)[0]);
563 // __info.getConf(aControlSocket, key)__.
564 // Sends GETCONF for a single key. Returns a promise with the result.
565 info.getConf = function (aControlSocket, key) {
566 // GETCONF with a single argument returns results with
567 // one or more lines that look like `250[- ]key=value`.
568 // Any GETCONF lines that contain a single keyword only are currently dropped.
569 // So we can use similar parsing to that for getInfo.
570 if (!utils.isString(key)) {
571 return utils.rejectPromise("key argument should be a string");
573 return aControlSocket.sendCommand("getconf " + key)
574 .then(info.getMultipleResponseValues);
578 // Handlers for events
582 // __event.parsers__.
583 // A map of EVENT keys to parsing functions, which convert result strings to JavaScript
586 "stream" : info.streamStatusParser,
588 // "circ" : info.circuitStatusParser,
591 // __event.messageToData(type, message)__.
592 // Extract the data from an event. Note, at present
593 // we only extract streams that look like `"650" SP...`
594 event.messageToData = function (type, message) {
595 let dataText = message.match(/^650 \S+?\s(.*)/m)[1];
596 return dataText ? event.parsers[type.toLowerCase()](dataText) : null;
599 // __event.watchEvent(controlSocket, type, filter, onData)__.
600 // Watches for a particular type of event. If filter(data) returns true, the event's
601 // data is passed to the onData callback. Returns a zero arg function that
602 // stops watching the event. Note: we only observe `"650" SP...` events
603 // currently (no `650+...` or `650-...` events).
604 event.watchEvent = function (controlSocket, type, filter, onData) {
605 return controlSocket.addNotificationCallback(new RegExp("^650 " + type),
607 let data = event.messageToData(type, message);
608 if (filter === null || filter(data)) {
615 // Things related to the main controller.
618 // __tor.controllerCache__.
619 // A map from "unix:socketpath" or "host:port" to controller objects. Prevents
620 // redundant instantiation of control sockets.
621 tor.controllerCache = {};
623 // __tor.controller(socketFile, host, port, password, onError)__.
624 // Creates a tor controller at the given socketFile or host and port, with the
626 // onError returns asynchronously whenever a connection error occurs.
627 tor.controller = function (socketFile, host, port, password, onError) {
628 let socket = io.controlSocket(socketFile, host, port, password, onError),
630 return { getInfo : key => info.getInfo(socket, key),
631 getConf : key => info.getConf(socket, key),
632 watchEvent : (type, filter, onData) =>
633 event.watchEvent(socket, type, filter, onData),
634 isOpen : () => isOpen,
635 close : () => { isOpen = false; socket.close(); }
641 // __controller(socketFile, host, port, password, onError)__.
642 // Instantiates and returns a controller object connected to a tor ControlPort
643 // on socketFile or at host:port, authenticating with the given password, if
644 // the controller doesn't yet exist. Otherwise returns the existing controller
645 // to the given socketFile or host:port.
646 // onError is called with an error object as its single argument whenever
647 // an error occurs. Example:
649 // // Get the controller
650 // let c = controller(undefined, "127.0.0.1", 9151, "MyPassw0rd",
651 // function (error) { console.log(error.message || error); });
652 // // Send command and receive `250` reply or error message in a promise:
653 // let replyPromise = c.getInfo("ip-to-country/16.16.16.16");
654 // // Close the controller permanently
656 var controller = function (socketFile, host, port, password, onError) {
657 let dest = (socketFile) ? "unix:" + socketFile.path : host + ":" + port,
658 maybeController = tor.controllerCache[dest];
659 return (tor.controllerCache[dest] =
660 (maybeController && maybeController.isOpen()) ?
662 tor.controller(socketFile, host, port, password, onError));
665 // Export the controller function for external use.
666 var EXPORTED_SYMBOLS = ["controller"];