diff --git a/cli/onionshare_cli/censorship.py b/cli/onionshare_cli/censorship.py index 4ab5c366..9268f578 100644 --- a/cli/onionshare_cli/censorship.py +++ b/cli/onionshare_cli/censorship.py @@ -19,8 +19,6 @@ along with this program. If not, see . """ import requests -from .meek import MeekNotRunning - class CensorshipCircumventionError(Exception): """ @@ -47,15 +45,12 @@ class CensorshipCircumvention(object): self.api_proxies = {} if meek: self.meek = meek - if not self.meek.meek_proxies: - raise MeekNotRunning() - else: - self.common.log( - "CensorshipCircumvention", - "__init__", - "Using Meek with CensorshipCircumvention API", - ) - self.api_proxies = self.meek.meek_proxies + self.common.log( + "CensorshipCircumvention", + "__init__", + "Using Meek with CensorshipCircumvention API", + ) + self.api_proxies = self.meek.meek_proxies if onion: self.onion = onion if not self.onion.is_authenticated: @@ -100,7 +95,7 @@ class CensorshipCircumvention(object): if r.status_code != 200: self.common.log( "CensorshipCircumvention", - "censorship_obtain_map", + "request_map", f"status_code={r.status_code}", ) return False @@ -110,7 +105,7 @@ class CensorshipCircumvention(object): if "errors" in result: self.common.log( "CensorshipCircumvention", - "censorship_obtain_map", + "request_map", f"errors={result['errors']}", ) return False @@ -138,7 +133,7 @@ class CensorshipCircumvention(object): if country: self.common.log( "CensorshipCircumvention", - "censorship_obtain_settings", + "request_settings", f"Trying to obtain bridges for country={country}", ) data = {"country": country} @@ -154,7 +149,7 @@ class CensorshipCircumvention(object): if r.status_code != 200: self.common.log( "CensorshipCircumvention", - "censorship_obtain_settings", + "request_settings", f"status_code={r.status_code}", ) return False @@ -164,7 +159,7 @@ class CensorshipCircumvention(object): if "errors" in result: self.common.log( "CensorshipCircumvention", - "censorship_obtain_settings", + "request_settings", f"errors={result['errors']}", ) return False @@ -175,7 +170,7 @@ class CensorshipCircumvention(object): if not "settings" in result: self.common.log( "CensorshipCircumvention", - "censorship_obtain_settings", + "request_settings", "No settings found for this country", ) return False @@ -200,7 +195,7 @@ class CensorshipCircumvention(object): if r.status_code != 200: self.common.log( "CensorshipCircumvention", - "censorship_obtain_builtin_bridges", + "request_builtin_bridges", f"status_code={r.status_code}", ) return False @@ -210,7 +205,7 @@ class CensorshipCircumvention(object): if "errors" in result: self.common.log( "CensorshipCircumvention", - "censorship_obtain_builtin_bridges", + "request_builtin_bridges", f"errors={result['errors']}", ) return False @@ -237,42 +232,15 @@ class CensorshipCircumvention(object): f"Obtained bridges: {bridges}", ) bridge_strings = bridges["bridge_strings"] - bridge_type = bridges["type"] - bridge_source = bridges["source"] - # If the recommended bridge source is to use the built-in - # bridges, set that in our settings, as if the user had - # selected the built-in bridges for a specific PT themselves. - # - if bridge_source == "builtin": - self.common.log( - "CensorshipCircumvention", - "save_settings", - "Will be using built-in bridges", - ) - self.settings.set("bridges_type", "built-in") - if bridge_type == "obfs4": - self.settings.set("bridges_builtin_pt", "obfs4") - if bridge_type == "snowflake": - self.settings.set("bridges_builtin_pt", "snowflake") - if bridge_type == "meek": - self.settings.set("bridges_builtin_pt", "meek-azure") + self.settings.set("bridges_type", "custom") + + # Sanity check the bridges provided from the Tor API before saving + bridges_checked = self.common.check_bridges_valid(bridge_strings) + + if bridges_checked: + self.settings.set("bridges_custom", "\n".join(bridges_checked)) bridges_ok = True - else: - self.common.log( - "CensorshipCircumvention", - "save_settings", - "Will be using custom bridges", - ) - # Any other type of bridge we can treat as custom. - self.settings.set("bridges_type", "custom") - - # Sanity check the bridges provided from the Tor API before saving - bridges_checked = self.common.check_bridges_valid(bridge_strings) - - if bridges_checked: - self.settings.set("bridges_custom", "\n".join(bridges_checked)) - bridges_ok = True # If we got any good bridges, save them to settings and return. if bridges_ok: @@ -291,3 +259,42 @@ class CensorshipCircumvention(object): "Could not use any of the obtained bridges.", ) return False + + + def request_default_bridges(self): + """ + Retrieves the list of default fall-back bridges from the Tor Project. + + These are intended for when no censorship settings were found for a + specific country, but maybe there was some connection issue anyway. + """ + if not self.api_proxies: + return False + endpoint = "https://bridges.torproject.org/moat/circumvention/defaults" + try: + r = requests.get( + endpoint, + headers={"Content-Type": "application/vnd.api+json"}, + proxies=self.api_proxies, + ) + if r.status_code != 200: + self.common.log( + "CensorshipCircumvention", + "request_default_bridges", + f"status_code={r.status_code}", + ) + return False + + result = r.json() + + if "errors" in result: + self.common.log( + "CensorshipCircumvention", + "request_default_bridges", + f"errors={result['errors']}", + ) + return False + + return result + except requests.exceptions.RequestException as e: + raise CensorshipCircumventionError(e) diff --git a/cli/onionshare_cli/meek.py b/cli/onionshare_cli/meek.py index 777c0ab6..81ef6c6e 100644 --- a/cli/onionshare_cli/meek.py +++ b/cli/onionshare_cli/meek.py @@ -69,7 +69,7 @@ class Meek(object): if self.meek_client_file_path is None or not os.path.exists( self.meek_client_file_path ): - raise MeekNotFound() + raise MeekNotFound(self.common) # Start the Meek Client as a subprocess. self.common.log("Meek", "start", "Starting meek client") @@ -128,7 +128,7 @@ class Meek(object): if "CMETHOD-ERROR" in line: self.cleanup() - raise MeekNotRunning() + raise MeekNotRunning(self.common, line) break if self.meek_port: @@ -137,9 +137,8 @@ class Meek(object): "https": f"socks5h://{self.meek_host}:{self.meek_port}", } else: - self.common.log("Meek", "start", "Could not obtain the meek port") self.cleanup() - raise MeekNotRunning() + raise MeekNotRunning(self.common, "Could not obtain the meek port") def cleanup(self): """ @@ -182,8 +181,19 @@ class MeekNotRunning(Exception): number it started on, in order to do domain fronting. """ + def __init__(self, common, info=None): + self.common = common + msg = "Meek experienced an error starting up" + if info: + msg = msg + f": {info}" + self.common.log("MeekNotRunning", "__init__", msg) + class MeekNotFound(Exception): """ We were unable to find the Meek Client binary. """ + + def __init__(self, common): + self.common = common + self.common.log("MeekNotFound", "__init__", "Could not find the meek binary") diff --git a/cli/onionshare_cli/onion.py b/cli/onionshare_cli/onion.py index 6ef4af2c..6e1dad74 100644 --- a/cli/onionshare_cli/onion.py +++ b/cli/onionshare_cli/onion.py @@ -954,20 +954,6 @@ class Onion(object): "update_builtin_bridges", f"Obtained bridges: {builtin_bridges}", ) - if builtin_bridges["meek"]: - # Meek bridge needs to be defined as "meek_lite", not "meek", - # for it to work with obfs4proxy. - # We also refer to this bridge type as 'meek-azure' in our settings. - # So first, rename the key in the dict - builtin_bridges["meek-azure"] = builtin_bridges.pop("meek") - new_meek_bridges = [] - # Now replace the values. They also need the url/front params appended - for item in builtin_bridges["meek-azure"]: - newline = item.replace("meek", "meek_lite") - new_meek_bridges.append( - f"{newline} url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com" - ) - builtin_bridges["meek-azure"] = new_meek_bridges # Save the new settings self.settings.set("bridges_builtin", builtin_bridges) self.settings.save() diff --git a/desktop/onionshare/connection_tab.py b/desktop/onionshare/connection_tab.py index 1cf3c376..d7b49563 100644 --- a/desktop/onionshare/connection_tab.py +++ b/desktop/onionshare/connection_tab.py @@ -181,7 +181,9 @@ class AutoConnectTab(QtWidgets.QWidget): self.tor_con.start(self.curr_settings) def _got_no_bridges(self): - # If we got no bridges, try connecting again using built-in obfs4 bridges + # If we got no bridges, even after trying the default bridges + # provided by the Censorship API, try connecting again using + # our built-in obfs4 bridges self.curr_settings.set("bridges_type", "built-in") self.curr_settings.set("bridges_builtin_pt", "obfs4") self.curr_settings.set("bridges_enabled", True) @@ -244,6 +246,18 @@ class AutoConnectTab(QtWidgets.QWidget): bridge_settings = self.censorship_circumvention.request_settings( country=country ) + + if not bridge_settings: + # Fall back to trying the default bridges from the API + self.common.log( + "AutoConnectTab", + "use_bridge_connect_clicked", + "Falling back to trying default bridges provided by the Censorship Circumvention API", + ) + bridge_settings = ( + self.censorship_circumvention.request_default_bridges() + ) + self.common.gui.meek.cleanup() if bridge_settings and self.censorship_circumvention.save_settings( diff --git a/desktop/onionshare/moat_dialog.py b/desktop/onionshare/moat_dialog.py index fd04ee9c..db4bdf29 100644 --- a/desktop/onionshare/moat_dialog.py +++ b/desktop/onionshare/moat_dialog.py @@ -236,14 +236,10 @@ class MoatThread(QtCore.QThread): # Start Meek so that we can do domain fronting try: self.meek.start() - except MeekNotFound: - self.common.log("MoatThread", "run", f"Could not find meek-client") - self.bridgedb_error.emit() - return - except MeekNotRunning: - self.common.log( - "MoatThread", "run", f"Ran meek-client, but there was an error" - ) + except ( + MeekNotFound, + MeekNotRunning, + ): self.bridgedb_error.emit() return