From 39d624e923d61dbbaf8d4216a4364c5b443d8802 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 13 Oct 2021 21:11:56 -0700 Subject: [PATCH] Allow selecting a snowflake bridge, and make it try to use the snowflake bridge --- cli/onionshare_cli/common.py | 6 ++ cli/onionshare_cli/onion.py | 5 ++ .../resources/torrc_template-meek_lite_amazon | 2 - cli/onionshare_cli/settings.py | 1 + desktop/src/onionshare/gui_common.py | 16 +++- .../src/onionshare/resources/locale/en.json | 12 +-- desktop/src/onionshare/tor_settings_dialog.py | 88 ++++++++++++++----- 7 files changed, 98 insertions(+), 32 deletions(-) delete mode 100644 cli/onionshare_cli/resources/torrc_template-meek_lite_amazon diff --git a/cli/onionshare_cli/common.py b/cli/onionshare_cli/common.py index 78da8882..945a75bb 100644 --- a/cli/onionshare_cli/common.py +++ b/cli/onionshare_cli/common.py @@ -312,6 +312,9 @@ class Common: # Look in resources first base_path = self.get_resource_path("tor") if os.path.exists(base_path): + self.log( + "Common", "get_tor_paths", f"using tor binaries in {base_path}" + ) tor_path = os.path.join(base_path, "tor") tor_geo_ip_file_path = os.path.join(base_path, "geoip") tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6") @@ -319,6 +322,9 @@ class Common: snowflake_file_path = os.path.join(base_path, "snowflake-client") else: # Fallback to looking in the path + self.log( + "Common", "get_tor_paths", f"using tor binaries in system path" + ) tor_path = shutil.which("tor") if not tor_path: raise CannotFindTor() diff --git a/cli/onionshare_cli/onion.py b/cli/onionshare_cli/onion.py index a0f967b9..a4453651 100644 --- a/cli/onionshare_cli/onion.py +++ b/cli/onionshare_cli/onion.py @@ -326,6 +326,11 @@ class Onion(object): ) as o: for line in o: f.write(line) + elif self.settings.get("tor_bridges_use_snowflake"): + # Taken from: tor-browser_en-US/Browser/TorBrowser/Data/Tor/torrc-defaults + f.write( + f"ClientTransportPlugin snowflake exec {self.snowflake_file_path} -url https://snowflake-broker.torproject.net.global.prod.fastly.net/ -front cdn.sstatic.net -ice stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478\n" + ) if self.settings.get("tor_bridges_use_custom_bridges"): if "obfs4" in self.settings.get("tor_bridges_use_custom_bridges"): diff --git a/cli/onionshare_cli/resources/torrc_template-meek_lite_amazon b/cli/onionshare_cli/resources/torrc_template-meek_lite_amazon deleted file mode 100644 index 606ae889..00000000 --- a/cli/onionshare_cli/resources/torrc_template-meek_lite_amazon +++ /dev/null @@ -1,2 +0,0 @@ -Bridge meek_lite 0.0.2.0:2 B9E7141C594AF25699E0079C1F0146F409495296 url=https://d2cly7j4zqgua7.cloudfront.net/ front=a0.awsstatic.com -UseBridges 1 \ No newline at end of file diff --git a/cli/onionshare_cli/settings.py b/cli/onionshare_cli/settings.py index 4755d5b3..37c00bb6 100644 --- a/cli/onionshare_cli/settings.py +++ b/cli/onionshare_cli/settings.py @@ -108,6 +108,7 @@ class Settings(object): "no_bridges": True, "tor_bridges_use_obfs4": False, "tor_bridges_use_meek_lite_azure": False, + "tor_bridges_use_snowflake": False, "tor_bridges_use_custom_bridges": "", "persistent_tabs": [], "locale": None, # this gets defined in fill_in_defaults() diff --git a/desktop/src/onionshare/gui_common.py b/desktop/src/onionshare/gui_common.py index cb10eb3b..5634d5f6 100644 --- a/desktop/src/onionshare/gui_common.py +++ b/desktop/src/onionshare/gui_common.py @@ -405,7 +405,21 @@ class GuiCommon: def get_tor_paths(self): if self.common.platform == "Linux": - return self.common.get_tor_paths() + base_path = self.get_resource_path("tor") + if os.path.exists(base_path): + tor_path = os.path.join(base_path, "tor") + tor_geo_ip_file_path = os.path.join(base_path, "geoip") + tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6") + obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy") + snowflake_file_path = os.path.join(base_path, "snowflake-client") + else: + # Fallback to looking in the path + tor_path = shutil.which("tor") + obfs4proxy_file_path = shutil.which("obfs4proxy") + snowflake_file_path = shutil.which("snowflake-client") + prefix = os.path.dirname(os.path.dirname(tor_path)) + tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip") + tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6") if self.common.platform == "Windows": base_path = self.get_resource_path("tor") diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json index 38da16fb..3d6c8539 100644 --- a/desktop/src/onionshare/resources/locale/en.json +++ b/desktop/src/onionshare/resources/locale/en.json @@ -60,13 +60,13 @@ "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", "gui_settings_authenticate_password_option": "Password", "gui_settings_password_label": "Password", - "gui_settings_tor_bridges": "Would you like to Use a Tor bridge?", + "gui_settings_tor_bridges": "Connect using a Tor bridge?", + "gui_settings_tor_bridges_label": "Bridges help you access the Tor Network in places where Tor is blocked. Depending on where you are, one bridge may work better than another.", "gui_settings_tor_bridges_no_bridges_radio_option": "Don't use a bridge", - "gui_settings_tor_bridges_obfs4_radio_option": "Use built-in obfs4 pluggable transports", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Use built-in obfs4 pluggable transports (requires obfs4proxy)", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek_lite (Azure) pluggable transports", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Use built-in meek_lite (Azure) pluggable transports (requires obfs4proxy)", - "gui_settings_meek_lite_expensive_warning": "Warning: The meek_lite bridges are very costly for the Tor Project to run.

