Commit 28a55f5b authored by Kathleen Brade's avatar Kathleen Brade
Browse files

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.
parent 5ea022aa
Loading
Loading
Loading
Loading
+11 −9
Original line number Diff line number Diff line
@@ -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.");
+66 −35
Original line number Diff line number Diff line
@@ -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,22 +337,36 @@ 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) {}
	}

    // 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 (m_tb_control_socket_file) {
        m_tb_control_desc = m_tb_control_socket_file.path;
    } else {
        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) {}
            } 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")) {
@@ -357,6 +379,7 @@ function torbutton_init() {
              m_tb_control_host = "127.0.0.1";
            }
        }
    }

    // Add event listener for about:tor page loads.
    document.addEventListener("AboutTorLoad", function(aEvent) {
@@ -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();
+47 −35
Original line number Diff line number Diff line
@@ -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"]
// 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,
	  UNBUFFERED = Ci.nsITransport.OPEN_UNBUFFERED;

  // Create an instance of a socket transport.
      socketTransport = socketTransportService.createTransport(null, 0, host, port, null),
  let socketTransport;
  if (socketFile) {
    socketTransport = sts.createUnixDomainTransport(socketFile);
  } else {
    socketTransport = sts.createTransport(null, 0, host, port, null);
  }

  // Open unbuffered asynchronous outputStream.
      outputStream = socketTransport.openOutputStream(UNBUFFERED, 1, 1)
  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.