diff --git a/cli/onionshare_cli/censorship.py b/cli/onionshare_cli/censorship.py index f84b1058..9f41d61c 100644 --- a/cli/onionshare_cli/censorship.py +++ b/cli/onionshare_cli/censorship.py @@ -25,21 +25,46 @@ from .meek import MeekNotRunning class CensorshipCircumvention(object): """ Connect to the Tor Moat APIs to retrieve censorship - circumvention recommendations, over the Meek client. + circumvention recommendations or the latest bridges. + + We support reaching this API over Tor, or Meek + (domain fronting) if Tor is not connected. """ - def __init__(self, common, meek, domain_fronting=True): + def __init__(self, common, meek=None, onion=None): """ Set up the CensorshipCircumvention object to hold common and meek objects. """ self.common = common - self.meek = meek self.common.log("CensorshipCircumvention", "__init__") - - # Bail out if we requested domain fronting but we can't use meek - if domain_fronting and not self.meek.meek_proxies: - raise MeekNotRunning() + 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 + if onion: + self.onion = onion + if not self.onion.is_authenticated: + return False + else: + self.common.log( + "CensorshipCircumvention", + "__init__", + "Using Tor with CensorShipCircumvention API", + ) + (socks_address, socks_port) = self.onion.get_tor_socks_port() + self.api_proxies = { + "http": f"socks5h://{socks_address}:{socks_port}", + "https": f"socks5h://{socks_address}:{socks_port}", + } def request_map(self, country=False): """ @@ -52,6 +77,8 @@ class CensorshipCircumvention(object): Note that this API endpoint doesn't return actual bridges, it just returns the recommended bridge type countries. """ + if not self.api_proxies: + return False endpoint = "https://bridges.torproject.org/moat/circumvention/map" data = {} if country: @@ -61,7 +88,7 @@ class CensorshipCircumvention(object): endpoint, json=data, headers={"Content-Type": "application/vnd.api+json"}, - proxies=self.meek.meek_proxies, + proxies=self.api_proxies, ) if r.status_code != 200: self.common.log( @@ -95,6 +122,8 @@ class CensorshipCircumvention(object): Optionally, a list of transports can be specified in order to return recommended settings for just that transport type. """ + if not self.api_proxies: + return False endpoint = "https://bridges.torproject.org/moat/circumvention/settings" data = {} if country: @@ -105,7 +134,7 @@ class CensorshipCircumvention(object): endpoint, json=data, headers={"Content-Type": "application/vnd.api+json"}, - proxies=self.meek.meek_proxies, + proxies=self.api_proxies, ) if r.status_code != 200: self.common.log( @@ -142,11 +171,13 @@ class CensorshipCircumvention(object): """ Retrieves the list of built-in bridges from the Tor Project. """ + if not self.api_proxies: + return False endpoint = "https://bridges.torproject.org/moat/circumvention/builtin" r = requests.post( endpoint, headers={"Content-Type": "application/vnd.api+json"}, - proxies=self.meek.meek_proxies, + proxies=self.api_proxies, ) if r.status_code != 200: self.common.log( diff --git a/cli/onionshare_cli/onion.py b/cli/onionshare_cli/onion.py index c0a4e6e1..bd8e28df 100644 --- a/cli/onionshare_cli/onion.py +++ b/cli/onionshare_cli/onion.py @@ -914,7 +914,8 @@ class Onion(object): Use the CensorshipCircumvention API to fetch the latest built-in bridges and update them in settings. """ - got_builtin_bridges = False + builtin_bridges = False + meek = None # Try obtaining bridges over Tor, if we're connected to it. if self.is_authenticated: self.common.log( @@ -922,43 +923,14 @@ class Onion(object): "update_builtin_bridges", "Updating the built-in bridges. Trying over Tor first", ) - (socks_address, socks_port) = self.get_tor_socks_port() - tor_proxies = { - "http": f"socks5h://{socks_address}:{socks_port}", - "https": f"socks5h://{socks_address}:{socks_port}", - } - # Request a bridge - r = requests.post( - "https://bridges.torproject.org/moat/circumvention/builtin", - headers={"Content-Type": "application/vnd.api+json"}, - proxies=tor_proxies, + self.censorship_circumvention = CensorshipCircumvention( + self.common, None, self ) - if r.status_code != 200: - self.common.log( - "Onion", - "update_builtin_bridges", - f"Trying over Tor failed: status_code={r.status_code}", - ) + builtin_bridges = self.censorship_circumvention.request_builtin_bridges() - try: - builtin_bridges = r.json() - if "errors" in builtin_bridges: - self.common.log( - "Onion", - "update_builtin_bridges", - f"Trying over Tor failed: errors={builtin_bridges['errors']}", - ) - else: - got_builtin_bridges = builtin_bridges - except Exception as e: - self.common.log( - "Onion", - "update_builtin_bridges", - f"Hit exception when trying over Tor: {e}", - ) - - if not got_builtin_bridges: - # Fall back to using Meek, without Tor + if not builtin_bridges: + # Tor was not running or it failed to hit the Tor API. + # Fall back to using Meek (domain-fronting). self.common.log( "Onion", "update_builtin_bridges", @@ -966,35 +938,35 @@ class Onion(object): ) meek = Meek(self.common) meek.start() - self.censorship_circumvention = CensorshipCircumvention(self.common, meek) - got_builtin_bridges = ( - self.censorship_circumvention.request_builtin_bridges() + self.censorship_circumvention = CensorshipCircumvention( + self.common, meek, None ) + builtin_bridges = self.censorship_circumvention.request_builtin_bridges() meek.cleanup() - # If we got to this point, we have bridges - if got_builtin_bridges: + if builtin_bridges: + # If we got to this point, we have bridges self.common.log( "Onion", "update_builtin_bridges", - f"Obtained bridges: {got_builtin_bridges}", + f"Obtained bridges: {builtin_bridges}", ) - if got_builtin_bridges["meek"]: + 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 - got_builtin_bridges["meek-azure"] = got_builtin_bridges.pop("meek") + 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 got_builtin_bridges["meek-azure"]: + 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" ) - got_builtin_bridges["meek-azure"] = new_meek_bridges + builtin_bridges["meek-azure"] = new_meek_bridges # Save the new settings - self.settings.set("bridges_builtin", got_builtin_bridges) + self.settings.set("bridges_builtin", builtin_bridges) self.settings.save() else: self.common.log(