diff options
Diffstat (limited to 'cli/onionshare_cli/censorship.py')
-rw-r--r-- | cli/onionshare_cli/censorship.py | 229 |
1 files changed, 161 insertions, 68 deletions
diff --git a/cli/onionshare_cli/censorship.py b/cli/onionshare_cli/censorship.py index c1845f6a..4ab5c366 100644 --- a/cli/onionshare_cli/censorship.py +++ b/cli/onionshare_cli/censorship.py @@ -22,6 +22,12 @@ import requests from .meek import MeekNotRunning +class CensorshipCircumventionError(Exception): + """ + There was a problem connecting to the Tor CensorshipCircumvention API. + """ + + class CensorshipCircumvention(object): """ Connect to the Tor Moat APIs to retrieve censorship @@ -47,7 +53,7 @@ class CensorshipCircumvention(object): self.common.log( "CensorshipCircumvention", "__init__", - "Using Meek with CensorShipCircumvention API", + "Using Meek with CensorshipCircumvention API", ) self.api_proxies = self.meek.meek_proxies if onion: @@ -58,7 +64,7 @@ class CensorshipCircumvention(object): self.common.log( "CensorshipCircumvention", "__init__", - "Using Tor with CensorShipCircumvention API", + "Using Tor with CensorshipCircumvention API", ) (socks_address, socks_port) = self.onion.get_tor_socks_port() self.api_proxies = { @@ -84,31 +90,34 @@ class CensorshipCircumvention(object): if country: data = {"country": country} - r = requests.post( - endpoint, - json=data, - headers={"Content-Type": "application/vnd.api+json"}, - proxies=self.api_proxies, - ) - if r.status_code != 200: - self.common.log( - "CensorshipCircumvention", - "censorship_obtain_map", - f"status_code={r.status_code}", + try: + r = requests.post( + endpoint, + json=data, + headers={"Content-Type": "application/vnd.api+json"}, + proxies=self.api_proxies, ) - return False + if r.status_code != 200: + self.common.log( + "CensorshipCircumvention", + "censorship_obtain_map", + f"status_code={r.status_code}", + ) + return False - result = r.json() + result = r.json() - if "errors" in result: - self.common.log( - "CensorshipCircumvention", - "censorship_obtain_map", - f"errors={result['errors']}", - ) - return False + if "errors" in result: + self.common.log( + "CensorshipCircumvention", + "censorship_obtain_map", + f"errors={result['errors']}", + ) + return False - return result + return result + except requests.exceptions.RequestException as e: + raise CensorshipCircumventionError(e) def request_settings(self, country=False, transports=False): """ @@ -127,45 +136,53 @@ class CensorshipCircumvention(object): endpoint = "https://bridges.torproject.org/moat/circumvention/settings" data = {} if country: - data = {"country": country} - if transports: - data.append({"transports": transports}) - r = requests.post( - endpoint, - json=data, - headers={"Content-Type": "application/vnd.api+json"}, - proxies=self.api_proxies, - ) - if r.status_code != 200: self.common.log( "CensorshipCircumvention", "censorship_obtain_settings", - f"status_code={r.status_code}", + f"Trying to obtain bridges for country={country}", ) - return False + data = {"country": country} + if transports: + data.append({"transports": transports}) + try: + r = requests.post( + endpoint, + json=data, + headers={"Content-Type": "application/vnd.api+json"}, + proxies=self.api_proxies, + ) + if r.status_code != 200: + self.common.log( + "CensorshipCircumvention", + "censorship_obtain_settings", + f"status_code={r.status_code}", + ) + return False - result = r.json() + result = r.json() - if "errors" in result: - self.common.log( - "CensorshipCircumvention", - "censorship_obtain_settings", - f"errors={result['errors']}", - ) - return False + if "errors" in result: + self.common.log( + "CensorshipCircumvention", + "censorship_obtain_settings", + f"errors={result['errors']}", + ) + return False - # There are no settings - perhaps this country doesn't require censorship circumvention? - # This is not really an error, so we can just check if False and assume direct Tor - # connection will work. - if not "settings" in result: - self.common.log( - "CensorshipCircumvention", - "censorship_obtain_settings", - "No settings found for this country", - ) - return False + # There are no settings - perhaps this country doesn't require censorship circumvention? + # This is not really an error, so we can just check if False and assume direct Tor + # connection will work. + if not "settings" in result: + self.common.log( + "CensorshipCircumvention", + "censorship_obtain_settings", + "No settings found for this country", + ) + return False - return result + return result + except requests.exceptions.RequestException as e: + raise CensorshipCircumventionError(e) def request_builtin_bridges(self): """ @@ -174,27 +191,103 @@ class CensorshipCircumvention(object): 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.api_proxies, + try: + r = requests.post( + endpoint, + headers={"Content-Type": "application/vnd.api+json"}, + proxies=self.api_proxies, + ) + if r.status_code != 200: + self.common.log( + "CensorshipCircumvention", + "censorship_obtain_builtin_bridges", + f"status_code={r.status_code}", + ) + return False + + result = r.json() + + if "errors" in result: + self.common.log( + "CensorshipCircumvention", + "censorship_obtain_builtin_bridges", + f"errors={result['errors']}", + ) + return False + + return result + except requests.exceptions.RequestException as e: + raise CensorshipCircumventionError(e) + + def save_settings(self, settings, bridge_settings): + """ + Checks the bridges and saves them in settings. + """ + bridges_ok = False + self.settings = settings + + # @TODO there might be several bridge types recommended. + # Should we attempt to iterate over each type if one of them fails to connect? + # But if so, how to stop it starting 3 separate Tor connection threads? + # for bridges in request_bridges["settings"]: + bridges = bridge_settings["settings"][0]["bridges"] + self.common.log( + "CensorshipCircumvention", + "save_settings", + f"Obtained bridges: {bridges}", ) - if r.status_code != 200: + 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", - "censorship_obtain_builtin_bridges", - f"status_code={r.status_code}", + "save_settings", + "Will be using built-in bridges", ) - return False + 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") + 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) - result = r.json() + if bridges_checked: + self.settings.set("bridges_custom", "\n".join(bridges_checked)) + bridges_ok = True - if "errors" in result: + # If we got any good bridges, save them to settings and return. + if bridges_ok: self.common.log( "CensorshipCircumvention", - "censorship_obtain_builtin_bridges", - f"errors={result['errors']}", + "save_settings", + "Saving settings with automatically-obtained bridges", + ) + self.settings.set("bridges_enabled", True) + self.settings.save() + return True + else: + self.common.log( + "CensorshipCircumvention", + "save_settings", + "Could not use any of the obtained bridges.", ) return False - - return result |