commit 28a55f5bf324f43373006328c5b03793096f71c4 Author: Kathy Brade brade@pearlcrescent.com Date: Fri Sep 9 11:53:23 2016 -0400
Bug 14271: Make Torbutton work with Unix Domain Socket option
Call Tor Launcher's new TorGetControlSocketFile() function to determine if a Unix domain socket is being used for Tor control port communication and, if it is, use it instead of a TCP connection. --- src/chrome/content/tor-circuit-display.js | 20 +++--- src/chrome/content/torbutton.js | 101 +++++++++++++++++++----------- src/modules/tor-control-port.js | 82 +++++++++++++----------- 3 files changed, 124 insertions(+), 79 deletions(-)
diff --git a/src/chrome/content/tor-circuit-display.js b/src/chrome/content/tor-circuit-display.js index c99d25d..f1cf2aa 100644 --- a/src/chrome/content/tor-circuit-display.js +++ b/src/chrome/content/tor-circuit-display.js @@ -5,9 +5,10 @@ // call earlier functions). The file can be processed // with docco.js to produce pretty documentation. // -// This script is to be embedded in torbutton.xul. It defines a single global function, -// runTorCircuitDisplay(host, port, password), which activates the automatic Tor -// circuit display for the current tab and any future tabs. +// This script is to be embedded in torbutton.xul. It defines a single global +// function, createTorCircuitDisplay(socketFile, host, port, password), which +// activates the automatic Tor circuit display for the current tab and any +// future tabs. // // See https://trac.torproject.org/8641
@@ -15,10 +16,10 @@ /* global document, gBrowser, Components */
// ### Main function -// __createTorCircuitDisplay(host, port, password, enablePrefName)__. +// __createTorCircuitDisplay(socketFile, host, port, password, enablePrefName)__. // The single function that prepares tor circuit display. Connects to a tor -// control port with the given host, port, and password, and binds to -// a named bool pref whose value determines whether the circuit display +// control port with the given socketFile or host plus port, and password, and +// binds to a named bool pref whose value determines whether the circuit display // is enabled or disabled. let createTorCircuitDisplay = (function () {
@@ -311,11 +312,11 @@ let syncDisplayWithSelectedTab = (function() {
// ## Main function
-// __setupDisplay(host, port, password, enablePrefName)__. +// __setupDisplay(socketFile, host, port, password, enablePrefName)__. // Once called, the Tor circuit display will be started whenever // the "enablePref" is set to true, and stopped when it is set to false. // A reference to this function (called createTorCircuitDisplay) is exported as a global. -let setupDisplay = function (host, port, password, enablePrefName) { +let setupDisplay = function (socketFile, host, port, password, enablePrefName) { let myController = null, stopCollectingIsolationData = null, stop = function() { @@ -329,7 +330,8 @@ let setupDisplay = function (host, port, password, enablePrefName) { }, start = function () { if (!myController) { - myController = controller(host, port || 9151, password, function (err) { + myController = controller(socketFile, host, port || 9151, password, + function (err) { // An error has occurred. logger.eclog(5, err); logger.eclog(5, "Disabling tor display circuit because of an error."); diff --git a/src/chrome/content/torbutton.js b/src/chrome/content/torbutton.js index acb4b4b..bdcbc4d 100644 --- a/src/chrome/content/torbutton.js +++ b/src/chrome/content/torbutton.js @@ -31,9 +31,11 @@ var m_tb_window_width = window.outerWidth;
var m_tb_tbb = false;
-var m_tb_control_port = null; -var m_tb_control_host = null; +var m_tb_control_socket_file = null; // Set if using a UNIX domain socket. +var m_tb_control_port = null; // Set if not using a socket. +var m_tb_control_host = null; // Set if not using a socket. var m_tb_control_pass = null; +var m_tb_control_desc = null; // For logging.
var m_tb_orig_BrowserOnAboutPageLoad = null;
@@ -314,6 +316,12 @@ function torbutton_init() { m_tb_prefs.setCharPref(k_tb_last_browser_version_pref, cur_version); }
+ let tlps; + try { + tlps = Cc["@torproject.org/torlauncher-protocol-service;1"] + .getService(Ci.nsISupports).wrappedJSObject; + } catch(e) {} + // Bug 1506 P4: These vars are very important for New Identity var environ = Components.classes["@mozilla.org/process/environment;1"] .getService(Components.interfaces.nsIEnvironment); @@ -329,33 +337,48 @@ function torbutton_init() { } catch(e) { torbutton_log(4, 'unable to read authentication cookie'); } - } else { + } else try { // Try to get password from Tor Launcher. - try { - let tlps = Cc["@torproject.org/torlauncher-protocol-service;1"] - .getService(Ci.nsISupports).wrappedJSObject; - m_tb_control_pass = tlps.TorGetPassword(false); - } catch(e) {} - } + m_tb_control_pass = tlps.TorGetPassword(false); + } catch(e) {}
- if (environ.exists("TOR_CONTROL_PORT")) { - m_tb_control_port = environ.get("TOR_CONTROL_PORT"); - } else { - try { - const kTLControlPortPref = "extensions.torlauncher.control_port"; - m_tb_control_port = m_tb_prefs.getIntPref(kTLControlPortPref); - } catch(e) {} - } + // Try to get control port socket (an nsIFile) from Tor Launcher, since + // Tor Launcher knows how to handle its own preferences and how to + // resolve relative paths. + try { + m_tb_control_socket_file = tlps.TorGetControlSocketFile(); + } catch(e) {}
- if (environ.exists("TOR_CONTROL_HOST")) { - m_tb_control_host = environ.get("TOR_CONTROL_HOST"); + if (m_tb_control_socket_file) { + m_tb_control_desc = m_tb_control_socket_file.path; } else { - try { - const kTLControlHostPref = "extensions.torlauncher.control_host"; - m_tb_control_host = m_tb_prefs.getCharPref(kTLControlHostPref); - } catch(e) { - m_tb_control_host = "127.0.0.1"; - } + if (environ.exists("TOR_CONTROL_PORT")) { + m_tb_control_port = environ.get("TOR_CONTROL_PORT"); + } else { + try { + const kTLControlPortPref = "extensions.torlauncher.control_port"; + m_tb_control_port = m_tb_prefs.getIntPref(kTLControlPortPref); + } catch(e) { + // Since we want to disable some features when Tor Launcher is + // not installed (e.g., New Identity), we do not set a default + // port value here. + } + } + + if (m_tb_control_port) { + m_tb_control_desc = "" + m_tb_control_port; + } + + if (environ.exists("TOR_CONTROL_HOST")) { + m_tb_control_host = environ.get("TOR_CONTROL_HOST"); + } else { + try { + const kTLControlHostPref = "extensions.torlauncher.control_host"; + m_tb_control_host = m_tb_prefs.getCharPref(kTLControlHostPref); + } catch(e) { + m_tb_control_host = "127.0.0.1"; + } + } }
// Add event listener for about:tor page loads. @@ -430,7 +453,8 @@ function torbutton_init() { torbutton_notify_if_update_needed(); torbutton_update_sync_ui();
- createTorCircuitDisplay(m_tb_control_host, m_tb_control_port, m_tb_control_pass, + createTorCircuitDisplay(m_tb_control_socket_file, m_tb_control_host, + m_tb_control_port, m_tb_control_pass, "extensions.torbutton.display_circuit");
torbutton_log(3, 'init completed'); @@ -999,9 +1023,15 @@ function torbutton_send_ctrl_cmd(command) { m_tb_domWindowUtils.suppressEventHandling(false);
try { - var socketTransportService = Components.classes["@mozilla.org/network/socket-transport-service;1"] - .getService(Components.interfaces.nsISocketTransportService); - var socket = socketTransportService.createTransport(null, 0, m_tb_control_host, m_tb_control_port, null); + let sts = Cc["@mozilla.org/network/socket-transport-service;1"] + .getService(Ci.nsISocketTransportService); + let socket; + if (m_tb_control_socket_file) { + socket = sts.createUnixDomainTransport(m_tb_control_socket_file); + } else { + socket = sts.createTransport(null, 0, m_tb_control_host, + m_tb_control_port, null); + }
// If we don't get a response from the control port in 2 seconds, someting is wrong.. socket.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, 2); @@ -1021,21 +1051,21 @@ function torbutton_send_ctrl_cmd(command) { var bytes = torbutton_socket_readline(inputStream);
if (bytes.indexOf("250") != 0) { - torbutton_safelog(4, "Unexpected auth response on control port "+m_tb_control_port+":", bytes); + torbutton_safelog(4, "Unexpected auth response on control port "+m_tb_control_desc+":", bytes); return null; }
outputStream.writeBytes(command, command.length); bytes = torbutton_socket_readline(inputStream); if(bytes.indexOf("250") != 0) { - torbutton_safelog(4, "Unexpected command response on control port "+m_tb_control_port+":", bytes); + torbutton_safelog(4, "Unexpected command response on control port "+m_tb_control_desc+":", bytes); return null; }
// Closing these streams prevents a shutdown hang on Mac OS. See bug 10201. inputStream.close(); outputStream.close(); - socket.close(Components.results.NS_OK); + socket.close(Cr.NS_OK); return bytes.substr(4); } catch(e) { torbutton_log(4, "Exception on control port "+e); @@ -1339,7 +1369,7 @@ function torbutton_do_new_identity() { torbutton_log(3, "New Identity: Sending NEWNYM");
// We only support TBB for newnym. - if (!m_tb_control_pass || !m_tb_control_port) { + if (!m_tb_control_pass || (!m_tb_control_socket_file && !m_tb_control_port)) { var warning = torbutton_get_property_string("torbutton.popup.no_newnym"); torbutton_log(5, "Torbutton cannot safely newnym. It does not have access to the Tor Control Port."); window.alert(warning); @@ -1497,7 +1527,7 @@ function torbutton_do_tor_check() const kEnvUseTransparentProxy = "TOR_TRANSPROXY"; var env = Cc["@mozilla.org/process/environment;1"] .getService(Ci.nsIEnvironment); - if (m_tb_control_port && + if ((m_tb_control_socket_file || m_tb_control_port) && !env.exists(kEnvUseTransparentProxy) && !env.exists(kEnvSkipControlPortTest) && m_tb_prefs.getBoolPref("extensions.torbutton.local_tor_check")) { @@ -2096,8 +2126,9 @@ function torbutton_check_protections() // See https://trac.torproject.org/projects/tor/ticket/10353 for more info. document.getElementById("torbutton-cookie-protector").hidden = m_tb_prefs.getBoolPref("extensions.torbutton.block_disk");
- if (!m_tb_control_pass || !m_tb_control_port) + if (!m_tb_control_pass || (!m_tb_control_socket_file && !m_tb_control_port)) { document.getElementById("torbutton-new-identity").disabled = true; + }
if (!m_tb_tbb && m_tb_prefs.getBoolPref("extensions.torbutton.prompt_torbrowser")) { torbutton_inform_about_tbb(); diff --git a/src/modules/tor-control-port.js b/src/modules/tor-control-port.js index 63c2cd9..f23daed 100644 --- a/src/modules/tor-control-port.js +++ b/src/modules/tor-control-port.js @@ -7,9 +7,10 @@ // // To import the module, use // -// let { controller } = Components.utils.import("path/to/controlPort.jsm"); +// let { controller } = Components.utils.import("path/to/tor-control-port.js"); // -// See the last function defined in this file, controller(host, port, password, onError) +// See the last function defined in this file: +// controller(socketFile, host, port, password, onError) // for usage of the controller function.
/* jshint esnext: true */ @@ -41,17 +42,24 @@ log("Loading tor-control-port.js\n"); // I/O utilities namespace let io = {};
-// __io.asyncSocketStreams(host, port)__. +// __io.asyncSocketStreams(socketFile, host, port)__. // Creates a pair of asynchronous input and output streams for a socket at the -// given host and port. -io.asyncSocketStreams = function (host, port) { - let socketTransportService = Cc["@mozilla.org/network/socket-transport-service;1"] - .getService(Components.interfaces.nsISocketTransportService), - UNBUFFERED = Ci.nsITransport.OPEN_UNBUFFERED, - // Create an instance of a socket transport. - socketTransport = socketTransportService.createTransport(null, 0, host, port, null), - // Open unbuffered asynchronous outputStream. - outputStream = socketTransport.openOutputStream(UNBUFFERED, 1, 1) +// given socketFile or host and port. +io.asyncSocketStreams = function (socketFile, host, port) { + let sts = Cc["@mozilla.org/network/socket-transport-service;1"] + .getService(Components.interfaces.nsISocketTransportService), + UNBUFFERED = Ci.nsITransport.OPEN_UNBUFFERED; + + // Create an instance of a socket transport. + let socketTransport; + if (socketFile) { + socketTransport = sts.createUnixDomainTransport(socketFile); + } else { + socketTransport = sts.createTransport(null, 0, host, port, null); + } + + // Open unbuffered asynchronous outputStream. + let outputStream = socketTransport.openOutputStream(UNBUFFERED, 1, 1) .QueryInterface(Ci.nsIAsyncOutputStream), // Open unbuffered asynchronous inputStream. inputStream = socketTransport.openInputStream(UNBUFFERED, 1, 1) @@ -91,14 +99,16 @@ io.pumpInputStream = function (inputStream, onInputData, onError) { } }, null); };
-// __io.asyncSocket(host, port, onInputData, onError)__. -// Creates an asynchronous, text-oriented TCP socket at host:port. +// __io.asyncSocket(socketFile, host, port, onInputData, onError)__. +// Creates an asynchronous, text-oriented UNIX domain socket (if socketFile +// is defined) or TCP socket at host:port. // The onInputData callback should accept a single argument, which will be called // repeatedly, whenever incoming text arrives. Returns a socket object with two methods: // socket.write(text) and socket.close(). onError will be passed the error object // whenever a write fails. -io.asyncSocket = function (host, port, onInputData, onError) { - let [inputStream, outputStream] = io.asyncSocketStreams(host, port), +io.asyncSocket = function (socketFile, host, port, onInputData, onError) { + let [inputStream, outputStream] = io.asyncSocketStreams(socketFile, host, + port), pendingWrites = []; // Run an input stream pump to send incoming data to the onInputData callback. io.pumpInputStream(inputStream, onInputData, onError); @@ -243,13 +253,13 @@ io.matchRepliesToCommands = function (asyncSend, dispatcher) { }); };
-// __io.controlSocket(host, port, password, onError)__. -// Instantiates and returns a socket to a tor ControlPort at host:port, -// authenticating with the given password. onError is called with an +// __io.controlSocket(socketFile, host, port, password, onError)__. +// Instantiates and returns a socket to a tor ControlPort at socketFile or +// host:port, authenticating with the given password. onError is called with an // error object as its single argument whenever an error occurs. Example: // // // Open the socket -// let socket = controlSocket("127.0.0.1", 9151, "MyPassw0rd", +// let socket = controlSocket(undefined, "127.0.0.1", 9151, "MyPassw0rd", // function (error) { console.log(error.message || error); }); // // Send command and receive "250" reply or error message // socket.sendCommand(commandText, replyCallback, errorCallback); @@ -259,11 +269,11 @@ io.matchRepliesToCommands = function (asyncSend, dispatcher) { // socket.removeNotificationCallback(callback); // // Close the socket permanently // socket.close(); -io.controlSocket = function (host, port, password, onError) { +io.controlSocket = function (socketFile, host, port, password, onError) { // Produce a callback dispatcher for Tor messages. let mainDispatcher = io.callbackDispatcher(), // Open the socket and convert format to Tor messages. - socket = io.asyncSocket(host, port, + socket = io.asyncSocket(socketFile, host, port, io.onDataFromOnLine( io.onLineFromOnMessage(mainDispatcher.pushMessage)), onError), @@ -606,15 +616,16 @@ event.watchEvent = function (controlSocket, type, filter, onData) { let tor = {};
// __tor.controllerCache__. -// A map from "host:port" to controller objects. Prevents redundant instantiation -// of control sockets. +// A map from "unix:socketpath" or "host:port" to controller objects. Prevents +// redundant instantiation of control sockets. tor.controllerCache = {};
-// __tor.controller(host, port, password, onError)__. -// Creates a tor controller at the given host and port, with the given password. +// __tor.controller(socketFile, host, port, password, onError)__. +// Creates a tor controller at the given socketFile or host and port, with the +// given password. // onError returns asynchronously whenever a connection error occurs. -tor.controller = function (host, port, password, onError) { - let socket = io.controlSocket(host, port, password, onError), +tor.controller = function (socketFile, host, port, password, onError) { + let socket = io.controlSocket(socketFile, host, port, password, onError), isOpen = true; return { getInfo : key => info.getInfo(socket, key), getConf : key => info.getConf(socket, key), @@ -627,27 +638,28 @@ tor.controller = function (host, port, password, onError) {
// ## Export
-// __controller(host, port, password, onError)__. +// __controller(socketFile, host, port, password, onError)__. // Instantiates and returns a controller object connected to a tor ControlPort -// at host:port, authenticating with the given password, if the controller doesn't yet -// exist. Otherwise returns the existing controller to the given host:port. +// on socketFile or at host:port, authenticating with the given password, if +// the controller doesn't yet exist. Otherwise returns the existing controller +// to the given socketFile or host:port. // onError is called with an error object as its single argument whenever // an error occurs. Example: // // // Get the controller -// let c = controller("127.0.0.1", 9151, "MyPassw0rd", +// let c = controller(undefined, "127.0.0.1", 9151, "MyPassw0rd", // function (error) { console.log(error.message || error); }); // // Send command and receive `250` reply or error message in a promise: // let replyPromise = c.getInfo("ip-to-country/16.16.16.16"); // // Close the controller permanently // c.close(); -var controller = function (host, port, password, onError) { - let dest = host + ":" + port, +var controller = function (socketFile, host, port, password, onError) { + let dest = (socketFile) ? "unix:" + socketFile.path : host + ":" + port, maybeController = tor.controllerCache[dest]; return (tor.controllerCache[dest] = (maybeController && maybeController.isOpen()) ? maybeController : - tor.controller(host, port, password, onError)); + tor.controller(socketFile, host, port, password, onError)); };
// Export the controller function for external use.