tbb-commits
Threads by month
- ----- 2025 -----
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
March 2018
- 1 participants
- 87 discussions

[rbm/master] Bug 25435: use --no-auto-check-trustdb to avoid modifying gpg keyring files
by gk@torproject.org 07 Mar '18
by gk@torproject.org 07 Mar '18
07 Mar '18
commit db41d8e754ed8cd6cee7bca18d76d59f8f7f369b
Author: Nicolas Vigier <boklm(a)torproject.org>
Date: Tue Mar 6 21:27:39 2018 +0100
Bug 25435: use --no-auto-check-trustdb to avoid modifying gpg keyring files
By default gpg will from time to time update its trust database. When
this happens it will also modify the keyring files to add some trust
information. To avoid this we add the --no-auto-check-trustdb option.
As we don't use the Web of Trust when we …
[View More]use a keyring file, we also
disable it using `--trust-model always`.
---
lib/RBM/DefaultConfig.pm | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/RBM/DefaultConfig.pm b/lib/RBM/DefaultConfig.pm
index 4121264..d758b8e 100644
--- a/lib/RBM/DefaultConfig.pm
+++ b/lib/RBM/DefaultConfig.pm
@@ -216,7 +216,8 @@ OPT_END
export LC_ALL=C
[%
IF c('gpg_keyring');
- SET gpg_kr = '--keyring ' _ path(c('gpg_keyring'), path(c('gpg_keyring_dir'))) _ ' --no-default-keyring';
+ SET gpg_kr = '--keyring ' _ path(c('gpg_keyring'), path(c('gpg_keyring_dir')))
+ _ ' --no-default-keyring --no-auto-check-trustdb --trust-model always';
END;
-%]
exec [% c('gpg_bin') %] [% c('gpg_args') %] --with-fingerprint [% gpg_kr %] "\$@"
[View Less]
1
0

