commit 8ca52414916c3d8bc2a2974017d759901ddc1736 Author: Kathy Brade brade@pearlcrescent.com Date: Thu Oct 20 09:59:20 2016 -0400
Bug 20111: use Unix domain sockets for SOCKS port by default
New preferences: extensions.torlauncher.socks_port_use_ipc (Boolean; defaults to true) extensions.torlauncher.socks_ipc_path (override default IPC path) extensions.torlauncher.socks_port_flags (SocksPort flags)
Also added support for the TOR_SOCKS_IPC_PATH environment variable.
Consistently use IPC to refer to Unix domain sockets. Renamed preferences: extensions.torlauncher.control_port_use_socket is now extensions.torlauncher.control_port_use_ipc extensions.torlauncher.control_socket_path is now extensions.torlauncher.control_ipc_path Renamed the TOR_CONTROL_SOCKET env variable to TOR_CONTROL_IPC_PATH.
Change _strUnescape() to throw and fix hex and octal unescaping. --- src/components/tl-process.js | 31 ++++- src/components/tl-protocol.js | 248 ++++++++++++++++++++++++++++---------- src/defaults/preferences/prefs.js | 27 +++-- src/modules/tl-util.jsm | 30 +++-- 4 files changed, 252 insertions(+), 84 deletions(-)
diff --git a/src/components/tl-process.js b/src/components/tl-process.js index db074b4..844aba1 100644 --- a/src/components/tl-process.js +++ b/src/components/tl-process.js @@ -330,8 +330,9 @@ TorProcessService.prototype = var torrcDefaultsFile = TorLauncherUtil.getTorFile("torrc-defaults", false); var hashedPassword = this.mProtocolSvc.TorGetPassword(true); - var controlSocketFile = this.mProtocolSvc.TorGetControlSocketFile(); + var controlIPCFile = this.mProtocolSvc.TorGetControlIPCFile(); var controlPort = this.mProtocolSvc.TorGetControlPort(); + var socksPortInfo = this.mProtocolSvc.TorGetSOCKSPortInfo();
var detailsKey; if (!exeFile) @@ -378,10 +379,10 @@ TorProcessService.prototype = args.push(hashedPassword);
// Include a ControlPort argument to support switching between - // a TCP port and a Unix domain socket. + // a TCP port and an IPC port (e.g., a Unix domain socket). let controlPortArg; - if (controlSocketFile) - controlPortArg = "unix:" + controlSocketFile.path; + if (controlIPCFile) + controlPortArg = this._ipcPortArg(controlIPCFile); else if (controlPort) controlPortArg = "" + controlPort; if (controlPortArg) @@ -390,6 +391,21 @@ TorProcessService.prototype = args.push(controlPortArg); }
+ // Include a SocksPort argument to support switching between + // a TCP port and an IPC port (e.g., a Unix domain socket). + if (socksPortInfo) + { + let socksPortArg = (socksPortInfo.ipcFile) + ? this._ipcPortArg(socksPortInfo.ipcFile) + : socksPortInfo.host + ':' + socksPortInfo.port; + let socksPortFlags = TorLauncherUtil.getCharPref( + "extensions.torlauncher.socks_port_flags"); + if (socksPortFlags) + socksPortArg += ' ' + socksPortFlags; + args.push("SocksPort"); + args.push(socksPortArg); + } + var pid = this._getpid(); if (0 != pid) { @@ -454,6 +470,13 @@ TorProcessService.prototype = } }, // _startTor()
+ // Return a ControlPort or SocksPort argument for aIPCFile (an nsIFile). + // The result is unix:/path or unix:"/path with spaces" with appropriate + // C-style escaping within the path portion. + _ipcPortArg: function(aIPCFile) + { + return "unix:" + this.mProtocolSvc.TorEscapeString(aIPCFile.path); + },
_controlTor: function() { diff --git a/src/components/tl-protocol.js b/src/components/tl-protocol.js index d394b53..12f3910 100644 --- a/src/components/tl-protocol.js +++ b/src/components/tl-protocol.js @@ -38,10 +38,10 @@ function TorProtocolService() .getService(Ci.nsIEnvironment); // Determine how Tor Launcher will connect to the Tor control port. // Environment variables get top priority followed by preferences. - if (!isWindows && env.exists("TOR_CONTROL_SOCKET")) + if (!isWindows && env.exists("TOR_CONTROL_IPC_PATH")) { - let socketPath = env.get("TOR_CONTROL_SOCKET"); - this.mControlSocketFile = new FileUtils.File(socketPath); + let ipcPath = env.get("TOR_CONTROL_IPC_PATH"); + this.mControlIPCFile = new FileUtils.File(ipcPath); } else { @@ -51,13 +51,10 @@ function TorProtocolService() if (env.exists("TOR_CONTROL_PORT")) this.mControlPort = parseInt(env.get("TOR_CONTROL_PORT"), 10);
- let useSocket = !isWindows && TorLauncherUtil.getBoolPref( - "extensions.torlauncher.control_port_use_socket", true); - if (!this.mControlHost && !this.mControlPort && useSocket) - { - this.mControlSocketFile = TorLauncherUtil.getTorFile("control_socket", - false); - } + let useIPC = !isWindows && TorLauncherUtil.getBoolPref( + "extensions.torlauncher.control_port_use_ipc", true); + if (!this.mControlHost && !this.mControlPort && useIPC) + this.mControlIPCFile = TorLauncherUtil.getTorFile("control_ipc", false); else { if (!this.mControlHost) @@ -75,7 +72,9 @@ function TorProtocolService()
// Populate mControlPassword so it is available when starting tor. if (env.exists("TOR_CONTROL_PASSWD")) + { this.mControlPassword = env.get("TOR_CONTROL_PASSWD"); + } else if (env.exists("TOR_CONTROL_COOKIE_AUTH_FILE")) { // TODO: test this code path (TOR_CONTROL_COOKIE_AUTH_FILE). @@ -86,6 +85,117 @@ function TorProtocolService()
if (!this.mControlPassword) this.mControlPassword = this._generateRandomPassword(); + + // Determine what kind of SOCKS port Tor and the browser will use. + // On Windows (where Unix domain sockets are not supported), TCP is + // always used. + // + // The following environment variables are supported and take + // precedence over preferences: + // TOR_SOCKS_IPC_PATH (file system path; ignored on Windows) + // TOR_SOCKS_HOST + // TOR_SOCKS_PORT + // + // The following preferences are consulted: + // network.proxy.socks + // network.proxy.socks_port + // extensions.torlauncher.socks_port_use_ipc (Boolean) + // extensions.torlauncher.socks_ipc_path (file system path) + // If extensions.torlauncher.socks_ipc_path is empty, a default + // path is used (<tor-data-directory>/socks.socket). + // + // When using TCP, if a value is not defined via an env variable it is + // taken from the corresponding browser preference if possible. The + // exceptions are: + // If network.proxy.socks contains a file: URL, a default value of + // "127.0.0.1" is used instead. + // If the network.proxy.socks_port value is 0, a default value of + // 9150 is used instead. + // + // Supported scenarios: + // 1. By default, an IPC object at a default path is used. + // 2. If extensions.torlauncher.socks_port_use_ipc is set to false, + // a TCP socket at 127.0.0.1:9150 is used, unless different values + // are set in network.proxy.socks and network.proxy.socks_port. + // 3. If the TOR_SOCKS_IPC_PATH env var is set, an IPC object at that + // path is used (e.g., a Unix domain socket). + // 4. If the TOR_SOCKS_HOST and/or TOR_SOCKS_PORT env vars are set, TCP + // is used. Values not set via env vars will be taken from the + // network.proxy.socks and network.proxy.socks_port prefs as described + // above. + // 5. If extensions.torlauncher.socks_port_use_ipc is true and + // extensions.torlauncher.socks_ipc_path is set, an IPC object at + // the specified path is used. + // 6. Tor Launcher is disabled. Torbutton will respect the env vars if + // present; if not, the values in network.proxy.socks and + // network.proxy.socks_port are used without modification. + + let useIPC; + this.mSOCKSPortInfo = { ipcFile: undefined, host: undefined, port: 0 }; + if (!isWindows && env.exists("TOR_SOCKS_IPC_PATH")) + { + let ipcPath = env.get("TOR_SOCKS_IPC_PATH"); + this.mSOCKSPortInfo.ipcFile = new FileUtils.File(ipcPath); + useIPC = true; + } + else + { + // Check for TCP host and port environment variables. + if (env.exists("TOR_SOCKS_HOST")) + { + this.mSOCKSPortInfo.host = env.get("TOR_SOCKS_HOST"); + useIPC = false; + } + if (env.exists("TOR_SOCKS_PORT")) + { + this.mSOCKSPortInfo.port = parseInt(env.get("TOR_SOCKS_PORT"), 10); + useIPC = false; + } + } + + if (useIPC === undefined) + { + useIPC = !isWindows && TorLauncherUtil.getBoolPref( + "extensions.torlauncher.socks_port_use_ipc", true); + } + + // Fill in missing SOCKS info from prefs. + if (useIPC) + { + if (!this.mSOCKSPortInfo.ipcFile) + { + this.mSOCKSPortInfo.ipcFile = + TorLauncherUtil.getTorFile("socks_ipc", false); + } + } + else + { + if (!this.mSOCKSPortInfo.host) + { + let socksAddr = TorLauncherUtil.getCharPref("network.proxy.socks", + "127.0.0.1"); + let socksAddrHasHost = (socksAddr && !socksAddr.startsWith("file:")); + this.mSOCKSPortInfo.host = socksAddrHasHost ? socksAddr : "127.0.0.1"; + } + + if (!this.mSOCKSPortInfo.port) + { + let socksPort = TorLauncherUtil.getIntPref("network.proxy.socks_port", + 0); + this.mSOCKSPortInfo.port = (socksPort != 0) ? socksPort : 9150; + } + } + + TorLauncherLogger.log(3, "SOCKS port type: " + (useIPC ? "IPC" : "TCP")); + if (useIPC) + { + TorLauncherLogger.log(3, "ipcFile: " + this.mSOCKSPortInfo.ipcFile.path); + } + else + { + TorLauncherLogger.log(3, "SOCKS host: " + this.mSOCKSPortInfo.host); + TorLauncherLogger.log(3, "SOCKS port: " + this.mSOCKSPortInfo.port); + } } catch(e) { @@ -164,12 +274,12 @@ TorProtocolService.prototype = kCmdStatusOK: 250, kCmdStatusEventNotification: 650,
- TorGetControlSocketFile: function() + TorGetControlIPCFile: function() { - if (!this.mControlSocketFile) + if (!this.mControlIPCFile) return undefined;
- return this.mControlSocketFile.clone(); + return this.mControlIPCFile.clone(); },
TorGetControlPort: function() @@ -184,6 +294,18 @@ TorProtocolService.prototype = return (aPleaseHash) ? this._hashPassword(pw) : pw; },
+ TorGetSOCKSPortInfo: function() + { + return this.mSOCKSPortInfo; + }, + + // Escape non-ASCII characters for use within the Tor Control protocol. + // Returns a string. + TorEscapeString: function(aStr) + { + return this._strEscape(aStr); + }, + // NOTE: Many Tor protocol functions return a reply object, which is a // a JavaScript object that has the following fields: // reply.statusCode -- integer, e.g., 250 @@ -373,11 +495,13 @@ TorProtocolService.prototype = else { token = tokenAndVal.substring(0, idx); - var valObj = {}; - if (!this._strUnescape(tokenAndVal.substring(idx + 1), valObj)) - continue; // skip this token/value pair. + try + { + val = this._strUnescape(tokenAndVal.substring(idx + 1)); + } catch (e) {}
- val = valObj.result; + if (!val) + continue; // skip this token/value pair. }
if ("BOOTSTRAP" == token) @@ -548,8 +672,9 @@ TorProtocolService.prototype = mConsoleSvc: null, mControlPort: null, mControlHost: null, - mControlSocketFile: null, // An nsIFile if using a UNIX domain socket. + mControlIPCFile: null, // An nsIFile if using IPC for control port. mControlPassword: null, // JS string that contains hex-encoded password. + mSOCKSPortInfo: null, // An object that contains ipcFile, host, port. mControlConnection: null, // This is cached and reused. mEventMonitorConnection: null, mEventMonitorBuffer: null, @@ -600,27 +725,28 @@ TorProtocolService.prototype = let sts = Cc["@mozilla.org/network/socket-transport-service;1"] .getService(Ci.nsISocketTransportService); let socket; - if (this.mControlSocketFile) + if (this.mControlIPCFile) { - let exists = this.mControlSocketFile.exists(); + let exists = this.mControlIPCFile.exists(); if (!exists) { - TorLauncherLogger.log(5, "Control port socket does not exist: " + - this.mControlSocketFile.path); + TorLauncherLogger.log(5, "Control port IPC object does not exist: " + + this.mControlIPCFile.path); } else { - let isSpecial = this.mControlSocketFile.isSpecial(); + let isSpecial = this.mControlIPCFile.isSpecial(); if (!isSpecial) { - TorLauncherLogger.log(5, "Control port socket is not a socket: " + - this.mControlSocketFile.path); + TorLauncherLogger.log(5, + "Control port IPC object is not a special file: " + + this.mControlIPCFile.path); } else { - TorLauncherLogger.log(2, "Opening control connection to socket " + - this.mControlSocketFile.path); - socket = sts.createUnixDomainTransport(this.mControlSocketFile); + TorLauncherLogger.log(2, "Opening control connection to " + + this.mControlIPCFile.path); + socket = sts.createUnixDomainTransport(this.mControlIPCFile); } } } @@ -855,14 +981,16 @@ TorProtocolService.prototype = } else { - var valObj = {}; - if (!this._strUnescape(line.substring(prefixLen), valObj)) + try { - TorLauncherLogger.safelog(4, "Invalid string within " + aCmd + + let s = this._strUnescape(line.substring(prefixLen)); + tmpArray.push(s); + } + catch (e) + { + TorLauncherLogger.safelog(4, e + " within " + aCmd + " response: ", line); } - else - tmpArray.push(valObj.result); } }
@@ -954,25 +1082,20 @@ TorProtocolService.prototype =
// Unescape Tor Control string aStr (removing surrounding "" and \ escapes). // Based on Vidalia's src/common/stringutil.cpp:string_unescape(). - // Returns true if successful and sets aResultObj.result. - _strUnescape: function(aStr, aResultObj) + // Returns the unescaped string. Throws upon failure. + // Within Torbutton, the file modules/utils.js also contains a copy of + // _strUnescape(). + _strUnescape: function(aStr) { - if (!aResultObj) - return false; - if (!aStr) - { - aResultObj.result = aStr; - return true; - } + return aStr;
var len = aStr.length; if ((len < 2) || ('"' != aStr.charAt(0)) || ('"' != aStr.charAt(len - 1))) - { - aResultObj.result = aStr; - return true; - } + return aStr;
+ const kHexRE = /[0-9A-Fa-f]{2}/; + const kOctalRE = /[0-7]{3}/; var rv = ""; var i = 1; var lastCharIndex = len - 2; @@ -982,7 +1105,7 @@ TorProtocolService.prototype = if ('\' == c) { if (++i > lastCharIndex) - return false; // error: \ without next character. + throw new Error("missing character after \");
c = aStr.charAt(i); if ('n' == c) @@ -994,24 +1117,26 @@ TorProtocolService.prototype = else if ('x' == c) { if ((i + 2) > lastCharIndex) - return false; // error: not enough hex characters. + throw new Error("not enough hex characters");
- var val = parseInt(aStr.substr(i, 2), 16); - if (isNaN(val)) - return false; // error: invalid hex characters. + let s = aStr.substr(i + 1, 2); + if (!kHexRE.test(s)) + throw new Error("invalid hex character");
+ let val = parseInt(s, 16); rv += String.fromCharCode(val); - i += 2; + i += 3; } else if (this._isDigit(c)) { - if ((i + 3) > lastCharIndex) - return false; // error: not enough octal characters. + let s = aStr.substr(i, 3); + if ((i + 2) > lastCharIndex) + throw new Error("not enough octal characters");
- var val = parseInt(aStr.substr(i, 3), 8); - if (isNaN(val)) - return false; // error: invalid octal characters. + if (!kOctalRE.test(s)) + throw new Error("invalid octal character");
+ let val = parseInt(s, 8); rv += String.fromCharCode(val); i += 3; } @@ -1022,7 +1147,7 @@ TorProtocolService.prototype = } } else if ('"' == c) - return false; // error: unescaped double quote in middle of string. + throw new Error("unescaped " within string"); else { rv += c; @@ -1031,10 +1156,7 @@ TorProtocolService.prototype = }
// Convert from UTF-8 to Unicode. TODO: is UTF-8 always used in protocol? - rv = decodeURIComponent(escape(rv)); - - aResultObj.result = rv; - return true; + return decodeURIComponent(escape(rv)); }, // _strUnescape()
// Returns a random 16 character password, hex-encoded. @@ -1188,6 +1310,8 @@ TorProtocolService.prototype = return rv; },
+ // Within Torbutton, the file modules/utils.js also contains a copy of + // _isDigit(). _isDigit: function(aChar) { const kRE = /^\d$/; diff --git a/src/defaults/preferences/prefs.js b/src/defaults/preferences/prefs.js index 500fe2f..875c300 100644 --- a/src/defaults/preferences/prefs.js +++ b/src/defaults/preferences/prefs.js @@ -9,17 +9,30 @@ pref("extensions.torlauncher.loglevel", 4); // 1=verbose, 2=debug, 3=info, 4=no pref("extensions.torlauncher.logmethod", 1); // 0=stdout, 1=errorconsole, 2=debuglog pref("extensions.torlauncher.max_tor_log_entries", 1000);
-// By default, a Unix domain socket at a default location is used for -// the Tor control port. -// Change control_port_use_socket to false to use a TCP connection -// instead, as defined by control_host and control_port. -// Modify control_socket_path to override the default socket location. If a +// By default, an IPC object (e.g., a Unix domain socket) at a default +// location is used for the Tor control port. +// Change control_port_use_ipc to false to use a TCP connection instead, as +// defined by control_host and control_port. +// Modify control_ipc_path to override the default IPC object location. If a // relative path is used, it is handled like torrc_path (see below). -pref("extensions.torlauncher.control_port_use_socket", true); -pref("extensions.torlauncher.control_socket_path", ""); +pref("extensions.torlauncher.control_port_use_ipc", true); +pref("extensions.torlauncher.control_ipc_path", ""); pref("extensions.torlauncher.control_host", "127.0.0.1"); pref("extensions.torlauncher.control_port", 9151);
+// By default, an IPC object (e.g., a Unix domain socket) at a default +// location is used for the Tor SOCKS port. +// Change socks_port_use_ipc to false to use a TCP connection. When a +// TCP connection is used, the host is taken from the network.proxy.socks +// pref and the port is taken from the network.proxy.socks_port pref. +// Modify socks_ipc_path to override the default IPC object location. If a +// relative path is used, it is handled like torrc_path (see below). +// Modify socks_port_flags to use a different set of SocksPort flags (but be +// careful). +pref("extensions.torlauncher.socks_port_use_ipc", true); +pref("extensions.torlauncher.socks_ipc_path", ""); +pref("extensions.torlauncher.socks_port_flags", "IPv6Traffic PreferIPv6 KeepAliveIsolateSOCKSAuth"); + // The tor_path is relative to the application directory. On Linux and // Windows this is the Browser/ directory that contains the firefox // executables, and on Mac OS it is the TorBrowser.app directory. diff --git a/src/modules/tl-util.jsm b/src/modules/tl-util.jsm index 85b5fbe..a1256fd 100644 --- a/src/modules/tl-util.jsm +++ b/src/modules/tl-util.jsm @@ -388,8 +388,8 @@ let TorLauncherUtil = // Public },
// Returns an nsIFile. - // If aTorFileType is "control_socket", aCreate is ignored and there is - // no requirement that the socket exists. + // If aTorFileType is "control_ipc" or "socks_ipc", aCreate is ignored + // and there is no requirement that the IPC object exists. // For all other file types, null is returned if the file does not exist // and it cannot be created (it will be created if aCreate is true). getTorFile: function(aTorFileType, aCreate) @@ -400,7 +400,9 @@ let TorLauncherUtil = // Public let isRelativePath = true; let isUserData = (aTorFileType != "tor") && (aTorFileType != "torrc-defaults"); - let isControlSocket = ("control_socket" == aTorFileType); + let isControlIPC = ("control_ipc" == aTorFileType); + let isSOCKSIPC = ("socks_ipc" == aTorFileType); + let isIPC = isControlIPC || isSOCKSIPC; let prefName = "extensions.torlauncher." + aTorFileType + "_path"; let path = this.getCharPref(prefName); if (path) @@ -435,8 +437,10 @@ let TorLauncherUtil = // Public path = "Tor/torrc"; else if ("tordatadir" == aTorFileType) path = "Tor"; - else if (isControlSocket) + else if (isControlIPC) path = "Tor/control.socket"; + else if (isSOCKSIPC) + path = "Tor/socks.socket"; } else // Linux and others. { @@ -448,8 +452,10 @@ let TorLauncherUtil = // Public path = "Tor/torrc"; else if ("tordatadir" == aTorFileType) path = "Tor"; - else if (isControlSocket) + else if (isControlIPC) path = "Tor/control.socket"; + else if (isSOCKSIPC) + path = "Tor/socks.socket"; } } else if (this.isWindows) @@ -475,8 +481,10 @@ let TorLauncherUtil = // Public path = "Data/Tor/torrc"; else if ("tordatadir" == aTorFileType) path = "Data/Tor"; - else if (isControlSocket) + else if (isControlIPC) path = "Data/Tor/control.socket"; + else if (isSOCKSIPC) + path = "Data/Tor/socks.socket"; } }
@@ -508,7 +516,7 @@ let TorLauncherUtil = // Public f.initWithPath(path); }
- if (!f.exists() && !isControlSocket && aCreate) + if (!f.exists() && !isIPC && aCreate) { try { @@ -524,10 +532,10 @@ let TorLauncherUtil = // Public } }
- // If the file exists or the control socket was requested, normalize - // the path and return a file object. The control socket will be + // If the file exists or an IPC object was requested, normalize the path + // and return a file object. The control and SOCKS IPC objects will be // created by tor. - if (f.exists() || isControlSocket) + if (f.exists() || isIPC) { try { f.normalize(); } catch(e) {} return f; @@ -538,7 +546,7 @@ let TorLauncherUtil = // Public catch(e) { TorLauncherLogger.safelog(4, "getTorFile " + aTorFileType + - " failed for " + path + ": ", e); + " failed for " + path + ": ", e); }
return null; // File not found or error (logged above).
tor-commits@lists.torproject.org