[tor-commits] [torbutton/master] Bug 14271: Make Torbutton work with Unix Domain Socket option

gk at torproject.org gk at torproject.org
Fri Sep 9 16:54:16 UTC 2016


commit 28a55f5bf324f43373006328c5b03793096f71c4
Author: Kathy Brade <brade at 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.



More information about the tor-commits mailing list