06 Mar '18
commit 5ab5b08179208d0fc4f38dd76686bf1df65a7389
Author: Georg Koppen <gk(a)torproject.org>
Date: Tue Mar 6 12:27:21 2018 +0000
Bump rbm (picking up fix for #25422)
---
rbm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rbm b/rbm
index 7494edc..b4c8c38 160000
--- a/rbm
+++ b/rbm
@@ -1 +1 @@
-Subproject commit 7494edc6d2556c511c213823a6549410ba75f73b
+Subproject commit b4c8c38e12d11eb232305949c5d42d5df5e8f5c8
1
0

[rbm/master] Bug 25422: Give more details in "Cannot checkout" errors
by gk@torproject.org 06 Mar '18
by gk@torproject.org 06 Mar '18
06 Mar '18
commit b4c8c38e12d11eb232305949c5d42d5df5e8f5c8
Author: Nicolas Vigier <boklm(a)torproject.org>
Date: Mon Mar 5 14:47:03 2018 +0100
Bug 25422: Give more details in "Cannot checkout" errors
Print stderr from the git/hg command if the checkout failed.
---
lib/RBM.pm | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/RBM.pm b/lib/RBM.pm
index 3b19ca9..f987130 100644
--- a/lib/RBM.pm
+++ b/lib/RBM.pm
@@ -472,7 +472,7 @@ sub execute {
…
[View More]git_clone_fetch_chdir($project, $options);
my ($stdout, $stderr, $success, $exit_code)
= capture_exec('git', 'checkout', $git_hash);
- exit_error "Cannot checkout $git_hash" unless $success;
+ exit_error "Cannot checkout $git_hash:\n$stderr" unless $success;
if (project_config($project, 'git_submodule', $options)) {
($stdout, $stderr, $success, $exit_code)
= capture_exec('git', 'submodule', 'update', '--init');
@@ -485,7 +485,7 @@ sub execute {
hg_clone_fetch_chdir($project, $options);
my ($stdout, $stderr, $success, $exit_code)
= capture_exec('hg', 'update', '-C', $hg_hash);
- exit_error "Cannot checkout $hg_hash" unless $success;
+ exit_error "Cannot checkout $hg_hash:\n$stderr" unless $success;
}
my ($stdout, $stderr, $success, $exit_code)
= run_script($project, $cmd, \&capture_exec);
[View Less]
1
0

[tor-browser-build/master] Bug 25394: remove support for unused distributions
by gk@torproject.org 02 Mar '18
by gk@torproject.org 02 Mar '18
02 Mar '18
commit ee58c17648e4c71d799096467ae6b9a617af9f9f
Author: Nicolas Vigier <boklm(a)torproject.org>
Date: Fri Mar 2 13:14:26 2018 +0100
Bug 25394: remove support for unused distributions
Remove support for precise and utopic.
---
projects/container-image/config | 23 -----------------------
projects/debootstrap-image/config | 24 ------------------------
2 files changed, 47 deletions(-)
diff --git a/projects/container-image/config b/projects/container-image/config
index …
[View More]c9f377f..95d7557 100644
--- a/projects/container-image/config
+++ b/projects/container-image/config
@@ -20,29 +20,6 @@ pre: |
[% IF pc(c('origin_project'), 'var/pre_pkginst') -%]
[% pc(c('origin_project'), 'var/pre_pkginst') %]
[% END -%]
- [% IF c("var/container/suite") == "precise" -%]
- export INITRD=no
- mkdir -p /etc/container_environment
- echo -n no > /etc/container_environment/INITRD
- dpkg-divert --local --rename --add /sbin/initctl
- ln -s /bin/true /sbin/initctl
- dpkg-divert --local --rename --add /usr/bin/ischroot
- ln -sf /bin/true /usr/bin/ischroot
- cat >> /etc/apt/sources.list << EOF
- deb http://archive.ubuntu.com/ubuntu/ precise-updates main
- deb-src http://archive.ubuntu.com/ubuntu/ precise-updates main
-
- deb http://archive.ubuntu.com/ubuntu/ precise universe
- deb-src http://archive.ubuntu.com/ubuntu/ precise universe
- deb http://archive.ubuntu.com/ubuntu/ precise-updates universe
- deb-src http://archive.ubuntu.com/ubuntu/ precise-updates universe
-
- deb http://archive.ubuntu.com/ubuntu/ precise-security main
- deb-src http://archive.ubuntu.com/ubuntu/ precise-security main
- deb http://archive.ubuntu.com/ubuntu/ precise-security universe
- deb-src http://archive.ubuntu.com/ubuntu/ precise-security universe
- EOF
- [% END -%]
apt-get update -y
apt-get upgrade -y
[%
diff --git a/projects/debootstrap-image/config b/projects/debootstrap-image/config
index 0b40142..10e2afe 100644
--- a/projects/debootstrap-image/config
+++ b/projects/debootstrap-image/config
@@ -44,30 +44,6 @@ targets:
container:
suite: buster
arch: amd64
- precise-amd64:
- var:
- container:
- suite: precise
- arch: amd64
- debootstrap_opt: --keyring=/usr/share/keyrings/ubuntu-archive-removed-keys.gpg
- precise-i386:
- var:
- container:
- suite: precise
- arch: i386
- debootstrap_opt: --keyring=/usr/share/keyrings/ubuntu-archive-removed-keys.gpg
- utopic-amd64:
- var:
- container:
- suite: utopic
- arch: amd64
- debootstrap_mirror: http://old-releases.ubuntu.com/ubuntu/
- utopic-i386:
- var:
- container:
- suite: utopic
- arch: i386
- debootstrap_mirror: http://old-releases.ubuntu.com/ubuntu/
input_files:
- URL: 'http://cdimage.ubuntu.com/ubuntu-base/releases/[% c("var/ubuntu_version") %]/release/ubuntu-base-[% c("var/ubuntu_version") %]-base-amd64.tar.gz'
[View Less]
1
0

[tor-browser-build/master] Bug 25339: update https-everywhere to 2018.2.26
by gk@torproject.org 02 Mar '18
by gk@torproject.org 02 Mar '18
02 Mar '18
commit 7f6cb4caa95b7818e4482f41fd333f5a2a8d60f1
Author: Nicolas Vigier <boklm(a)torproject.org>
Date: Tue Feb 27 13:03:25 2018 +0100
Bug 25339: update https-everywhere to 2018.2.26
This new https-everywhere version requires python 3.6, so we are now
building it in a buster container instead of wheezy.
---
projects/debootstrap-image/config | 5 +++++
projects/https-everywhere/config | 8 ++++----
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/projects/…
[View More]debootstrap-image/config b/projects/debootstrap-image/config
index a06cc53..0b40142 100644
--- a/projects/debootstrap-image/config
+++ b/projects/debootstrap-image/config
@@ -39,6 +39,11 @@ targets:
container:
suite: jessie
arch: i386
+ buster-amd64:
+ var:
+ container:
+ suite: buster
+ arch: amd64
precise-amd64:
var:
container:
diff --git a/projects/https-everywhere/config b/projects/https-everywhere/config
index 907e8e1..43be728 100644
--- a/projects/https-everywhere/config
+++ b/projects/https-everywhere/config
@@ -1,5 +1,5 @@
# vim: filetype=yaml sw=2
-version: 2018.1.29
+version: 2018.2.26
git_url: https://git.torproject.org/https-everywhere.git
git_hash: '[% c("version") %]'
git_submodule: 1
@@ -17,12 +17,12 @@ var:
# inputs. This allows us to save a little time and disk space.
container:
use_container: 1
- suite: wheezy
+ suite: buster
arch: amd64
deps:
- git
- - python
- - python-lxml
+ - python3
+ - python3-lxml
- libxslt1.1
- libxml2-utils
- sqlite3
[View Less]
1
0

[tor-launcher/master] Bug 23136: Moat integration (fetch bridges for the user)
by gk@torproject.org 02 Mar '18
by gk@torproject.org 02 Mar '18
02 Mar '18
commit e921bb15681ac54c9e937b564d31a2a6ec2ceb33
Author: Kathy Brade <brade(a)pearlcrescent.com>
Date: Wed Feb 14 10:07:45 2018 -0500
Bug 23136: Moat integration (fetch bridges for the user)
Modify the setup wizard and Network Settings window to allow automated
retrieval of bridges using Moat, a BridgeDB service which works
over a meek transport and requires the user to solve a CAPTCHA to
obtain bridges.
The new tl-bridgedb.jsm JavaScript module handles …
[View More]all communication
with BridgeDB, and it functions by starting a copy of
meek-client-torbrowser and operating as a PT client parent process
(see https://gitweb.torproject.org/torspec.git/tree/pt-spec.txt)
This feature can be disabled (and the Moat-related Tor Launcher UI
hidden) by setting the pref extensions.torlauncher.moat_service to
an empty string.
---
src/chrome/content/network-settings-overlay.xul | 60 +-
src/chrome/content/network-settings-wizard.xul | 9 +-
src/chrome/content/network-settings.js | 630 +++++++++++++++++++-
src/chrome/content/network-settings.xul | 7 +-
src/chrome/locale/en/network-settings.dtd | 4 +
src/chrome/locale/en/torlauncher.properties | 14 +
src/chrome/skin/activity.svg | 17 +
src/chrome/skin/network-settings.css | 84 ++-
src/chrome/skin/reload.svg | 6 +
src/defaults/preferences/prefs.js | 6 +
src/modules/tl-bridgedb.jsm | 746 ++++++++++++++++++++++++
src/modules/tl-util.jsm | 57 +-
12 files changed, 1583 insertions(+), 57 deletions(-)
diff --git a/src/chrome/content/network-settings-overlay.xul b/src/chrome/content/network-settings-overlay.xul
index b49dbab..3d42c15 100644
--- a/src/chrome/content/network-settings-overlay.xul
+++ b/src/chrome/content/network-settings-overlay.xul
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<!--
- - Copyright (c) 2017, The Tor Project, Inc.
+ - Copyright (c) 2018, The Tor Project, Inc.
- See LICENSE for licensing information.
- vim: set sw=2 sts=2 ts=8 et syntax=xml:
-->
@@ -93,11 +93,11 @@
oncommand="toggleElemUI(this);"/>
<groupbox id="bridgeSpecificSettings">
<hbox align="end" pack="end">
- <radiogroup id="bridgeTypeRadioGroup" flex="1" style="margin: 0px"
- oncommand="onBridgeTypeRadioChange()">
- <hbox align="center">
+ <radiogroup id="bridgeTypeRadioGroup" flex="1" style="margin: 0px">
+ <hbox class="bridgeRadioContainer">
<radio id="bridgeRadioDefault"
- label="&torsettings.useBridges.default;" selected="true"/>
+ label="&torsettings.useBridges.default;" selected="true"
+ oncommand="onBridgeTypeRadioChange()"/>
<button class="helpButton"
oncommand="onOpenHelp('bridgeHelpContent')"/>
<spacer style="width: 3em"/>
@@ -108,8 +108,24 @@
<spring/>
</hbox>
- <radio align="start" id="bridgeRadioCustom"
- label="&torsettings.useBridges.custom;"/>
+ <vbox id="bridgeDBSettings">
+ <hbox class="bridgeRadioContainer">
+ <radio id="bridgeRadioBridgeDB"
+ label="&torsettings.useBridges.bridgeDB;"
+ oncommand="onBridgeTypeRadioChange()"/>
+ </hbox>
+ <vbox id="bridgeDBContainer" align="start">
+ <description id="bridgeDBResult"/>
+ <button id="bridgeDBRequestButton"
+ oncommand="onOpenBridgeDBRequestPrompt()"/>
+ </vbox>
+ </vbox>
+
+ <hbox class="bridgeRadioContainer">
+ <radio align="start" id="bridgeRadioCustom"
+ label="&torsettings.useBridges.custom;"
+ oncommand="onBridgeTypeRadioChange()"/>
+ </hbox>
</radiogroup>
</hbox>
<vbox id="bridgeCustomEntry">
@@ -153,6 +169,36 @@
</hbox>
</vbox>
+ <vbox id="bridgeDBRequestOverlayContent" align="center">
+ <vbox>
+ <label id="bridgeDBPrompt"/>
+ <image id="bridgeDBCaptchaImage"/>
+ <hbox>
+ <spacer id="bridgeDBReloadSpacer"/>
+ <spacer flex="1"/>
+ <textbox id="bridgeDBCaptchaSolution" size="35"
+ placeholder="&torsettings.useBridges.captchaSolution.placeholder;"
+ oninput="onCaptchaSolutionChange()"/>
+ <spacer flex="1"/>
+ <deck id="bridgeDBReloadDeck">
+ <button id="bridgeDBReloadCaptchaButton"
+ tooltiptext="&torsettings.useBridges.reloadCaptcha.tooltip;"
+ oncommand="onReloadCaptcha()"/>
+ <image id="bridgeDBNetworkActivity"/>
+ </deck>
+ </hbox>
+ <label id="bridgeDBCaptchaError"/>
+ <separator/>
+ <hbox pack="center">
+ <button id="bridgeDBCancelButton"
+ oncommand="onCancelBridgeDBRequestPrompt()"/>
+ <button id="bridgeDBSubmitButton" disabled="true"
+ label="&torsettings.useBridges.captchaSubmit;"
+ oncommand="onCaptchaSolutionSubmit()"/>
+ </hbox>
+ </vbox>
+ </vbox>
+
<vbox id="errorOverlayContent">
<hbox pack="center">
<description errorElemId="message" flex="1"/>
diff --git a/src/chrome/content/network-settings-wizard.xul b/src/chrome/content/network-settings-wizard.xul
index 86c2e01..00145a8 100644
--- a/src/chrome/content/network-settings-wizard.xul
+++ b/src/chrome/content/network-settings-wizard.xul
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<!--
- - Copyright (c) 2017, The Tor Project, Inc.
+ - Copyright (c) 2018, The Tor Project, Inc.
- See LICENSE for licensing information.
- vim: set sw=2 sts=2 ts=8 et syntax=xml:
-->
@@ -33,7 +33,6 @@
<image class="tbb-logo"/>
</hbox>
- <separator class="tall"/>
<vbox class="firstResponses" align="center">
<label>&torSettings.connectPrompt;</label>
<label>&torSettings.configurePrompt;</label>
@@ -52,11 +51,13 @@
torShowNavButtons="true">
<stack flex="1">
<vbox>
- <separator class="tall"/>
<vbox id="bridgeSettings"/>
- <separator/>
<vbox id="proxySettings"/>
</vbox>
+ <vbox id="bridgeDBRequestOverlay" class="messagePanel" pack="center"
+ hidden="true">
+ <vbox id="bridgeDBRequestOverlayContent"/>
+ </vbox>
<vbox id="configErrorOverlay" class="messagePanel" pack="center"
hidden="true">
<vbox id="errorOverlayContent"/>
diff --git a/src/chrome/content/network-settings.js b/src/chrome/content/network-settings.js
index 773a647..dc3c9ab 100644
--- a/src/chrome/content/network-settings.js
+++ b/src/chrome/content/network-settings.js
@@ -1,4 +1,4 @@
-// Copyright (c) 2017, The Tor Project, Inc.
+// Copyright (c) 2018, The Tor Project, Inc.
// See LICENSE for licensing information.
//
// vim: set sw=2 sts=2 ts=8 et syntax=javascript:
@@ -8,12 +8,15 @@
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
+const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TorLauncherUtil",
"resource://torlauncher/modules/tl-util.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TorLauncherLogger",
"resource://torlauncher/modules/tl-logger.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TorLauncherBridgeDB",
+ "resource://torlauncher/modules/tl-bridgedb.jsm");
const kPrefPromptForLocale = "extensions.torlauncher.prompt_for_locale";
const kPrefLocale = "general.useragent.locale";
@@ -24,6 +27,14 @@ const kPrefDefaultBridgeRecommendedType =
"extensions.torlauncher.default_bridge_recommended_type";
const kPrefDefaultBridgeType = "extensions.torlauncher.default_bridge_type";
+// The type of bridges to request from BridgeDB via Moat.
+const kPrefBridgeDBType = "extensions.torlauncher.bridgedb_bridge_type";
+
+// The bridges that we receive from BridgeDB via Moat are stored as
+// extensions.torlauncher.bridgedb_bridge.0,
+// extensions.torlauncher.bridgedb_bridge.1, and so on.
+const kPrefBranchBridgeDBBridge = "extensions.torlauncher.bridgedb_bridge.";
+
// As of April 2016, no one is responding to help desk email. Hopefully this will change soon.
//const kSupportAddr = "help(a)rt.torproject.org";
const kSupportURL = "torproject.org/about/contact.html#support";
@@ -51,10 +62,34 @@ const kProxyPassword = "proxyPassword";
const kUseFirewallPortsCheckbox = "useFirewallPorts";
const kFirewallAllowedPorts = "firewallAllowedPorts";
const kUseBridgesCheckbox = "useBridges";
+const kDefaultBridgesRadio = "bridgeRadioDefault";
const kDefaultBridgeTypeMenuList = "defaultBridgeType";
+const kBridgeDBBridgesRadio = "bridgeRadioBridgeDB";
+const kBridgeDBContainer = "bridgeDBContainer";
+const kBridgeDBRequestButton = "bridgeDBRequestButton";
+const kBridgeDBResult = "bridgeDBResult";
const kCustomBridgesRadio = "bridgeRadioCustom";
const kBridgeList = "bridgeList";
-
+const kCopyLogFeedbackPanel = "copyLogFeedbackPanel";
+
+// BridgeDB Moat request overlay (for interaction with a CAPTCHA challenge).
+const kBridgeDBRequestOverlay = "bridgeDBRequestOverlay";
+const kBridgeDBPrompt = "bridgeDBPrompt";
+const kBridgeDBCaptchaImage = "bridgeDBCaptchaImage";
+const kCaptchaImageTransition = "height 250ms ease-in-out";
+const kBridgeDBCaptchaSolution = "bridgeDBCaptchaSolution";
+const kBridgeDBCaptchaError = "bridgeDBCaptchaError";
+const kBridgeDBSubmitButton = "bridgeDBSubmitButton";
+const kBridgeDBCancelButton = "bridgeDBCancelButton";
+const kBridgeDBReloadCaptchaButton = "bridgeDBReloadCaptchaButton";
+const kBridgeDBNetworkActivity = "bridgeDBNetworkActivity";
+
+// Custom event types.
+const kCaptchaSubmitEventType = "TorLauncherCaptchaSubmitEvent";
+const kCaptchaCancelEventType = "TorLauncherCaptchaCancelEvent";
+const kCaptchaReloadEventType = "TorLauncherCaptchaReloadEvent";
+
+// Tor SETCONF keywords.
const kTorConfKeyDisableNetwork = "DisableNetwork";
const kTorConfKeySocks4Proxy = "Socks4Proxy";
const kTorConfKeySocks5Proxy = "Socks5Proxy";
@@ -77,6 +112,8 @@ var gRestoreAfterHelpPanelID = null;
var gIsPostRestartBootstrapNeeded = false;
var gIsWindowScheduledToClose = false;
var gActiveTopics = []; // Topics for which an observer is currently installed.
+var gBridgeDBBridges = undefined; // Array of bridge lines.
+var gBridgeDBRequestEventListeners = [];
function initDialogCommon()
@@ -461,16 +498,218 @@ function onCustomBridgesTextInput()
function onBridgeTypeRadioChange()
{
- var useCustom = getElemValue(kCustomBridgesRadio, false);
- setBoolAttrForElemWithLabel(kDefaultBridgeTypeMenuList, "hidden", useCustom);
+ let useBridgeDB = getElemValue(kBridgeDBBridgesRadio, false);
+ let useCustom = getElemValue(kCustomBridgesRadio, false);
+ let useDefault = !useBridgeDB && !useCustom;
+ setBoolAttrForElemWithLabel(kDefaultBridgeTypeMenuList, "hidden",
+ !useDefault);
+ setBoolAttrForElemWithLabel(kBridgeDBContainer, "hidden", !useBridgeDB);
setBoolAttrForElemWithLabel(kBridgeList, "hidden", !useCustom);
- var focusElemID = (useCustom) ? kBridgeList : kDefaultBridgeTypeMenuList;
- var elem = document.getElementById(focusElemID);
+
+ let focusElemID;
+ if (useBridgeDB)
+ focusElemID = kBridgeDBRequestButton;
+ else if (useCustom)
+ focusElemID = kBridgeList;
+ else
+ focusElemID = kDefaultBridgeTypeMenuList;
+
+ let elem = document.getElementById(focusElemID);
if (elem)
elem.focus();
}
+function onOpenBridgeDBRequestPrompt()
+{
+ // Obtain the meek client path and args from the tor configuration.
+ let reply = gProtocolSvc.TorGetConf("ClientTransportPlugin");
+ if (!gProtocolSvc.TorCommandSucceeded(reply))
+ return;
+
+ let meekClientPath;
+ let meekClientArgs;
+ reply.lineArray.forEach(aLine =>
+ {
+ let tokens = aLine.split(' ');
+ if ((tokens.length > 2) && (tokens[0] == "meek") && (tokens[1] == "exec"))
+ {
+ meekClientPath = tokens[2];
+ meekClientArgs = tokens.slice(3);
+ }
+ });
+
+ if (!meekClientPath)
+ {
+ reportMoatError(TorLauncherUtil.getLocalizedString("no_meek"));
+ return;
+ }
+
+ let proxySettings;
+ if (isProxyConfigured())
+ {
+ proxySettings = getAndValidateProxySettings(true);
+ if (!proxySettings)
+ return;
+ }
+
+ let overlay = document.getElementById(kBridgeDBRequestOverlay);
+ if (overlay)
+ {
+ let cancelBtn = document.getElementById(kBridgeDBCancelButton);
+ if (cancelBtn)
+ cancelBtn.setAttribute("label", gCancelLabelStr);
+
+ showOrHideDialogButtons(false);
+ resetBridgeDBRequestPrompt();
+ setBridgeDBRequestState("fetchingCaptcha");
+ overlay.hidden = false;
+ requestMoatCaptcha(proxySettings, meekClientPath, meekClientArgs);
+ }
+}
+
+
+// When aState is anything other than undefined, a network request is
+// in progress.
+function setBridgeDBRequestState(aState)
+{
+ let overlay = document.getElementById(kBridgeDBRequestOverlay);
+ if (overlay)
+ {
+ if (aState)
+ overlay.setAttribute("state", aState);
+ else
+ overlay.removeAttribute("state");
+ }
+
+ let key = (aState) ? "contacting_bridgedb" : "captcha_prompt";
+ setElemValue(kBridgeDBPrompt, TorLauncherUtil.getLocalizedString(key));
+
+ let textBox = document.getElementById(kBridgeDBCaptchaSolution);
+ if (textBox)
+ {
+ if (aState)
+ textBox.setAttribute("disabled", "true");
+ else
+ textBox.removeAttribute("disabled");
+ }
+
+ // Show the network activity spinner or the reload button, as appropriate.
+ let deckElem = document.getElementById("bridgeDBReloadDeck");
+ if (deckElem)
+ {
+ let panelID = aState ? kBridgeDBNetworkActivity
+ : kBridgeDBReloadCaptchaButton;
+ deckElem.selectedPanel = document.getElementById(panelID);
+ }
+}
+
+
+function onDismissBridgeDBRequestPrompt()
+{
+ let overlay = document.getElementById(kBridgeDBRequestOverlay);
+ if (overlay)
+ {
+ overlay.hidden = true;
+ showOrHideDialogButtons(true);
+ }
+
+ setBridgeDBRequestState(undefined);
+}
+
+
+function onCancelBridgeDBRequestPrompt()
+{
+ // If an event listener is installed, the cancel of pending Moat requests
+ // and other necessary cleanup is handled in a cancel event listener.
+ if (gBridgeDBRequestEventListeners.length > 0)
+ document.dispatchEvent(new CustomEvent(kCaptchaCancelEventType, {}));
+ else
+ onDismissBridgeDBRequestPrompt();
+}
+
+
+function resetBridgeDBRequestPrompt()
+{
+ let textBox = document.getElementById(kBridgeDBCaptchaSolution);
+ if (textBox)
+ textBox.value = "";
+
+ let image = document.getElementById(kBridgeDBCaptchaImage);
+ if (image)
+ {
+ image.removeAttribute("src");
+ image.style.transition = "";
+ image.style.height = "0px";
+ }
+
+ onCaptchaSolutionChange();
+}
+
+
+function onCaptchaSolutionChange()
+{
+ let val = getElemValue(kBridgeDBCaptchaSolution, undefined);
+ enableButton(kBridgeDBSubmitButton, val && (val.length > 0));
+ setElemValue(kBridgeDBCaptchaError, undefined); // clear error
+}
+
+
+function onReloadCaptcha()
+{
+ document.dispatchEvent(new CustomEvent(kCaptchaReloadEventType, {}));
+}
+
+
+function onCaptchaSolutionSubmit()
+{
+ let val = getElemValue(kBridgeDBCaptchaSolution, undefined);
+ if (val)
+ document.dispatchEvent(new CustomEvent(kCaptchaSubmitEventType, {}));
+}
+
+
+function isShowingBridgeDBRequestPrompt()
+{
+ let overlay = document.getElementById(kBridgeDBRequestOverlay);
+ return overlay && !overlay.hasAttribute("hidden");
+}
+
+
+function showBridgeDBBridges()
+{
+ // Truncate the bridge info for display.
+ const kMaxLen = 65;
+ let val;
+ if (gBridgeDBBridges)
+ {
+ gBridgeDBBridges.forEach(aBridgeLine =>
+ {
+ let line;
+ if (aBridgeLine.length <= kMaxLen)
+ line = aBridgeLine;
+ else
+ line = aBridgeLine.substring(0, kMaxLen) + "\u2026"; // ellipsis;
+ if (val)
+ val += "\n" + line;
+ else
+ val = line;
+ });
+ }
+
+ setElemValue(kBridgeDBResult, val);
+
+ // Update the "Get a Bridge" button label.
+ let btn = document.getElementById(kBridgeDBRequestButton);
+ if (btn)
+ {
+ let btnLabelKey = val ? "request_a_new_bridge"
+ : "request_a_bridge";
+ btn.label = TorLauncherUtil.getLocalizedString(btnLabelKey);
+ }
+}
+
+
function onDeckSelect()
{
let deckElem = document.getElementById("deck");
@@ -960,7 +1199,9 @@ function setButtonAttr(aID, aAttr, aValue)
if (!aID || !aAttr)
return null;
- var btn = document.documentElement.getButton(aID);
+ let btn = document.documentElement.getButton(aID); // dialog buttons
+ if (!btn)
+ btn = document.getElementById(aID); // other buttons
if (btn)
{
if (aValue)
@@ -1159,6 +1400,12 @@ function onCancel()
return false;
}
+ if (isShowingBridgeDBRequestPrompt())
+ {
+ onCancelBridgeDBRequestPrompt();
+ return false;
+ }
+
let wizard = getWizard();
if (!wizard && isShowingProgress())
{
@@ -1193,15 +1440,19 @@ function onWizardFinish()
return false;
}
- if (isShowingProgress())
+ if (isShowingBridgeDBRequestPrompt())
{
- onProgressCancelOrReconfigure(getWizard());
+ onCaptchaSolutionSubmit();
return false;
}
- else
+
+ if (isShowingProgress())
{
- return applySettings(false);
+ onProgressCancelOrReconfigure(getWizard());
+ return false;
}
+
+ return applySettings(false);
}
@@ -1219,6 +1470,12 @@ function onNetworkSettingsFinish()
return false;
}
+ if (isShowingBridgeDBRequestPrompt())
+ {
+ onCaptchaSolutionSubmit();
+ return false;
+ }
+
return applySettings(false);
}
@@ -1256,7 +1513,7 @@ function onCopyLog()
// Display a feedback popup that fades away after a few seconds.
let copyLogBtn = document.documentElement.getButton("extra2");
- let panel = document.getElementById("copyLogFeedbackPanel");
+ let panel = document.getElementById(kCopyLogFeedbackPanel);
if (copyLogBtn && panel)
{
panel.firstChild.textContent = TorLauncherUtil.getFormattedLocalizedString(
@@ -1268,7 +1525,7 @@ function onCopyLog()
function closeCopyLogFeedbackPanel()
{
- let panel = document.getElementById("copyLogFeedbackPanel");
+ let panel = document.getElementById(kCopyLogFeedbackPanel);
if (panel && (panel.state =="open"))
panel.hidePopup();
}
@@ -1463,6 +1720,9 @@ function initBridgeSettings()
let canUseDefaultBridges = (typeList && (typeList.length > 0));
let defaultType = TorLauncherUtil.getCharPref(kPrefDefaultBridgeType);
let useDefault = canUseDefaultBridges && !!defaultType;
+ let isMoatConfigured = TorLauncherBridgeDB.isMoatConfigured;
+
+ showOrHideElemById("bridgeDBSettings", isMoatConfigured);
// If not configured to use a default set of bridges, get UseBridges setting
// from tor.
@@ -1477,25 +1737,72 @@ function initBridgeSettings()
useBridges = reply.retVal;
- // Get bridge list from tor.
+ // Get the list of configured bridges from tor.
let bridgeReply = gProtocolSvc.TorGetConf(kTorConfKeyBridgeList);
if (!gProtocolSvc.TorCommandSucceeded(bridgeReply))
return false;
- if (!setBridgeListElemValue(bridgeReply.lineArray))
+ let configuredBridges = [];
+ if (bridgeReply.lineArray)
{
- if (canUseDefaultBridges)
- useDefault = true; // We have no custom values... back to default.
- else
- useBridges = false; // No custom or default bridges are available.
+ bridgeReply.lineArray.forEach(aLine =>
+ {
+ let val = aLine.trim();
+ if (val.length > 0)
+ configuredBridges.push(val);
+ });
+ }
+
+ gBridgeDBBridges = undefined;
+
+ let prefBranch = TorLauncherUtil.getPrefBranch(kPrefBranchBridgeDBBridge);
+ if (isMoatConfigured)
+ {
+ // Determine if we are using a set of bridges that was obtained via Moat.
+ // This is done by checking each of the configured bridge lines against
+ // the values stored under the extensions.torlauncher.bridgedb_bridge.
+ // pref branch. The algorithm used here assumes there are no duplicate
+ // values.
+ let childPrefs = prefBranch.getChildList("", []);
+
+ let bridgeCount = configuredBridges.length;
+ if ((bridgeCount > 0) && (bridgeCount == childPrefs.length))
+ {
+ let foundCount = 0;
+ childPrefs.forEach(aChild =>
+ {
+ if (configuredBridges.indexOf(prefBranch.getCharPref(aChild)) >= 0)
+ ++foundCount;
+ });
+
+ if (foundCount == bridgeCount)
+ gBridgeDBBridges = configuredBridges;
+ }
+ }
+
+ if (!gBridgeDBBridges)
+ {
+ // The stored bridges do not match what is now in torrc. Clear
+ // the stored info and treat the configured bridges as a set of
+ // custom bridges.
+ prefBranch.deleteBranch("");
+ if (!setBridgeListElemValue(configuredBridges))
+ {
+ if (canUseDefaultBridges)
+ useDefault = true; // We have no custom values... back to default.
+ else
+ useBridges = false; // No custom or default bridges are available.
+ }
}
}
setElemValue(kUseBridgesCheckbox, useBridges);
+ showBridgeDBBridges();
showOrHideElemById("bridgeTypeRadioGroup", canUseDefaultBridges);
- let radioID = (useDefault) ? "bridgeRadioDefault" : "bridgeRadioCustom";
+ let radioID = (useDefault) ? kDefaultBridgesRadio
+ : (gBridgeDBBridges) ? kBridgeDBBridgesRadio : kCustomBridgesRadio;
let radio = document.getElementById(radioID);
if (radio)
radio.control.selectedItem = radio;
@@ -1536,6 +1843,18 @@ function useSettings()
if (!didApply)
return;
+ // Record the new BridgeDB bridge values in preferences so later we
+ // can detect that the bridges were received from BridgeDB via Moat.
+ TorLauncherUtil.getPrefBranch(kPrefBranchBridgeDBBridge).deleteBranch("");
+ if (isUsingBridgeDBBridges())
+ {
+ for (let i = 0; i < gBridgeDBBridges.length; ++i)
+ {
+ TorLauncherUtil.setCharPref(kPrefBranchBridgeDBBridge + i,
+ gBridgeDBBridges[i].trim());
+ }
+ }
+
gIsPostRestartBootstrapNeeded = false;
gProtocolSvc.TorSendCommand("SAVECONF");
@@ -1633,7 +1952,7 @@ function showProgressMeterIfNoError()
function applyProxySettings(aUseDefaults)
{
let settings = aUseDefaults ? getDefaultProxySettings()
- : getAndValidateProxySettings();
+ : getAndValidateProxySettings(false);
if (!settings)
return false;
@@ -1655,7 +1974,7 @@ function getDefaultProxySettings()
// Return a settings object if successful and null if not.
-function getAndValidateProxySettings()
+function getAndValidateProxySettings(aIsForMoat)
{
var settings = getDefaultProxySettings();
@@ -1666,7 +1985,11 @@ function getAndValidateProxySettings()
proxyType = getElemValue(kProxyTypeMenulist, null);
if (!proxyType)
{
- reportValidationError("error_proxy_type_missing");
+ let key = "error_proxy_type_missing";
+ if (aIsForMoat)
+ reportMoatError(TorLauncherUtil.getLocalizedString(key));
+ else
+ reportValidationError(key);
return null;
}
@@ -1674,7 +1997,11 @@ function getAndValidateProxySettings()
getElemValue(kProxyPort, null));
if (!proxyAddrPort)
{
- reportValidationError("error_proxy_addr_missing");
+ let key = "error_proxy_addr_missing";
+ if (aIsForMoat)
+ reportMoatError(TorLauncherUtil.getLocalizedString(key));
+ else
+ reportValidationError(key);
return null;
}
@@ -1886,16 +2213,27 @@ function getDefaultBridgeSettings()
// Return a settings object if successful and null if not.
function getAndValidateBridgeSettings()
{
- var settings = getDefaultBridgeSettings();
- var useBridges = isBridgeConfigured();
- var defaultBridgeType;
- var bridgeList;
+ let settings = getDefaultBridgeSettings();
+ let useBridges = isBridgeConfigured();
+ let defaultBridgeType;
+ let bridgeList;
if (useBridges)
{
- var useCustom = getElemValue(kCustomBridgesRadio, false);
- if (useCustom)
+ if (getElemValue(kBridgeDBBridgesRadio, false))
{
- var bridgeStr = getElemValue(kBridgeList, null);
+ if (gBridgeDBBridges)
+ {
+ bridgeList = gBridgeDBBridges;
+ }
+ else
+ {
+ reportValidationError("error_bridgedb_bridges_missing");
+ return null;
+ }
+ }
+ else if (getElemValue(kCustomBridgesRadio, false))
+ {
+ let bridgeStr = getElemValue(kBridgeList, null);
bridgeList = parseAndValidateBridges(bridgeStr);
if (!bridgeList)
{
@@ -1939,6 +2277,13 @@ function isBridgeConfigured()
}
+function isUsingBridgeDBBridges()
+{
+ return isBridgeConfigured() && getElemValue(kBridgeDBBridgesRadio, false) &&
+ gBridgeDBBridges;
+}
+
+
// Returns an array or null.
function parseAndValidateBridges(aStr)
{
@@ -2028,8 +2373,15 @@ function setElemValue(aID, aValue)
// fallthru
case "menulist":
case "listbox":
+ case "label":
elem.value = (val) ? val : "";
break;
+ case "description":
+ while (elem.firstChild)
+ elem.removeChild(elem.firstChild);
+ if (val)
+ elem.appendChild(document.createTextNode(val));
+ break;
}
}
}
@@ -2140,3 +2492,219 @@ function createColonStr(aStr1, aStr2)
return rv;
}
+
+
+function requestMoatCaptcha(aProxySettings, aMeekClientPath, aMeekClientArgs)
+{
+ function cleanup(aMoatRequestor, aErr)
+ {
+ if (aMoatRequestor)
+ aMoatRequestor.close();
+ removeAllBridgeDBRequestEventListeners();
+ onDismissBridgeDBRequestPrompt();
+ if (aErr && (aErr != Cr.NS_ERROR_ABORT))
+ {
+ let details;
+ if (aErr.message)
+ {
+ details = aErr.message;
+ }
+ else if (aErr.code)
+ {
+ if (aErr.code < 1000)
+ details = aErr.code; // HTTP status code
+ else
+ details = "0x" + aErr.code.toString(16); // nsresult
+ }
+
+ reportMoatError(details);
+ }
+ }
+
+ let moatRequestor = TorLauncherBridgeDB.createMoatRequestor();
+
+ let cancelListener = function(aEvent) {
+ if (!moatRequestor.cancel())
+ cleanup(moatRequestor, undefined); // There was no network request to cancel.
+ };
+ addBridgeDBRequestEventListener(kCaptchaCancelEventType, cancelListener);
+
+ moatRequestor.init(proxyURLFromSettings(aProxySettings),
+ aMeekClientPath, aMeekClientArgs)
+ .then(()=>
+ {
+ let bridgeType = TorLauncherUtil.getCharPref(kPrefBridgeDBType);
+ moatRequestor.fetchBridges([bridgeType])
+ .then(aCaptchaInfo =>
+ {
+ return waitForCaptchaResponse(moatRequestor, aCaptchaInfo);
+ })
+ .then(aBridgeInfo =>
+ {
+ // Success! Keep and display the received bridge information.
+ cleanup(moatRequestor, undefined);
+ gBridgeDBBridges = aBridgeInfo.bridges;
+ showBridgeDBBridges();
+ })
+ .catch(aErr =>
+ {
+ cleanup(moatRequestor, aErr);
+ });
+ })
+ .catch(aErr =>
+ {
+ cleanup(moatRequestor, aErr);
+ });
+} // requestMoatCaptcha
+
+
+function reportMoatError(aDetails)
+{
+ if (!aDetails)
+ aDetails = "";
+
+ let msg = TorLauncherUtil.getFormattedLocalizedString("unable_to_get_bridge",
+ [aDetails], 1);
+ showErrorMessage({ message: msg }, false);
+}
+
+
+function proxyURLFromSettings(aProxySettings)
+{
+ if (!aProxySettings)
+ return undefined;
+
+ let proxyURL;
+ if (aProxySettings[kTorConfKeySocks4Proxy])
+ {
+ proxyURL = "socks4a://" + aProxySettings[kTorConfKeySocks4Proxy];
+ }
+ else if (aProxySettings[kTorConfKeySocks5Proxy])
+ {
+ proxyURL = "socks5://";
+ if (aProxySettings[kTorConfKeySocks5ProxyUsername])
+ {
+ proxyURL += createColonStr(
+ aProxySettings[kTorConfKeySocks5ProxyUsername],
+ aProxySettings[kTorConfKeySocks5ProxyPassword]);
+ proxyURL += "@";
+ }
+ proxyURL += aProxySettings[kTorConfKeySocks5Proxy];
+ }
+ else if (aProxySettings[kTorConfKeyHTTPSProxy])
+ {
+ proxyURL = "http://";
+ if (aProxySettings[kTorConfKeyHTTPSProxyAuthenticator])
+ {
+ proxyURL += aProxySettings[kTorConfKeyHTTPSProxyAuthenticator];
+ proxyURL += "@";
+ }
+ proxyURL += aProxySettings[kTorConfKeyHTTPSProxy];
+ }
+
+ return proxyURL;
+} // proxyURLFromSettings
+
+
+// Returns a promise that is resolved with a bridge info object that includes
+// a bridges property, which is an array of bridge configuration lines.
+function waitForCaptchaResponse(aMoatRequestor, aCaptchaInfo)
+{
+ let mCaptchaInfo;
+
+ function displayCaptcha(aCaptchaInfoArg)
+ {
+ mCaptchaInfo = aCaptchaInfoArg;
+ let image = document.getElementById(kBridgeDBCaptchaImage);
+ if (image)
+ {
+ image.setAttribute("src", mCaptchaInfo.captchaImage);
+ image.style.transition = kCaptchaImageTransition;
+ image.style.height = "125px";
+ }
+
+ setBridgeDBRequestState(undefined);
+ focusCaptchaSolutionTextbox();
+ }
+
+ displayCaptcha(aCaptchaInfo);
+
+ return new Promise((aResolve, aReject) =>
+ {
+ let reloadListener = function(aEvent) {
+ // Reset the UI and request a new CAPTCHA.
+ resetBridgeDBRequestPrompt();
+ setBridgeDBRequestState("fetchingCaptcha");
+ aMoatRequestor.fetchBridges([mCaptchaInfo.transport])
+ .then(aCaptchaInfoArg =>
+ {
+ displayCaptcha(aCaptchaInfoArg);
+ })
+ .catch(aErr =>
+ {
+ aReject(aErr);
+ });
+ };
+
+ let submitListener = function(aEvent) {
+ mCaptchaInfo.solution = getElemValue(kBridgeDBCaptchaSolution);
+ setBridgeDBRequestState("checkingSolution");
+ aMoatRequestor.finishFetch(mCaptchaInfo.transport,
+ mCaptchaInfo.challenge, mCaptchaInfo.solution)
+ .then(aBridgeInfo =>
+ {
+ setBridgeDBRequestState(undefined);
+ aResolve(aBridgeInfo);
+ })
+ .catch(aErr =>
+ {
+ setBridgeDBRequestState(undefined);
+ if ((aErr instanceof TorLauncherBridgeDB.error) &&
+ (aErr.code == TorLauncherBridgeDB.errorCodeBadCaptcha))
+ {
+ // Incorrect solution was entered. Allow the user to try again.
+ let s = TorLauncherUtil.getLocalizedString("bad_captcha_solution");
+ setElemValue(kBridgeDBCaptchaError, s);
+ focusCaptchaSolutionTextbox();
+ }
+ else
+ {
+ aReject(aErr);
+ }
+ });
+ };
+
+ addBridgeDBRequestEventListener(kCaptchaReloadEventType, reloadListener);
+ addBridgeDBRequestEventListener(kCaptchaSubmitEventType, submitListener);
+ });
+} // waitForCaptchaResponse
+
+
+function addBridgeDBRequestEventListener(aEventType, aListener)
+{
+ document.addEventListener(aEventType, aListener, false);
+ gBridgeDBRequestEventListeners.push({type: aEventType, listener: aListener});
+}
+
+
+function removeAllBridgeDBRequestEventListeners()
+{
+ for (let i = gBridgeDBRequestEventListeners.length - 1; i >= 0; --i)
+ {
+ document.removeEventListener(gBridgeDBRequestEventListeners[i].type,
+ gBridgeDBRequestEventListeners[i].listener, false);
+ }
+
+ gBridgeDBRequestEventListeners = [];
+}
+
+
+function focusCaptchaSolutionTextbox()
+{
+ let textBox = document.getElementById(kBridgeDBCaptchaSolution);
+ if (textBox)
+ {
+ textBox.focus();
+ textBox.select();
+ }
+}
diff --git a/src/chrome/content/network-settings.xul b/src/chrome/content/network-settings.xul
index 707990a..6f95183 100644
--- a/src/chrome/content/network-settings.xul
+++ b/src/chrome/content/network-settings.xul
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<!--
- - Copyright (c) 2017, The Tor Project, Inc.
+ - Copyright (c) 2018, The Tor Project, Inc.
- See LICENSE for licensing information.
- vim: set sw=2 sts=2 ts=8 et syntax=xml:
-->
@@ -74,6 +74,11 @@
<panel id="copyLogFeedbackPanel"/>
</vbox>
+ <vbox id="bridgeDBRequestOverlay" class="messagePanel" pack="center"
+ hidden="true">
+ <vbox id="bridgeDBRequestOverlayContent"/>
+ </vbox>
+
<vbox id="errorOverlay" class="messagePanel" pack="center" hidden="true">
<vbox id="errorOverlayContent"/>
</vbox>
diff --git a/src/chrome/locale/en/network-settings.dtd b/src/chrome/locale/en/network-settings.dtd
index 85645d7..4615146 100644
--- a/src/chrome/locale/en/network-settings.dtd
+++ b/src/chrome/locale/en/network-settings.dtd
@@ -41,6 +41,10 @@
<!ENTITY torsettings.useBridges.checkbox "Tor is censored in my country">
<!ENTITY torsettings.useBridges.default "Select a built-in bridge">
<!ENTITY torsettings.useBridges.default.placeholder "select a bridge">
+<!ENTITY torsettings.useBridges.bridgeDB "Request a bridge from torproject.org">
+<!ENTITY torsettings.useBridges.captchaSolution.placeholder "Enter the characters from the image">
+<!ENTITY torsettings.useBridges.reloadCaptcha.tooltip "Get a new challenge">
+<!ENTITY torsettings.useBridges.captchaSubmit "Submit">
<!ENTITY torsettings.useBridges.custom "Provide a bridge I know">
<!ENTITY torsettings.useBridges.label "Enter bridge information from a trusted source.">
<!ENTITY torsettings.useBridges.placeholder "type address:port (one per line)">
diff --git a/src/chrome/locale/en/torlauncher.properties b/src/chrome/locale/en/torlauncher.properties
index b09753e..a4d097a 100644
--- a/src/chrome/locale/en/torlauncher.properties
+++ b/src/chrome/locale/en/torlauncher.properties
@@ -26,11 +26,21 @@ torlauncher.error_proxy_addr_missing=You must specify both an IP address or host
torlauncher.error_proxy_type_missing=You must select the proxy type.
torlauncher.error_bridges_missing=You must specify one or more bridges.
torlauncher.error_default_bridges_type_missing=You must select a transport type for the provided bridges.
+torlauncher.error_bridgedb_bridges_missing=Please request a bridge.
torlauncher.error_bridge_bad_default_type=No provided bridges that have the transport type %S are available. Please adjust your settings.
torlauncher.bridge_suffix.meek-amazon=(works in China)
torlauncher.bridge_suffix.meek-azure=(works in China)
+torlauncher.request_a_bridge=Request a Bridge…
+torlauncher.request_a_new_bridge=Request a New Bridge…
+torlauncher.contacting_bridgedb=Contacting BridgeDB. Please wait.
+torlauncher.captcha_prompt=Solve the CAPTCHA to request a bridge.
+torlauncher.bad_captcha_solution=The solution is not correct. Please try again.
+torlauncher.unable_to_get_bridge=Unable to obtain a bridge from BridgeDB.\n\n%S
+torlauncher.no_meek=This browser is not configured for meek, which is needed to obtain bridges.
+torlauncher.no_bridges_available=No bridges are available at this time. Sorry.
+
torlauncher.connect=Connect
torlauncher.restart_tor=Restart Tor
torlauncher.quit=Quit
@@ -62,3 +72,7 @@ torlauncher.bootstrapWarning.timeout=connection timeout
torlauncher.bootstrapWarning.noroute=no route to host
torlauncher.bootstrapWarning.ioerror=read/write error
torlauncher.bootstrapWarning.pt_missing=missing pluggable transport
+
+torlauncher.nsresult.NS_ERROR_NET_RESET=The connection to the server was lost.
+torlauncher.nsresult.NS_ERROR_CONNECTION_REFUSED=Could not connect to the server.
+torlauncher.nsresult.NS_ERROR_PROXY_CONNECTION_REFUSED=Could not connect to the proxy.
diff --git a/src/chrome/skin/activity.svg b/src/chrome/skin/activity.svg
new file mode 100644
index 0000000..3aae4aa
--- /dev/null
+++ b/src/chrome/skin/activity.svg
@@ -0,0 +1,17 @@
+<!-- Based on http://goo.gl/7AJzbL By Sam Herbert -->
+<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg" stroke="#000">
+ <g fill="none" fill-rule="evenodd">
+ <g transform="translate(1 1)" stroke-width="6">
+ <circle stroke-opacity=".4" cx="18" cy="18" r="16"/>
+ <path d="M34 18c0-9.94-8.06-18-18-16">
+ <animateTransform
+ attributeName="transform"
+ type="rotate"
+ from="0 18 18"
+ to="360 18 18"
+ dur="1s"
+ repeatCount="indefinite"/>
+ </path>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/src/chrome/skin/network-settings.css b/src/chrome/skin/network-settings.css
index 259e38d..9a02493 100644
--- a/src/chrome/skin/network-settings.css
+++ b/src/chrome/skin/network-settings.css
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, The Tor Project, Inc.
+ * Copyright (c) 2018, The Tor Project, Inc.
* See LICENSE for licensing information.
*
* vim: set sw=2 sts=2 ts=8 et syntax=css:
@@ -18,14 +18,14 @@ dialog.os-windows {
wizard {
width: 45em;
- height: 36em;
+ height: 38em;
font: -moz-dialog;
padding-top: 0px;
}
wizard.os-windows {
width: 49em;
- height: 42em;
+ height: 44em;
}
.wizard-page-box {
@@ -67,8 +67,15 @@ wizard radiogroup {
margin: 9px 40px;
}
-separator.tall {
- height: 2.1em;
+.firstResponses,
+wizard #bridgeSettings,
+wizard #proxySettings {
+ margin-top: 15px;
+}
+
+.bridgeRadioContainer {
+ min-height: 30px; /* ensure no height change when dropdown menu is hidden */
+ vertical-align: middle;
}
.help .heading,
@@ -105,6 +112,7 @@ wizard#TorLauncherLocalePicker button[dlgtype="next"] {
#bridgeNote,
#bridgeDefaultEntry,
+#bridgeDBContainer,
#bridgeCustomEntry {
margin-left: 1.8em;
}
@@ -114,6 +122,15 @@ wizard.os-mac #bridgeList {
font-size: 90%;
}
+#bridgeDBResult {
+ font-size: 90%;
+ white-space: pre;
+}
+
+#bridgeDBResult[value=""] {
+ display: none;
+}
+
/* reuse Mozilla's help button from the Firefox hamburger menu */
.helpButton {
list-style-image: url(chrome://browser/skin/menuPanel-help.png);
@@ -171,6 +188,7 @@ wizardpage[pageid="restartPanel"] description,
text-align: start;
}
+#bridgeDBRequestOverlayContent,
#errorOverlayContent {
margin: 50px;
min-height: 12em;
@@ -178,6 +196,62 @@ wizardpage[pageid="restartPanel"] description,
box-shadow: 0px 0px 50px rgba(0,0,0,0.9);
}
+#bridgeDBRequestOverlayContent > vbox {
+ margin: 20px;
+}
+
+#bridgeDBPrompt {
+ text-align: center;
+}
+
+#bridgeDBCaptchaImage {
+ margin: 16px 0px;
+ width: 400px;
+ /* height is set via code so it can be animated. */
+}
+
+#bridgeDBReloadSpacer {
+ width: 20px; /* matches the width of #bridgeDBReloadCaptchaButton */
+}
+
+#bridgeDBReloadCaptchaButton {
+ list-style-image: url("chrome://torlauncher/skin/reload.svg");
+ -moz-appearance: none;
+ width: 20px; /* matches the width of #bridgeDBReloadSpacer */
+ height: 20px;
+ min-height: 20px;
+ min-width: 20px;
+ margin: 0;
+ background: none;
+ border: none;
+ box-shadow: none;
+}
+
+#bridgeDBNetworkActivity {
+ list-style-image: url("chrome://torlauncher/skin/activity.svg");
+ width: 20px;
+ height: 20px;
+}
+
+#bridgeDBCaptchaError {
+ color: red;
+ font-weight: bold;
+ text-align: center;
+}
+
+/* Hide BridgeDB overlay elements based on the state attribute. */
+#bridgeDBRequestOverlay[state="fetchingCaptcha"] #bridgeDBReloadCaptchaButton,
+#bridgeDBRequestOverlay[state="checkingSolution"] #bridgeDBReloadCaptchaButton,
+#bridgeDBRequestOverlay[state="fetchingCaptcha"] #bridgeDBCaptchaSolution {
+ visibility: hidden;
+}
+
+#bridgeDBRequestOverlay[state="fetchingCaptcha"] #bridgeDBCaptchaError,
+#bridgeDBRequestOverlay[state="fetchingCaptcha"] #bridgeDBSubmitButton,
+#bridgeDBRequestOverlay[state="checkingSolution"] #bridgeDBSubmitButton {
+ display: none;
+}
+
#errorOverlayContent button[errorElemId="dismissButton"] {
margin-bottom: 20px;
}
diff --git a/src/chrome/skin/reload.svg b/src/chrome/skin/reload.svg
new file mode 100644
index 0000000..d218991
--- /dev/null
+++ b/src/chrome/skin/reload.svg
@@ -0,0 +1,6 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+ <path fill="#000000" d="M15 8H8l2.8-2.8a3.691 3.691 0 0 0-2.3-.7 4 4 0 0 0 0 8 3.9 3.9 0 0 0 3.4-1.9l2.3 1A6.5 6.5 0 1 1 8.5 2a6.773 6.773 0 0 1 4.1 1.4L15 1z"/>
+</svg>
diff --git a/src/defaults/preferences/prefs.js b/src/defaults/preferences/prefs.js
index 752514a..cab235a 100644
--- a/src/defaults/preferences/prefs.js
+++ b/src/defaults/preferences/prefs.js
@@ -45,6 +45,12 @@ pref("extensions.torlauncher.tor_path", "");
pref("extensions.torlauncher.torrc_path", "");
pref("extensions.torlauncher.tordatadir_path", "");
+// BridgeDB-related preferences (used for Moat).
+pref("extensions.torlauncher.bridgedb_front", "www.google.com");
+pref("extensions.torlauncher.bridgedb_reflector", "https://tor-bridges-hyphae-channel.appspot.com");
+pref("extensions.torlauncher.moat_service", "https://bridges.torproject.org/moat");
+pref("extensions.torlauncher.bridgedb_bridge_type", "obfs4");
+
// Recommended default bridge type (can be set per localized bundle).
// pref("extensions.torlauncher.default_bridge_recommended_type", "obfs3");
diff --git a/src/modules/tl-bridgedb.jsm b/src/modules/tl-bridgedb.jsm
new file mode 100644
index 0000000..339cb39
--- /dev/null
+++ b/src/modules/tl-bridgedb.jsm
@@ -0,0 +1,746 @@
+// Copyright (c) 2018, The Tor Project, Inc.
+// See LICENSE for licensing information.
+//
+// vim: set sw=2 sts=2 ts=8 et syntax=javascript:
+
+/*************************************************************************
+ * Tor Launcher BridgeDB Communication Module
+ * https://github.com/isislovecruft/bridgedb/#accessing-the-moat-interface
+ *************************************************************************/
+
+let EXPORTED_SYMBOLS = [ "TorLauncherBridgeDB" ];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Subprocess.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "TorLauncherUtil",
+ "resource://torlauncher/modules/tl-util.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TorLauncherLogger",
+ "resource://torlauncher/modules/tl-logger.jsm");
+
+let TorLauncherBridgeDB = // Public
+{
+ get isMoatConfigured()
+ {
+ let pref = _MoatRequestor.prototype.kPrefMoatService;
+ return !!TorLauncherUtil.getCharPref(pref);
+ },
+
+ // Returns an _MoatRequestor object.
+ createMoatRequestor: function()
+ {
+ return new _MoatRequestor();
+ },
+
+ // Extended Error object which is used when we have a numeric code and
+ // a text error message.
+ error: function(aCode, aMessage)
+ {
+ this.code = aCode;
+ this.message = aMessage;
+ },
+
+ errorCodeBadCaptcha: 419
+};
+
+TorLauncherBridgeDB.error.prototype = Error.prototype; // subclass Error
+
+Object.freeze(TorLauncherBridgeDB);
+
+
+function _MoatRequestor()
+{
+}
+
+_MoatRequestor.prototype =
+{
+ kMaxResponseLength: 1024 * 400,
+ kTransport: "meek",
+ kMoatContentType: "application/vnd.api+json",
+ kMoatVersion: "0.1.0",
+ kPrefBridgeDBFront: "extensions.torlauncher.bridgedb_front",
+ kPrefBridgeDBReflector: "extensions.torlauncher.bridgedb_reflector",
+ kPrefMoatService: "extensions.torlauncher.moat_service",
+ kMoatFetchURLPath: "/fetch",
+ kMoatFetchRequestType: "client-transports",
+ kMoatFetchResponseType: "moat-challenge",
+ kMoatCheckURLPath: "/check",
+ kMoatCheckRequestType: "moat-solution",
+ kMoatCheckResponseType: "moat-bridges",
+
+ kStateIdle: 0,
+ kStateWaitingForVersion: 1,
+ kStateWaitingForProxyDone: 2,
+ kStateWaitingForCMethod: 3,
+ kStateWaitingForCMethodsDone: 4,
+ kStateInitialized: 5,
+
+ mState: this.kStateIdle,
+
+ mLocalProxyURL: undefined,
+ mMeekFront: undefined, // Frontend server, if we are using one.
+ mMeekClientProcess: undefined,
+ mMeekClientStdoutBuffer: undefined,
+ mMeekClientProxyType: undefined, // contains Mozilla names such as socks4
+ mMeekClientIP: undefined,
+ mMeekClientPort: undefined,
+ mMoatResponseListener: undefined,
+ mUserCanceled: false,
+
+ // Returns a promise.
+ init: function(aProxyURL, aMeekClientPath, aMeekClientArgs)
+ {
+ this.mLocalProxyURL = aProxyURL;
+ return this._startMeekClient(aMeekClientPath, aMeekClientArgs);
+ },
+
+ close: function()
+ {
+ if (this.mMeekClientProcess)
+ {
+ this.mMeekClientProcess.kill();
+ this.mMeekClientProcess = undefined;
+ }
+ },
+
+ // Public function: request bridges via Moat.
+ // Returns a promise that is fulfilled with an object that contains:
+ // transport
+ // captchaImage
+ // challenge
+ //
+ // aTransports is an array of transport strings. Supported values:
+ // "vanilla"
+ // "fte"
+ // "obfs3"
+ // "obfs4"
+ // "scramblesuit"
+ fetchBridges: function(aTransports)
+ {
+ this.mUserCanceled = false;
+ if (!this.mMeekClientProcess)
+ return this._meekClientNotRunningError();
+
+ let requestObj = {
+ data: [{
+ version: this.kMoatVersion,
+ type: this.kMoatFetchRequestType,
+ supported: aTransports
+ }]
+ };
+ return this._sendMoatRequest(requestObj, false);
+ },
+
+ // Public function: check CAPTCHA and retrieve bridges via Moat.
+ // Returns a promise that is fulfilled with an object that contains:
+ // bridges // an array of strings (bridge lines)
+ finishFetch: function(aTransport, aChallenge, aSolution)
+ {
+ this.mUserCanceled = false;
+ if (!this.mMeekClientProcess)
+ return this._meekClientNotRunningError();
+
+ let requestObj = {
+ data: [{
+ id: "2",
+ type: this.kMoatCheckRequestType,
+ version: this.kMoatVersion,
+ transport: aTransport,
+ challenge: aChallenge,
+ solution: aSolution,
+ qrcode: "false"
+ }]
+ };
+ return this._sendMoatRequest(requestObj, true);
+ },
+
+ // Returns true if a promise is pending (which will be rejected), e.g.,
+ // if a network request is active or we are inside init().
+ cancel: function()
+ {
+ this.mUserCanceled = true;
+ if (this.mMoatResponseListener)
+ return this.mMoatResponseListener.cancelMoatRequest();
+
+ if (this.mState != this.kStateInitialized)
+ {
+ // close() will kill the meek client process, which will cause
+ // initialization to fail.
+ this.close();
+ return true;
+ }
+
+ return false;
+ },
+
+ // Returns a rejected promise.
+ _meekClientNotRunningError()
+ {
+ return Promise.reject(new Error("The meek client exited unexpectedly."));
+ },
+
+ // Returns a promise.
+ _startMeekClient: function(aMeekClientPath, aMeekClientArgs)
+ {
+ let workDir = TorLauncherUtil.getTorFile("pt-startup-dir", false);
+ if (!workDir)
+ return Promise.reject(new Error("Missing pt-startup-dir."));
+
+ // Ensure that we have an absolute path for the meek client program.
+ // This is necessary because Subprocess.call() checks for the existence
+ // of the file before it changes to the startup (working) directory.
+ let meekClientPath;
+ let re = (TorLauncherUtil.isWindows) ? /^[A-Za-z]:\\/ : /^\//;
+ if (re.test(aMeekClientPath))
+ {
+ meekClientPath = aMeekClientPath; // We already have an absolute path.
+ }
+ else
+ {
+ let f = workDir.clone();
+ f.appendRelativePath(aMeekClientPath);
+ meekClientPath = f.path;
+ }
+
+ // Construct the args array.
+ let args = aMeekClientArgs.slice(); // make a copy
+ let meekReflector = TorLauncherUtil.getCharPref(this.kPrefBridgeDBReflector);
+ if (meekReflector)
+ {
+ args.push("-url");
+ args.push(meekReflector);
+ }
+ this.mMeekFront = TorLauncherUtil.getCharPref(this.kPrefBridgeDBFront);
+ if (this.mMeekFront)
+ {
+ args.push("-front");
+ args.push(this.mMeekFront);
+ }
+
+ let ptStateDir = TorLauncherUtil.getTorFile("tordatadir", false);
+ if (!ptStateDir)
+ {
+ let msg = TorLauncherUtil.getLocalizedString("datadir_missing");
+ return Promise.reject(new Error(msg));
+ }
+ ptStateDir.append("pt_state"); // Match what tor uses.
+
+ let envAdditions = { TOR_PT_MANAGED_TRANSPORT_VER: "1",
+ TOR_PT_STATE_LOCATION: ptStateDir.path,
+ TOR_PT_EXIT_ON_STDIN_CLOSE: "1",
+ TOR_PT_CLIENT_TRANSPORTS: this.kTransport };
+ if (this.mLocalProxyURL)
+ envAdditions.TOR_PT_PROXY = this.mLocalProxyURL;
+
+ TorLauncherLogger.log(3, "starting " + meekClientPath + " in "
+ + workDir.path);
+ TorLauncherLogger.log(3, "args " + JSON.stringify(args));
+ TorLauncherLogger.log(3, "env additions " + JSON.stringify(envAdditions));
+ let opts = { command: meekClientPath,
+ arguments: args,
+ workdir: workDir.path,
+ environmentAppend: true,
+ environment: envAdditions,
+ stderr: "pipe" };
+ return Subprocess.call(opts)
+ .then(aProc =>
+ {
+ this.mMeekClientProcess = aProc;
+ aProc.wait()
+ .then(aExitObj =>
+ {
+ this.mMeekClientProcess = undefined;
+ TorLauncherLogger.log(3, "The meek client exited");
+ });
+
+ this.mState = this.kStateWaitingForVersion;
+ TorLauncherLogger.log(3, "The meek client process has been started");
+ this._startStderrLogger();
+ return this._meekClientHandshake(aProc);
+ });
+ }, // _startMeekClient
+
+ // Returns a promise that is resolved when the PT handshake finishes.
+ _meekClientHandshake: function(aMeekClientProc)
+ {
+ return new Promise((aResolve, aReject) =>
+ {
+ this._startStdoutRead(aResolve, aReject);
+ });
+ },
+
+ _startStdoutRead: function(aResolve, aReject)
+ {
+ if (!this.mMeekClientProcess)
+ throw new Error("No meek client process.");
+
+ let readPromise = this.mMeekClientProcess.stdout.readString();
+ readPromise
+ .then(aStr =>
+ {
+ if (!aStr || (aStr.length == 0))
+ {
+ let err = "The meek client exited unexpectedly during the pluggable transport handshake.";
+ TorLauncherLogger.log(3, err);
+ throw new Error(err);
+ }
+
+ TorLauncherLogger.log(2, "meek client stdout: " + aStr);
+ if (!this.mMeekClientStdoutBuffer)
+ this.mMeekClientStdoutBuffer = aStr;
+ else
+ this.mMeekClientStdoutBuffer += aStr;
+
+ if (this._processStdoutLines())
+ {
+ aResolve();
+ }
+ else
+ {
+ // The PT handshake has not finished yet. Read more data.
+ this._startStdoutRead(aResolve, aReject);
+ }
+ })
+ .catch(aErr =>
+ {
+ aReject(this.mUserCanceled ? Cr.NS_ERROR_ABORT : aErr);
+ });
+ }, // _startStdoutRead
+
+ _startStderrLogger: function()
+ {
+ if (!this.mMeekClientProcess)
+ return;
+
+ let readPromise = this.mMeekClientProcess.stderr.readString();
+ readPromise
+ .then(aStr =>
+ {
+ if (aStr)
+ {
+ TorLauncherLogger.log(5, "meek client stderr: " + aStr);
+ this._startStderrLogger();
+ }
+ });
+ }, // _startStderrLogger
+
+ // May throw. Returns true when the PT handshake is complete.
+ // Conforms to the parent process role of the PT protocol.
+ // See: https://gitweb.torproject.org/torspec.git/tree/pt-spec.txt
+ _processStdoutLines: function()
+ {
+ if (!this.mMeekClientStdoutBuffer)
+ throw new Error("The stdout buffer is missing.");
+
+ let idx = this.mMeekClientStdoutBuffer.indexOf('\n');
+ while (idx >= 0)
+ {
+ let line = this.mMeekClientStdoutBuffer.substring(0, idx);
+ let tokens = line.split(' ');
+ this.mMeekClientStdoutBuffer =
+ this.mMeekClientStdoutBuffer.substring(idx + 1);
+ idx = this.mMeekClientStdoutBuffer.indexOf('\n');
+
+ // Per the PT specification, unknown keywords are ignored.
+ let keyword = tokens[0];
+ let errMsg;
+ switch (this.mState) {
+ case this.kStateWaitingForVersion:
+ if (keyword == "VERSION")
+ {
+ if (this.mLocalProxyURL)
+ this.mState = this.kStateWaitingForProxyDone;
+ else
+ this.mState = this.kStateWaitingForCMethod;
+ }
+ else if (keyword == "VERSION-ERROR")
+ {
+ throw new Error("Unsupported pluggable transport version.");
+ }
+ break;
+ case this.kStateWaitingForProxyDone:
+ if ((keyword == "ENV-ERROR") || (keyword == "PROXY-ERROR"))
+ throw new Error(line);
+
+ if ((keyword == "PROXY") &&
+ (tokens.length > 1) && (tokens[1] == "DONE"))
+ {
+ this.mState = this.kStateWaitingForCMethod;
+ }
+ break;
+ case this.kStateWaitingForCMethod:
+ if (keyword == "ENV-ERROR")
+ throw new Error(line);
+
+ if (keyword == "CMETHOD")
+ {
+ if (tokens.length != 4)
+ {
+ errMsg = "Invalid CMETHOD response (too few parameters).";
+ }
+ else if (tokens[1] != this.kTransport)
+ {
+ errMsg = "Unexpected transport " + tokens[1]
+ + " in CMETHOD response.";
+ }
+ else
+ {
+ let proxyType = tokens[2];
+ if (proxyType == "socks5")
+ {
+ this.mMeekClientProxyType = "socks";
+ }
+ else if ((proxyType == "socks4a") || (proxyType == "socks4"))
+ {
+ this.mMeekClientProxyType = "socks4";
+ }
+ else
+ {
+ errMsg = "Unexpected proxy type " + proxyType +
+ " in CMETHOD response.";
+ break;
+ }
+ let addrPort = tokens[3];
+ let colonIdx = addrPort.indexOf(':');
+ if (colonIdx < 1)
+ {
+ errMsg = "Missing port in CMETHOD response.";
+ }
+ else
+ {
+ this.mMeekClientIP = addrPort.substring(0, colonIdx);
+ this.mMeekClientPort =
+ parseInt(addrPort.substring(colonIdx + 1));
+ }
+ }
+ }
+ else if (keyword == "CMETHOD-ERROR")
+ {
+ if (tokens.length < 3)
+ {
+ errMsg = "Invalid CMETHOD-ERROR response (too few parameters).";
+ }
+ else
+ {
+ errMsg = tokens[1] + " not available: "
+ + tokens.slice(2).join(' ');
+ }
+ }
+ else if ((keyword == "CMETHODS") && (tokens.length > 1) &&
+ (tokens[1] == "DONE"))
+ {
+ this.mState = this.kStateInitialized;
+ }
+ break;
+ }
+
+ if (errMsg)
+ throw new Error(errMsg);
+ }
+
+ if (this.mState == this.kStateInitialized)
+ {
+ TorLauncherLogger.log(2, "meek client proxy type: "
+ + this.mMeekClientProxyType);
+ TorLauncherLogger.log(2, "meek client proxy IP: "
+ + this.mMeekClientIP);
+ TorLauncherLogger.log(2, "meek client proxy port: "
+ + this.mMeekClientPort);
+ }
+
+ return (this.mState == this.kStateInitialized);
+ }, // _processStdoutLines
+
+ // Returns a promise.
+ // Based on meek/firefox/components/main.js
+ _sendMoatRequest: function(aRequestObj, aIsCheck)
+ {
+ let proxyPS = Cc["@mozilla.org/network/protocol-proxy-service;1"]
+ .getService(Ci.nsIProtocolProxyService);
+ let flags = Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST;
+ let noTimeout = 0xFFFFFFFF; // UINT32_MAX
+ let proxyInfo = proxyPS.newProxyInfo(this.mMeekClientProxyType,
+ this.mMeekClientIP, this.mMeekClientPort,
+ flags, noTimeout, undefined);
+ let uriStr = TorLauncherUtil.getCharPref(this.kPrefMoatService);
+ if (!uriStr)
+ {
+ return Promise.reject(
+ new Error("Missing value for " + this.kPrefMoatService));
+ }
+
+ uriStr += (aIsCheck) ? this.kMoatCheckURLPath : this.kMoatFetchURLPath;
+ let uri = Services.io.newURI(uriStr);
+
+ // There does not seem to be a way to directly create an nsILoadInfo from
+ // JavaScript, so we create a throw away non-proxied channel to get one.
+ let loadInfo = Services.io.newChannelFromURI2(uri, undefined,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ undefined,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER).loadInfo;
+ let httpHandler = Services.io.getProtocolHandler("http")
+ .QueryInterface(Ci.nsIHttpProtocolHandler);
+ let ch = httpHandler.newProxiedChannel2(uri, proxyInfo, 0, undefined,
+ loadInfo).QueryInterface(Ci.nsIHttpChannel);
+
+ // Remove unwanted HTTP headers and set request parameters.
+ let headers = [];
+ ch.visitRequestHeaders({visitHeader: function(aKey, aValue) {
+ headers.push(aKey); }});
+ headers.forEach(aKey =>
+ {
+ if (aKey !== "Host")
+ ch.setRequestHeader(aKey, "", false);
+ });
+
+ // BridgeDB expects to receive an X-Forwarded-For header. If we are
+ // not using domain fronting (e.g., in a test setup), include a fake
+ // header value.
+ if (!this.mMeekFront)
+ ch.setRequestHeader("X-Forwarded-For", "1.2.3.4", false);
+
+ // Arrange for the POST data to be sent.
+ let requestData = JSON.stringify(aRequestObj);
+ let inStream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ inStream.setData(requestData, requestData.length);
+ let upChannel = ch.QueryInterface(Ci.nsIUploadChannel);
+ upChannel.setUploadStream(inStream, this.kMoatContentType,
+ requestData.length);
+ ch.requestMethod = "POST";
+
+ return new Promise((aResolve, aReject) =>
+ {
+ this.mMoatResponseListener =
+ new _MoatResponseListener(this, ch, aIsCheck, aResolve, aReject);
+ TorLauncherLogger.log(1, "Moat JSON request: " + requestData);
+ ch.asyncOpen(this.mMoatResponseListener, ch);
+ });
+ } // _sendMoatRequest
+};
+
+
+// _MoatResponseListener is an HTTP stream listener that knows how to
+// process Moat /fetch and /check responses.
+function _MoatResponseListener(aRequestor, aChannel, aIsCheck,
+ aResolve, aReject)
+{
+ this.mRequestor = aRequestor;
+ this.mChannel = aChannel;
+ this.mIsCheck = aIsCheck;
+ this.mResolveCallback = aResolve;
+ this.mRejectCallback = aReject;
+}
+
+
+_MoatResponseListener.prototype =
+{
+ mRequestor: undefined,
+ mChannel: undefined,
+ mIsCheck: false,
+ mResolveCallback: undefined,
+ mRejectCallback: undefined,
+ mResponseLength: 0,
+ mResponseBody: undefined,
+
+ onStartRequest: function(aRequest, aContext)
+ {
+ this.mResponseLength = 0;
+ this.mResponseBody = "";
+ },
+
+ onStopRequest: function(aRequest, aContext, aStatus)
+ {
+ this.mChannel = undefined;
+
+ if (!Components.isSuccessCode(aStatus))
+ {
+ this.mRejectCallback(new TorLauncherBridgeDB.error(aStatus,
+ TorLauncherUtil.getLocalizedStringForError(aStatus)));
+ return;
+ }
+
+ let statusCode, msg;
+ try
+ {
+ statusCode = aContext.responseStatus;
+ if (aContext.responseStatusText)
+ msg = statusCode + " " + aContext.responseStatusText;
+ }
+ catch (e) {}
+
+ TorLauncherLogger.log(3, "Moat response HTTP status: " + statusCode);
+ if (statusCode != 200)
+ {
+ this.mRejectCallback(new TorLauncherBridgeDB.error(statusCode, msg));
+ return;
+ }
+
+ TorLauncherLogger.log(1, "Moat JSON response: " + this.mResponseBody);
+
+ try
+ {
+ // Parse the response. We allow response.data to be an array or object.
+ let response = JSON.parse(this.mResponseBody);
+ if (response.data && Array.isArray(response.data))
+ response.data = response.data[0];
+
+ let errCode = 400;
+ let errStr;
+ if (!response.data)
+ {
+ if (response.errors && Array.isArray(response.errors))
+ {
+ errCode = response.errors[0].code;
+ errStr = response.errors[0].detail;
+ if (this.mIsCheck && (errCode == 404))
+ errStr = TorLauncherUtil.getLocalizedString("no_bridges_available");
+ }
+ else
+ {
+ errStr = "missing data in Moat response";
+ }
+ }
+ else if (response.data.version !== this.mRequestor.kMoatVersion)
+ {
+ errStr = "unexpected version";
+ }
+
+ if (errStr)
+ this.mRejectCallback(new TorLauncherBridgeDB.error(errCode, errStr));
+ else if (!this.mIsCheck)
+ this._parseFetchResponse(response);
+ else
+ this._parseCheckResponse(response);
+ }
+ catch(e)
+ {
+ TorLauncherLogger.log(3, "received invalid JSON: " + e);
+ this.mRejectCallback(e);
+ }
+ }, // onStopRequest
+
+ onDataAvailable: function(aRequest, aContext, aStream, aSrcOffset, aLength)
+ {
+ TorLauncherLogger.log(2, "Moat onDataAvailable: " + aLength + " bytes");
+ if ((this.mResponseLength + aLength) > this.mRequestor.kMaxResponseLength)
+ {
+ aRequest.cancel(Cr.NS_ERROR_FAILURE);
+ this.mChannel = undefined;
+ this.mRejectCallback(new TorLauncherBridgeDB.error(500,
+ "Moat response too large"));
+ return;
+ }
+
+ this.mResponseLength += aLength;
+ let scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ scriptableStream.init(aStream);
+ this.mResponseBody += scriptableStream.read(aLength);
+ },
+
+ cancelMoatRequest: function()
+ {
+ let didCancel = false;
+ let rv = Cr.NS_ERROR_ABORT;
+ if (this.mChannel)
+ {
+ this.mChannel.cancel(rv);
+ this.mChannel = undefined;
+ didCancel = true;
+ }
+
+ this.mRejectCallback(rv);
+ return didCancel;
+ },
+
+ _parseFetchResponse: function(aResponse)
+ {
+ /*
+ * Expected response if successful:
+ * {
+ * "data": {
+ * "id": "1",
+ * "type": "moat-challenge",
+ * "version": "0.1.0",
+ * "transport": TRANSPORT,
+ * "image": CAPTCHA,
+ * "challenge": CHALLENGE
+ * }
+ * }
+ *
+ * If there is no overlap between the type of bridge we requested and
+ * the transports which BridgeDB supports, the response is the same except
+ * the transport property will contain an array of supported transports:
+ * ...
+ * "transport": [ "TRANSPORT", "TRANSPORT", ... ],
+ * ...
+ */
+
+ // We do not check aResponse.id because it may vary.
+ let errStr;
+ if (aResponse.data.type !== this.mRequestor.kMoatFetchResponseType)
+ errStr = "unexpected response type";
+ else if (!aResponse.data.transport)
+ errStr = "missing transport";
+ else if (!aResponse.data.challenge)
+ errStr = "missing challenge";
+ else if (!aResponse.data.image)
+ errStr = "missing CAPTCHA image";
+
+ if (errStr)
+ {
+ this.mRejectCallback(new TorLauncherBridgeDB.error(500, errStr));
+ }
+ else
+ {
+ let imageURI = "data:image/jpeg;base64,"
+ + encodeURIComponent(aResponse.data.image);
+ // If there was no overlap between the bridge type we requested and what
+ // BridgeDB has, we use the first type that BridgeDB can provide.
+ let t = aResponse.data.transport;
+ if (Array.isArray(t))
+ t = t[0];
+ this.mResolveCallback({ captchaImage: imageURI,
+ transport: t,
+ challenge: aResponse.data.challenge });
+ }
+ }, // _parseFetchResponse
+
+ _parseCheckResponse: function(aResponse)
+ {
+ /*
+ * Expected response if successful:
+ * {
+ * "data": {
+ * "id": "3",
+ * "type": "moat-bridges",
+ * "version": "0.1.0",
+ * "bridges": [ "BRIDGE_LINE", ... ],
+ * "qrcode": "QRCODE"
+ * }
+ * }
+ */
+
+ // We do not check aResponse.id because it may vary.
+ // To be robust, we treat a zero-length bridge array the same as the 404
+ // error (no bridges available), which is handled inside onStopRequest().
+ let errStr;
+ if (aResponse.data.type !== this.mRequestor.kMoatCheckResponseType)
+ errStr = "unexpected response type";
+ else if (!aResponse.data.bridges || (aResponse.data.bridges.length == 0))
+ errStr = TorLauncherUtil.getLocalizedString("no_bridges_available");
+
+ if (errStr)
+ this.mRejectCallback(new TorLauncherBridgeDB.error(500, errStr));
+ else
+ this.mResolveCallback({ bridges: aResponse.data.bridges });
+ } // _parseCheckResponse
+};
diff --git a/src/modules/tl-util.jsm b/src/modules/tl-util.jsm
index bb84bdf..a79e2bd 100644
--- a/src/modules/tl-util.jsm
+++ b/src/modules/tl-util.jsm
@@ -1,4 +1,4 @@
-// Copyright (c) 2017, The Tor Project, Inc.
+// Copyright (c) 2018, The Tor Project, Inc.
// See LICENSE for licensing information.
//
// vim: set sw=2 sts=2 ts=8 et syntax=javascript:
@@ -12,8 +12,10 @@ let EXPORTED_SYMBOLS = [ "TorLauncherUtil" ];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
+const Cr = Components.results;
const kPropBundleURI = "chrome://torlauncher/locale/torlauncher.properties";
const kPropNamePrefix = "torlauncher.";
+const kPrefBranchDefaultBridge = "extensions.torlauncher.default_bridge.";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TorLauncherLogger",
@@ -161,6 +163,24 @@ let TorLauncherUtil = // Public
return aStringName;
},
+ getLocalizedStringForError: function(aNSResult)
+ {
+ for (let prop in Cr)
+ {
+ if (Cr[prop] == aNSResult)
+ {
+ let key = "nsresult." + prop;
+ let rv = this.getLocalizedString(key);
+ if (rv !== key)
+ return rv;
+
+ return prop; // As a fallback, return the NS_ERROR... name.
+ }
+ }
+
+ return undefined;
+ },
+
getLocalizedBootstrapStatus: function(aStatusObj, aKeyword)
{
if (!aStatusObj || !aKeyword)
@@ -276,6 +296,13 @@ let TorLauncherUtil = // Public
} catch (e) {}
},
+ getPrefBranch: function(aBranchName)
+ {
+ return Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefService)
+ .getBranch(aBranchName);
+ },
+
// Currently, this returns a random permutation of an array, bridgeArray.
// Later, we might want to change this function to weight based on the
// bridges' bandwidths.
@@ -361,9 +388,7 @@ let TorLauncherUtil = // Public
{
try
{
- var prefBranch = Cc["@mozilla.org/preferences-service;1"]
- .getService(Ci.nsIPrefService)
- .getBranch("extensions.torlauncher.default_bridge.");
+ var prefBranch = this.getPrefBranch(kPrefBranchDefaultBridge);
var childPrefs = prefBranch.getChildList("", []);
var typeArray = [];
for (var i = 0; i < childPrefs.length; ++i)
@@ -390,9 +415,7 @@ let TorLauncherUtil = // Public
try
{
- var prefBranch = Cc["@mozilla.org/preferences-service;1"]
- .getService(Ci.nsIPrefService)
- .getBranch("extensions.torlauncher.default_bridge.");
+ var prefBranch = this.getPrefBranch(kPrefBranchDefaultBridge);
var childPrefs = prefBranch.getChildList("", []);
var bridgeArray = [];
// The pref service seems to return the values in reverse order, so
@@ -430,11 +453,13 @@ let TorLauncherUtil = // Public
let isRelativePath = false;
let isUserData = (aTorFileType != "tor") &&
+ (aTorFileType != "pt-startup-dir") &&
(aTorFileType != "torrc-defaults");
let isControlIPC = ("control_ipc" == aTorFileType);
let isSOCKSIPC = ("socks_ipc" == aTorFileType);
let isIPC = isControlIPC || isSOCKSIPC;
let checkIPCPathLen = true;
+ let useAppDir = false;
const kControlIPCFileName = "control.socket";
const kSOCKSIPCFileName = "socks.socket";
@@ -523,6 +548,8 @@ let TorLauncherUtil = // Public
{
if ("tor" == aTorFileType)
path = "TorBrowser\\Tor\\tor.exe";
+ else if ("pt-startup-dir" == aTorFileType)
+ useAppDir = true;
else if ("torrc-defaults" == aTorFileType)
path = "TorBrowser\\Tor\\torrc-defaults";
else if ("torrc" == aTorFileType)
@@ -534,6 +561,8 @@ let TorLauncherUtil = // Public
{
if ("tor" == aTorFileType)
path = "Contents/Resources/TorBrowser/Tor/tor";
+ else if ("pt-startup-dir" == aTorFileType)
+ path = "Contents/MacOS/Tor";
else if ("torrc-defaults" == aTorFileType)
path = "Contents/Resources/TorBrowser/Tor/torrc-defaults";
else if ("torrc" == aTorFileType)
@@ -547,6 +576,8 @@ let TorLauncherUtil = // Public
{
if ("tor" == aTorFileType)
path = "TorBrowser/Tor/tor";
+ else if ("pt-startup-dir" == aTorFileType)
+ useAppDir = true;
else if ("torrc-defaults" == aTorFileType)
path = "TorBrowser/Tor/torrc-defaults";
else if ("torrc" == aTorFileType)
@@ -562,6 +593,8 @@ let TorLauncherUtil = // Public
// This block is used for the non-TorBrowser-Data/ case.
if ("tor" == aTorFileType)
path = "Tor\\tor.exe";
+ else if ("pt-startup-dir" == aTorFileType)
+ useAppDir = true;
else if ("torrc-defaults" == aTorFileType)
path = "Data\\Tor\\torrc-defaults";
else if ("torrc" == aTorFileType)
@@ -574,6 +607,8 @@ let TorLauncherUtil = // Public
// This block is also used for the non-TorBrowser-Data/ case.
if ("tor" == aTorFileType)
path = "Tor/tor";
+ else if ("pt-startup-dir" == aTorFileType)
+ useAppDir = true;
else if ("torrc-defaults" == aTorFileType)
path = "Data/Tor/torrc-defaults";
else if ("torrc" == aTorFileType)
@@ -584,13 +619,17 @@ let TorLauncherUtil = // Public
path = "Data/Tor/" + ipcFileName;
}
- if (!path)
+ if (!path && !useAppDir)
return null;
}
try
{
- if (path)
+ if (useAppDir)
+ {
+ torFile = TLUtilInternal._appDir.clone();
+ }
+ else if (path)
{
if (isRelativePath)
{
[View Less]
1
0

[tor-browser/tor-browser-52.6.0esr-8.0-2] Bug 1370027: Part 1 - Cleanly handle a subprocess child being reaped by NSPR. r=aswan
by gk@torproject.org 01 Mar '18
by gk@torproject.org 01 Mar '18
01 Mar '18
commit f7e0d580f4159357cfa3dc69cb9ed4d9027e9b9f
Author: Kris Maglione <maglione.k(a)gmail.com>
Date: Tue Jun 6 16:00:53 2017 -0700
Bug 1370027: Part 1 - Cleanly handle a subprocess child being reaped by NSPR. r=aswan
The first time any other code in the parent process uses NSPR (usually via
nsIProcess) to spawn a new process, it spawns a thread to contuously wait for
any child process to exit. This thread winds up reaping our child processes
before we get the …
[View More]chance to wait for them, which leads us to continuously poll
for them to exit.
We don't have a good way to handle this, but checking the error status of
waitpid at least prevents us from failing catastrophically.
MozReview-Commit-ID: 75Z1yUHUmjy
--HG--
extra : rebase_source : db45f781190b6fc84873c32c611134326736a1ba
This closes our bug 25389.
---
dom/system/OSFileConstants.cpp | 1 +
.../modules/subprocess/subprocess_worker_unix.js | 28 +++++++++++++---------
2 files changed, 18 insertions(+), 11 deletions(-)
diff --git a/dom/system/OSFileConstants.cpp b/dom/system/OSFileConstants.cpp
index 945233f4c879..7d6aafff61cb 100644
--- a/dom/system/OSFileConstants.cpp
+++ b/dom/system/OSFileConstants.cpp
@@ -549,6 +549,7 @@ static const dom::ConstantSpec gLibcProperties[] =
INT_CONSTANT(EFAULT),
INT_CONSTANT(EFBIG),
INT_CONSTANT(EINVAL),
+ INT_CONSTANT(EINTR),
INT_CONSTANT(EIO),
INT_CONSTANT(EISDIR),
#if defined(ELOOP) // not defined with VC9
diff --git a/toolkit/modules/subprocess/subprocess_worker_unix.js b/toolkit/modules/subprocess/subprocess_worker_unix.js
index 839402deb189..1584a9df508b 100644
--- a/toolkit/modules/subprocess/subprocess_worker_unix.js
+++ b/toolkit/modules/subprocess/subprocess_worker_unix.js
@@ -463,19 +463,25 @@ class Process extends BaseProcess {
let status = ctypes.int();
let res = libc.waitpid(this.pid, status.address(), LIBC.WNOHANG);
- if (res == this.pid) {
- let sig = unix.WTERMSIG(status.value);
- if (sig) {
- this.exitCode = -sig;
- } else {
- this.exitCode = unix.WEXITSTATUS(status.value);
- }
+ // If there's a failure here and we get any errno other than EINTR, it
+ // means that the process has been reaped by another thread (most likely
+ // the nspr process wait thread), and its actual exit status is not
+ // available to us. In that case, we have to assume success.
+ if (res == 0 || (res == -1 && ctypes.errno == LIBC.EINTR)) {
+ return null;
+ }
- this.fd.dispose();
- io.updatePollFds();
- this.resolveExit(this.exitCode);
- return this.exitCode;
+ let sig = unix.WTERMSIG(status.value);
+ if (sig) {
+ this.exitCode = -sig;
+ } else {
+ this.exitCode = unix.WEXITSTATUS(status.value);
}
+
+ this.fd.dispose();
+ io.updatePollFds();
+ this.resolveExit(this.exitCode);
+ return this.exitCode;
}
}
[View Less]
1
0