GitLab is used only for code review, issue tracking and project management. Canonical locations for source code are still https://gitweb.torproject.org/ https://git.torproject.org/ and git-rw.torproject.org.

Commit 8871259c authored by Kathleen Brade's avatar Kathleen Brade
Browse files

Bug 14272: Make Tor Launcher work with Unix Domain Socket option

On non-Windows platforms, use a Unix domain socket by default for the
Tor control port. New preferences:
  extensions.torlauncher.control_port_use_socket (Boolean)
  extensions.torlauncher.control_socket_path (path string)
New environment variable: TOR_CONTROL_SOCKET

When starting tor, always include a ControlPort argument so that
users can use environment variables to switch between the two control
port types.

Add a public TorGetControlSocketFile() function that Torbutton will
use to retrieve the socket file (an nsIFile) when a Unix domain
socket is in use.

Moved the getTorFile() function to our utilities module.
parent 44c1cc24
......@@ -31,8 +31,6 @@ TorProcessService.prototype =
{
kContractID : "@torproject.org/torlauncher-process-service;1",
kServiceName : "Tor Launcher Process Service",
kThunderbirdID: "{3550f703-e582-4d05-9a08-453d09bdfdc6}",
kInstantbirdID: "{33cb9019-c295-46dd-be21-8c4936574bee}",
kClassID: Components.ID("{FE7B4CAF-BCF4-4848-8BFF-EFA66C9AFDA1}"),
kTorLauncherExtPath: "tor-launcher@torproject.org", // This could vary.
......@@ -308,11 +306,6 @@ TorProcessService.prototype =
mProtocolSvc: null,
mTorProcess: null, // nsIProcess
mTorProcessStartTime: null, // JS Date.now()
// mIsUserDataOutsideOfAppDir is true when TorBrowser-Data is used.
// (cached; access via this._isUserDataOutsideOfAppDir)
mIsUserDataOutsideOfAppDir: undefined,
mAppDir: null, // nsIFile (cached; access via this._appDir)
mDataDir: null, // nsIFile (cached; access via this._dataDir)
mControlConnTimer: null,
mControlConnDelayMS: 0,
mQuitSoon: false, // Quit was requested by the user; do so soon.
......@@ -333,11 +326,14 @@ TorProcessService.prototype =
// Get the Tor data directory first so it is created before we try to
// construct paths to files that will be inside it.
var dataDir = this._getTorFile("tordatadir", true);
var exeFile = this._getTorFile("tor", false);
var torrcFile = this._getTorFile("torrc", true);
var torrcDefaultsFile = this._getTorFile("torrc-defaults", false);
var dataDir = TorLauncherUtil.getTorFile("tordatadir", true);
var exeFile = TorLauncherUtil.getTorFile("tor", false);
var torrcFile = TorLauncherUtil.getTorFile("torrc", true);
var torrcDefaultsFile =
TorLauncherUtil.getTorFile("torrc-defaults", false);
var hashedPassword = this.mProtocolSvc.TorGetPassword(true);
var controlSocketFile = this.mProtocolSvc.TorGetControlSocketFile();
var controlPort = this.mProtocolSvc.TorGetControlPort();
var detailsKey;
if (!exeFile)
......@@ -359,7 +355,6 @@ TorProcessService.prototype =
return;
}
// The geoip and geoip6 files are in the same directory as torrc-defaults.
var geoipFile = torrcDefaultsFile.clone();
geoipFile.leafName = "geoip";
......@@ -384,6 +379,19 @@ TorProcessService.prototype =
args.push("HashedControlPassword");
args.push(hashedPassword);
// Include a ControlPort argument to support switching between
// a TCP port and a Unix domain socket.
let controlPortArg;
if (controlSocketFile)
controlPortArg = "unix:" + controlSocketFile.path;
else if (controlPort)
controlPortArg = "" + controlPort;
if (controlPortArg)
{
args.push("ControlPort");
args.push(controlPortArg);
}
var pid = this._getpid();
if (0 != pid)
{
......@@ -692,227 +700,6 @@ TorProcessService.prototype =
return argsArray;
},
// Returns an nsIFile.
// If aCreate is true and the file doesn't exist, it is created.
_getTorFile: function(aTorFileType, aCreate)
{
if (!aTorFileType)
return null;
let isRelativePath = true;
let isUserData = (aTorFileType != "tor") &&
(aTorFileType != "torrc-defaults");
let prefName = "extensions.torlauncher." + aTorFileType + "_path";
let path = TorLauncherUtil.getCharPref(prefName);
if (path)
{
let re = (TorLauncherUtil.isWindows) ? /^[A-Za-z]:\\/ : /^\//;
isRelativePath = !re.test(path);
}
else
{
// Get default path.
if (this._isUserDataOutsideOfAppDir)
{
// This block is used for the TorBrowser-Data/ case.
if (TorLauncherUtil.isWindows)
{
if ("tor" == aTorFileType)
path = "TorBrowser\\Tor\\tor.exe";
else if ("torrc-defaults" == aTorFileType)
path = "TorBrowser\\Tor\\torrc-defaults";
else if ("torrc" == aTorFileType)
path = "Tor\\torrc";
else if ("tordatadir" == aTorFileType)
path = "Tor";
}
else if (TorLauncherUtil.isMac)
{
if ("tor" == aTorFileType)
path = "Contents/Resources/TorBrowser/Tor/tor";
else if ("torrc-defaults" == aTorFileType)
path = "Contents/Resources/TorBrowser/Tor/torrc-defaults";
else if ("torrc" == aTorFileType)
path = "Tor/torrc";
else if ("tordatadir" == aTorFileType)
path = "Tor";
}
else // Linux and others.
{
if ("tor" == aTorFileType)
path = "TorBrowser/Tor/tor";
else if ("torrc-defaults" == aTorFileType)
path = "TorBrowser/Tor/torrc-defaults";
else if ("torrc" == aTorFileType)
path = "Tor/torrc";
else if ("tordatadir" == aTorFileType)
path = "Tor";
}
}
else if (TorLauncherUtil.isWindows)
{
// This block is used for the non-TorBrowser-Data/ case.
if ("tor" == aTorFileType)
path = "Tor\\tor.exe";
else if ("torrc-defaults" == aTorFileType)
path = "Data\\Tor\\torrc-defaults";
else if ("torrc" == aTorFileType)
path = "Data\\Tor\\torrc";
else if ("tordatadir" == aTorFileType)
path = "Data\\Tor";
}
else // Linux, Mac OS and others.
{
// This block is also used for the non-TorBrowser-Data/ case.
if ("tor" == aTorFileType)
path = "Tor/tor";
else if ("torrc-defaults" == aTorFileType)
path = "Data/Tor/torrc-defaults";
else if ("torrc" == aTorFileType)
path = "Data/Tor/torrc";
else if ("tordatadir" == aTorFileType)
path = "Data/Tor";
}
}
if (!path)
return null;
try
{
let f;
if (isRelativePath)
{
// Turn 'path' into an absolute path.
if (this._isUserDataOutsideOfAppDir)
{
let baseDir = isUserData ? this._dataDir : this._appDir;
f = baseDir.clone();
}
else
{
f = this._appDir.clone();
f.append("TorBrowser");
}
f.appendRelativePath(path);
}
else
{
f = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);
f.initWithPath(path);
}
if (!f.exists() && aCreate)
{
try
{
if ("tordatadir" == aTorFileType)
f.create(f.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
else
f.create(f.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
}
catch (e)
{
TorLauncherLogger.safelog(4, "unable to create " + f.path + ": ", e);
return null;
}
}
if (f.exists())
{
try { f.normalize(); } catch(e) {}
return f;
}
TorLauncherLogger.log(4, aTorFileType + " file not found: " + f.path);
}
catch(e)
{
TorLauncherLogger.safelog(4, "_getTorFile " + aTorFileType +
" failed for " + path + ": ", e);
}
return null; // File not found or error (logged above).
}, // _getTorFile()
get _isUserDataOutsideOfAppDir()
{
if (this.mIsUserDataOutsideOfAppDir == undefined)
{
// Determine if we are using a "side-by-side" data model by checking
// whether the user profile is outside of the app directory.
try
{
let ds = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
let profDir = ds.get("ProfD", Ci.nsIFile);
this.mIsUserDataOutsideOfAppDir = !this._appDir.contains(profDir);
}
catch (e)
{
this.mIsUserDataOutsideOfAppDir = false;
}
}
return this.mIsUserDataOutsideOfAppDir;
}, // get _isUserDataOutsideOfAppDir
// Returns an nsIFile that points to the application directory.
// May throw.
get _appDir()
{
if (!this.mAppDir)
{
let topDir = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties).get("CurProcD", Ci.nsIFile);
let appInfo = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo);
// On Linux and Windows, we want to return the Browser/ directory.
// Because topDir ("CurProcD") points to Browser/browser on those
// platforms, we need to go up one level.
// On Mac OS, we want to return the TorBrowser.app/ directory.
// Because topDir points to Contents/Resources/browser on Mac OS,
// we need to go up 3 levels.
let tbbBrowserDepth = (TorLauncherUtil.isMac) ? 3 : 1;
if ((appInfo.ID == this.kThunderbirdID) ||
(appInfo.ID == this.kInstantbirdID))
{
// On Thunderbird/Instantbird, the topDir is the root dir and not
// browser/, so we need to iterate one level less than Firefox.
--tbbBrowserDepth;
}
while (tbbBrowserDepth > 0)
{
let didRemove = (topDir.leafName != ".");
topDir = topDir.parent;
if (didRemove)
tbbBrowserDepth--;
}
this.mAppDir = topDir;
}
return this.mAppDir;
}, // get _appDir
// Returns an nsIFile that points to the TorBrowser-Data/ directory.
// This function is only used when this._isUserDataOutsideOfAppDir == true.
// May throw.
get _dataDir()
{
if (!this.mDataDir)
{
let ds = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
let profDir = ds.get("ProfD", Ci.nsIFile);
this.mDataDir = profDir.parent.parent;
}
return this.mDataDir;
}, // get _dataDir
_getpid: function()
{
// Use nsIXULRuntime.processID if it is available.
......
// Copyright (c) 2015, The Tor Project, Inc.
// Copyright (c) 2016, The Tor Project, Inc.
// See LICENSE for licensing information.
// TODO: Some code came from torbutton.js (pull in copyright and license?)
//
......@@ -17,6 +17,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "TorLauncherUtil",
"resource://torlauncher/modules/tl-util.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TorLauncherLogger",
"resource://torlauncher/modules/tl-logger.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
function TorProtocolService()
......@@ -31,23 +33,44 @@ function TorProtocolService()
try
{
var env = Cc["@mozilla.org/process/environment;1"]
let isWindows = TorLauncherUtil.isWindows;
let env = Cc["@mozilla.org/process/environment;1"]
.getService(Ci.nsIEnvironment);
if (env.exists("TOR_CONTROL_HOST"))
this.mControlHost = env.get("TOR_CONTROL_HOST");
else
// 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"))
{
this.mControlHost = TorLauncherUtil.getCharPref(
"extensions.torlauncher.control_host", "127.0.0.1");
let socketPath = env.get("TOR_CONTROL_SOCKET");
this.mControlSocketFile = new FileUtils.File(socketPath);
}
if (env.exists("TOR_CONTROL_PORT"))
this.mControlPort = parseInt(env.get("TOR_CONTROL_PORT"), 10);
else
{
this.mControlPort = TorLauncherUtil.getIntPref(
// Check for TCP host and port environment variables.
if (env.exists("TOR_CONTROL_HOST"))
this.mControlHost = env.get("TOR_CONTROL_HOST");
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);
}
else
{
if (!this.mControlHost)
{
this.mControlHost = TorLauncherUtil.getCharPref(
"extensions.torlauncher.control_host", "127.0.0.1");
}
if (!this.mControlPort)
{
this.mControlPort = TorLauncherUtil.getIntPref(
"extensions.torlauncher.control_port", 9151);
}
}
}
// Populate mControlPassword so it is available when starting tor.
......@@ -141,6 +164,19 @@ TorProtocolService.prototype =
kCmdStatusOK: 250,
kCmdStatusEventNotification: 650,
TorGetControlSocketFile: function()
{
if (!this.mControlSocketFile)
return undefined;
return this.mControlSocketFile.clone();
},
TorGetControlPort: function()
{
return this.mControlPort;
},
// Returns Tor password string or null if an error occurs.
TorGetPassword: function(aPleaseHash)
{
......@@ -512,6 +548,7 @@ TorProtocolService.prototype =
mConsoleSvc: null,
mControlPort: null,
mControlHost: null,
mControlSocketFile: null, // An nsIFile if using a UNIX domain socket.
mControlPassword: null, // JS string that contains hex-encoded password.
mControlConnection: null, // This is cached and reused.
mEventMonitorConnection: null,
......@@ -560,12 +597,43 @@ TorProtocolService.prototype =
var conn;
try
{
var sts = Cc["@mozilla.org/network/socket-transport-service;1"]
let sts = Cc["@mozilla.org/network/socket-transport-service;1"]
.getService(Ci.nsISocketTransportService);
TorLauncherLogger.log(2, "Opening control connection to " +
let socket;
if (this.mControlSocketFile)
{
let exists = this.mControlSocketFile.exists();
if (!exists)
{
TorLauncherLogger.log(5, "Control port socket does not exist: " +
this.mControlSocketFile.path);
}
else
{
let isSpecial = this.mControlSocketFile.isSpecial();
if (!isSpecial)
{
TorLauncherLogger.log(5, "Control port socket is not a socket: " +
this.mControlSocketFile.path);
}
else
{
TorLauncherLogger.log(2, "Opening control connection to socket " +
this.mControlSocketFile.path);
socket = sts.createUnixDomainTransport(this.mControlSocketFile);
}
}
}
else
{
TorLauncherLogger.log(2, "Opening control connection to " +
this.mControlHost + ":" + this.mControlPort);
var socket = sts.createTransport(null, 0, this.mControlHost,
this.mControlPort, null);
socket = sts.createTransport(null, 0, this.mControlHost,
this.mControlPort, null);
}
if (!socket)
return null;
// Our event monitor connection is non-blocking and unbuffered (an
// asyncWait() call is used so we only read data when we know that
......
......@@ -2,16 +2,24 @@
pref("intl.locale.matchOS", true);
pref("extensions.torlauncher.prompt_for_locale", true);
pref("extensions.torlauncher.start_tor", true);
pref("extensions.torlauncher.prompt_at_startup", true);
pref("extensions.torlauncher.loglevel", 4); // 1=verbose, 2=debug, 3=info, 4=note, 5=warn
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
// 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_host", "127.0.0.1");
pref("extensions.torlauncher.control_port", 9151);
pref("extensions.torlauncher.start_tor", true);
pref("extensions.torlauncher.prompt_at_startup", true);
// 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.
......
// Copyright (c) 2015, The Tor Project, Inc.
// Copyright (c) 2016, The Tor Project, Inc.
// See LICENSE for licensing information.
//
// vim: set sw=2 sts=2 ts=8 et syntax=javascript:
......@@ -11,14 +11,19 @@ let EXPORTED_SYMBOLS = [ "TorLauncherUtil" ];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const kPropBundleURI = "chrome://torlauncher/locale/torlauncher.properties";
const kPropNamePrefix = "torlauncher.";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TorLauncherLogger",
"resource://torlauncher/modules/tl-logger.jsm");
let TorLauncherUtil = // Public
{
get isMac()
{
return ("Darwin" == TLUtilInternal._OS);
return TLUtilInternal._isMac;
},
get isWindows()
......@@ -381,6 +386,163 @@ let TorLauncherUtil = // Public
return undefined;
},
// Returns an nsIFile.
// If aTorFileType is "control_socket", aCreate is ignored and there is
// no requirement that the socket exist.
// 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)
{
if (!aTorFileType)
return null;
let isRelativePath = true;
let isUserData = (aTorFileType != "tor") &&
(aTorFileType != "torrc-defaults");
let isControlSocket = ("control_socket" == aTorFileType);
let prefName = "extensions.torlauncher." + aTorFileType + "_path";
let path = this.getCharPref(prefName);
if (path)
{
let re = (this.isWindows) ? /^[A-Za-z]:\\/ : /^\//;
isRelativePath = !re.test(path);
}
else
{
// Get default path.
if (TLUtilInternal._isUserDataOutsideOfAppDir)
{
// This block is used for the TorBrowser-Data/ case.
if (this.isWindows)
{
if ("tor" == aTorFileType)
path = "TorBrowser\\Tor\\tor.exe";
else if ("torrc-defaults" == aTorFileType)
path = "TorBrowser\\Tor\\torrc-defaults";
else if ("torrc" == aTorFileType)
path = "Tor\\torrc";
else if ("tordatadir" == aTorFileType)
path = "Tor";
}
else if (this.isMac)
{
if ("tor" == aTorFileType)
path = "Contents/Resources/TorBrowser/Tor/tor";
else if ("torrc-defaults" == aTorFileType)
path = "Contents/Resources/TorBrowser/Tor/torrc-defaults";
else if ("torrc" == aTorFileType)
path = "Tor/torrc";
else if ("tordatadir" == aTorFileType)
path = "Tor";
else if (isControlSocket)
path = "Tor/control.socket";
}
else // Linux and others.
{
if ("tor" == aTorFileType)
path = "TorBrowser/Tor/tor";
else if ("torrc-defaults" == aTorFileType)
path = "TorBrowser/Tor/torrc-defaults";
else if ("torrc" == aTorFileType)
path = "Tor/torrc";
else if ("tordatadir" == aTorFileType)
path = "Tor";
else if (isControlSocket)
path = "Tor/control.socket";
}
}
else if (this.isWindows)
{
// This block is used for the non-TorBrowser-Data/ case.
if ("tor" == aTorFileType)
path = "Tor\\tor.exe";
else if ("torrc-defaults" == aTorFileType)
path = "Data\\Tor\\torrc-defaults";
else if ("torrc" == aTorFileType)
path = "Data\\Tor\\torrc";
else if ("tordatadir" == aTorFileType)
path = "Data\\Tor";
}
else // Linux, Mac OS and others.
{
// This block is also used for the non-TorBrowser-Data/ case.
if ("tor" == aTorFileType)
path = "Tor/tor";
else if ("torrc-defaults" == aTorFileType)
path = "Data/Tor/torrc-defaults";
else if ("torrc" == aTorFileType)
path = "Data/Tor/torrc";
else if ("tordatadir" == aTorFileType)
path = "Data/Tor";
else if (isControlSocket)
path = "Data/Tor/control.socket";
}
}
if (!path)
return null;
try
{
let f;
if (isRelativePath)
{
// Turn 'path' into an absolute path.
if (TLUtilInternal._isUserDataOutsideOfAppDir)
{
let baseDir = isUserData ? TLUtilInternal._dataDir
: TLUtilInternal._appDir;
f = baseDir.clone();
}
else
{
f = TLUtilInternal._appDir.clone();
f.append("TorBrowser");