Only use them if unable to connect to Tor directly, via obfs4 transports, or other normal bridges.", + "gui_settings_tor_bridges_obfs4_radio_option": "Use built-in obfs4 bridge", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek-azure bridge", + "gui_settings_tor_bridges_snowflake_radio_option": "Use built-in snowflake bridge", + "gui_settings_meek_lite_expensive_warning": "Warning: The meek-azure bridges are very costly for the Tor Project to run.

Only use them if unable to connect to Tor directly, via obfs4 transports, or other normal bridges.", "gui_settings_tor_bridges_custom_radio_option": "Use custom bridges", "gui_settings_tor_bridges_custom_label": "You can get bridges from https://bridges.torproject.org", "gui_settings_tor_bridges_invalid": "None of the bridges you added work.\nDouble-check them or add others.", diff --git a/desktop/src/onionshare/tor_settings_dialog.py b/desktop/src/onionshare/tor_settings_dialog.py index a1b02313..00d221a7 100644 --- a/desktop/src/onionshare/tor_settings_dialog.py +++ b/desktop/src/onionshare/tor_settings_dialog.py @@ -89,6 +89,9 @@ class TorSettingsDialog(QtWidgets.QDialog): # Bridge options for bundled tor + bridges_label = QtWidgets.QLabel(strings._("gui_settings_tor_bridges_label")) + bridges_label.setWordWrap(True) + # No bridges option radio self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton( strings._("gui_settings_tor_bridges_no_bridges_radio_option") @@ -107,39 +110,55 @@ class TorSettingsDialog(QtWidgets.QDialog): # obfs4 option radio # if the obfs4proxy binary is missing, we can't use obfs4 transports - if not self.obfs4proxy_file_path or not os.path.isfile( - self.obfs4proxy_file_path - ): - self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( - strings._("gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy") - ) - self.tor_bridges_use_obfs4_radio.setEnabled(False) - else: - self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( - strings._("gui_settings_tor_bridges_obfs4_radio_option") - ) + self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_obfs4_radio_option") + ) self.tor_bridges_use_obfs4_radio.toggled.connect( self.tor_bridges_use_obfs4_radio_toggled ) - - # meek_lite-azure option radio - # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports if not self.obfs4proxy_file_path or not os.path.isfile( self.obfs4proxy_file_path ): - self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( - strings._( - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy" - ) - ) - self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False) - else: - self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( - strings._("gui_settings_tor_bridges_meek_lite_azure_radio_option") + self.common.log( + "TorSettingsDialog", + "__init__", + f"missing binary {self.obfs4proxy_file_path}, hiding obfs4 bridge", ) + self.tor_bridges_use_obfs4_radio.hide() + + # meek-azure option radio + # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports + self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_meek_lite_azure_radio_option") + ) self.tor_bridges_use_meek_lite_azure_radio.toggled.connect( self.tor_bridges_use_meek_lite_azure_radio_toggled ) + if not self.obfs4proxy_file_path or not os.path.isfile( + self.obfs4proxy_file_path + ): + self.common.log( + "TorSettingsDialog", + "__init__", + f"missing binary {self.obfs4proxy_file_path}, hiding meek-azure bridge", + ) + self.tor_bridges_use_meek_lite_azure_radio.hide() + + # snowflake option radio + # if the snowflake-client binary is missing, we can't use snowflake transports + self.tor_bridges_use_snowflake_radio = QtWidgets.QRadioButton( + strings._("gui_settings_tor_bridges_snowflake_radio_option") + ) + self.tor_bridges_use_snowflake_radio.toggled.connect( + self.tor_bridges_use_snowflake_radio_toggled + ) + if not self.snowflake_file_path or not os.path.isfile(self.snowflake_file_path): + self.common.log( + "TorSettingsDialog", + "__init__", + f"missing binary {self.snowflake_file_path}, hiding snowflake bridge", + ) + self.tor_bridges_use_snowflake_radio.hide() # Custom bridges radio and textbox self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton( @@ -178,9 +197,11 @@ class TorSettingsDialog(QtWidgets.QDialog): # Bridges layout/widget bridges_layout = QtWidgets.QVBoxLayout() + bridges_layout.addWidget(bridges_label) bridges_layout.addWidget(self.tor_bridges_no_bridges_radio) bridges_layout.addWidget(self.tor_bridges_use_obfs4_radio) bridges_layout.addWidget(self.tor_bridges_use_meek_lite_azure_radio) + bridges_layout.addWidget(self.tor_bridges_use_snowflake_radio) bridges_layout.addWidget(self.tor_bridges_use_custom_radio) bridges_layout.addWidget(self.tor_bridges_use_custom_textbox_options) @@ -411,6 +432,7 @@ class TorSettingsDialog(QtWidgets.QDialog): self.tor_bridges_no_bridges_radio.setChecked(True) self.tor_bridges_use_obfs4_radio.setChecked(False) self.tor_bridges_use_meek_lite_azure_radio.setChecked(False) + self.tor_bridges_use_snowflake_radio.setChecked(False) self.tor_bridges_use_custom_radio.setChecked(False) else: self.tor_bridges_no_bridges_radio.setChecked(False) @@ -420,6 +442,9 @@ class TorSettingsDialog(QtWidgets.QDialog): self.tor_bridges_use_meek_lite_azure_radio.setChecked( self.old_settings.get("tor_bridges_use_meek_lite_azure") ) + self.tor_bridges_use_snowflake_radio.setChecked( + self.old_settings.get("tor_bridges_use_snowflake") + ) if self.old_settings.get("tor_bridges_use_custom_bridges"): self.tor_bridges_use_custom_radio.setChecked(True) @@ -473,6 +498,13 @@ class TorSettingsDialog(QtWidgets.QDialog): QtWidgets.QMessageBox.Warning, ) + def tor_bridges_use_snowflake_radio_toggled(self, checked): + """ + snowflake bridges option was toggled. If checked, disable custom bridge options. + """ + if checked: + self.tor_bridges_use_custom_textbox_options.hide() + def tor_bridges_use_custom_radio_toggled(self, checked): """ Custom bridges option was toggled. If checked, show custom bridge options. @@ -712,21 +744,31 @@ class TorSettingsDialog(QtWidgets.QDialog): settings.set("no_bridges", True) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_snowflake", False) settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_obfs4_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", True) settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_snowflake", False) settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_meek_lite_azure_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", True) + settings.set("tor_bridges_use_snowflake", False) + settings.set("tor_bridges_use_custom_bridges", "") + if self.tor_bridges_use_snowflake_radio.isChecked(): + settings.set("no_bridges", False) + settings.set("tor_bridges_use_obfs4", False) + settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_snowflake", True) settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_custom_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", False) + settings.set("tor_bridges_use_snowflake", False) # Insert a 'Bridge' line at the start of each bridge. # This makes it easier to copy/paste a set of bridges