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
......@@ -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.");
......
......@@ -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();
......
......@@ -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.
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment