aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiguel Jacq <mig@mig5.net>2021-10-18 17:17:47 +1100
committerMiguel Jacq <mig@mig5.net>2021-10-18 17:17:47 +1100
commit0989f2b133a46f293a65c9e11a01e8a097e479a1 (patch)
tree26dcc2d37b87220af2722656fc0f19cc1169ee20
parentc9fa2308a7c9fb99b40d1c7b8c112c5b9f510d75 (diff)
downloadonionshare-0989f2b133a46f293a65c9e11a01e8a097e479a1.tar.gz
onionshare-0989f2b133a46f293a65c9e11a01e8a097e479a1.zip
Move Censorship stuff into its own class. Early attempt at subprocessing out to meek (unfinished)
-rw-r--r--cli/onionshare_cli/__init__.py17
-rw-r--r--cli/onionshare_cli/censorship.py216
-rw-r--r--cli/onionshare_cli/common.py73
-rw-r--r--cli/onionshare_cli/onion.py1
4 files changed, 226 insertions, 81 deletions
diff --git a/cli/onionshare_cli/__init__.py b/cli/onionshare_cli/__init__.py
index 4bc00929..ddba332e 100644
--- a/cli/onionshare_cli/__init__.py
+++ b/cli/onionshare_cli/__init__.py
@@ -27,13 +27,9 @@ from datetime import datetime
from datetime import timedelta
from .common import Common, CannotFindTor
+from .censorship import CensorshipCircumvention
from .web import Web
-from .onion import (
- TorErrorProtocolError,
- TorTooOldEphemeral,
- TorTooOldStealth,
- Onion,
-)
+from .onion import TorErrorProtocolError, TorTooOldEphemeral, TorTooOldStealth, Onion
from .onionshare import OnionShare
from .mode_settings import ModeSettings
@@ -94,12 +90,7 @@ def main(cwd=None):
help="Filename of persistent session",
)
# General args
- parser.add_argument(
- "--title",
- metavar="TITLE",
- default=None,
- help="Set a title",
- )
+ parser.add_argument("--title", metavar="TITLE", default=None, help="Set a title")
parser.add_argument(
"--public",
action="store_true",
@@ -409,7 +400,7 @@ def main(cwd=None):
sys.exit(1)
# Warn about sending large files over Tor
- if web.share_mode.download_filesize >= 157286400: # 150mb
+ if web.share_mode.download_filesize >= 157_286_400: # 150mb
print("")
print("Warning: Sending a large share could take hours")
print("")
diff --git a/cli/onionshare_cli/censorship.py b/cli/onionshare_cli/censorship.py
new file mode 100644
index 00000000..176f95e6
--- /dev/null
+++ b/cli/onionshare_cli/censorship.py
@@ -0,0 +1,216 @@
+# -*- coding: utf-8 -*-
+"""
+OnionShare | https://onionshare.org/
+
+Copyright (C) 2014-2021 Micah Lee, et al. <micah@micahflee.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+import requests
+import subprocess
+
+
+class CensorshipCircumvention:
+ """
+ The CensorShipCircumvention object contains methods to detect
+ and offer solutions to censorship when connecting to Tor.
+ """
+
+ def __init__(self, common):
+
+ self.common = common
+ self.common.log("CensorshipCircumvention", "__init__")
+
+ get_tor_paths = self.common.get_tor_paths
+ (
+ self.tor_path,
+ self.tor_geo_ip_file_path,
+ self.tor_geo_ipv6_file_path,
+ self.obfs4proxy_file_path,
+ self.meek_client_file_path,
+ ) = get_tor_paths()
+
+ meek_url = "https://moat.torproject.org.global.prod.fastly.net/"
+ meek_front = "cdn.sstatic.net"
+ meek_env = {
+ "TOR_PT_MANAGED_TRANSPORT_VER": "1",
+ "TOR_PT_CLIENT_TRANSPORTS": "meek",
+ }
+
+ # @TODO detect the port from the subprocess output
+ meek_address = "127.0.0.1"
+ meek_port = "43533" # hardcoded for testing
+ self.meek_proxies = {
+ "http": f"socks5h://{meek_address}:{meek_port}",
+ "https": f"socks5h://{meek_address}:{meek_port}",
+ }
+
+ # Start the Meek Client as a subprocess.
+ # This will be used to do domain fronting to the Tor
+ # Moat API endpoints for censorship circumvention as
+ # well as BridgeDB lookups.
+
+ if self.common.platform == "Windows":
+ # In Windows, hide console window when opening tor.exe subprocess
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+ self.meek_proc = subprocess.Popen(
+ [self.meek_client_file_path, "--url", meek_url, "--front", meek_front],
+ stdout=subprocess.PIPE,
+ startupinfo=startupinfo,
+ bufsize=1,
+ env=meek_env,
+ text=True,
+ )
+ else:
+ self.meek_proc = subprocess.Popen(
+ [self.meek_client_file_path, "--url", meek_url, "--front", meek_front],
+ stdout=subprocess.PIPE,
+ bufsize=1,
+ env=meek_env,
+ text=True,
+ )
+
+ # if "CMETHOD meek socks5" in line:
+ # self.meek_host = (line.split(" ")[3].split(":")[0])
+ # self.meek_port = (line.split(" ")[3].split(":")[1])
+ # self.common.log("CensorshipCircumvention", "__init__", f"Meek host is {self.meek_host}")
+ # self.common.log("CensorshipCircumvention", "__init__", f"Meek port is {self.meek_port}")
+
+ def censorship_obtain_map(self, country=False):
+ """
+ Retrieves the Circumvention map from Tor Project and store it
+ locally for further look-ups if required.
+
+ Optionally pass a country code in order to get recommended settings
+ just for that country.
+
+ Note that this API endpoint doesn't return actual bridges,
+ it just returns the recommended bridge type countries.
+ """
+ endpoint = "https://bridges.torproject.org/moat/circumvention/map"
+ data = {}
+ if country:
+ data = {"country": country}
+
+ r = requests.post(
+ endpoint,
+ json=data,
+ headers={"Content-Type": "application/vnd.api+json"},
+ proxies=self.meek_proxies,
+ )
+ if r.status_code != 200:
+ self.common.log(
+ "CensorshipCircumvention",
+ "censorship_obtain_map",
+ f"status_code={r.status_code}",
+ )
+ return False
+
+ result = r.json()
+
+ if "errors" in result:
+ self.common.log(
+ "CensorshipCircumvention",
+ "censorship_obtain_map",
+ f"errors={result['errors']}",
+ )
+ return False
+
+ return result
+
+ def censorship_obtain_settings(self, country=False, transports=False):
+ """
+ Retrieves the Circumvention Settings from Tor Project, which
+ will return recommended settings based on the country code of
+ the requesting IP.
+
+ Optionally, a country code can be specified in order to override
+ the IP detection.
+
+ Optionally, a list of transports can be specified in order to
+ return recommended settings for just that transport type.
+ """
+ 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.meek_proxies,
+ )
+ if r.status_code != 200:
+ self.common.log(
+ "CensorshipCircumvention",
+ "censorship_obtain_settings",
+ f"status_code={r.status_code}",
+ )
+ return False
+
+ result = r.json()
+
+ 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
+
+ return result
+
+ def censorship_obtain_builtin_bridges(self):
+ """
+ Retrieves the list of built-in bridges from the Tor Project.
+ """
+ endpoint = "https://bridges.torproject.org/moat/circumvention/builtin"
+ r = requests.post(
+ endpoint,
+ headers={"Content-Type": "application/vnd.api+json"},
+ proxies=self.meek_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
diff --git a/cli/onionshare_cli/common.py b/cli/onionshare_cli/common.py
index 195de2fe..549b1c21 100644
--- a/cli/onionshare_cli/common.py
+++ b/cli/onionshare_cli/common.py
@@ -314,6 +314,7 @@ class Common:
if not tor_path:
raise CannotFindTor()
obfs4proxy_file_path = shutil.which("obfs4proxy")
+ meek_client_file_path = shutil.which("meek-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")
@@ -321,6 +322,7 @@ class Common:
base_path = self.get_resource_path("tor")
tor_path = os.path.join(base_path, "Tor", "tor.exe")
obfs4proxy_file_path = os.path.join(base_path, "Tor", "obfs4proxy.exe")
+ meek_client_file_path = os.path.join(base_path, "Tor", "meek-client.exe")
tor_geo_ip_file_path = os.path.join(base_path, "Data", "Tor", "geoip")
tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6")
elif self.platform == "Darwin":
@@ -328,6 +330,7 @@ class Common:
if not tor_path:
raise CannotFindTor()
obfs4proxy_file_path = shutil.which("obfs4proxy")
+ meek_client_file_path = shutil.which("meek-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")
@@ -336,12 +339,14 @@ class Common:
tor_geo_ip_file_path = "/usr/local/share/tor/geoip"
tor_geo_ipv6_file_path = "/usr/local/share/tor/geoip6"
obfs4proxy_file_path = "/usr/local/bin/obfs4proxy"
+ meek_client_file_path = "/usr/local/bin/meek-client"
return (
tor_path,
tor_geo_ip_file_path,
tor_geo_ipv6_file_path,
obfs4proxy_file_path,
+ meek_client_file_path,
)
def build_data_dir(self):
@@ -505,74 +510,6 @@ class Common:
total_size += os.path.getsize(fp)
return total_size
- def censorship_obtain_map(self):
- """
- Retrieves the Circumvention map from Tor Project and store it
- locally for further look-ups if required.
- """
- endpoint = "https://bridges.torproject.org/moat/circumvention/map"
- # @TODO this needs to be using domain fronting to defeat censorship
- # of the lookup itself.
- response = requests.get(endpoint)
- self.censorship_map = response.json()
- self.log("Common", "censorship_obtain_map", self.censorship_map)
-
- def censorship_obtain_settings_from_api(self):
- """
- Retrieves the Circumvention Settings from Tor Project, which
- will return recommended settings based on the country code of
- the requesting IP.
- """
- endpoint = "https://bridges.torproject.org/moat/circumvention/settings"
- # @TODO this needs to be using domain fronting to defeat censorship
- # of the lookup itself.
- response = requests.get(endpoint)
- self.censorship_settings = response.json()
- self.log(
- "Common", "censorship_obtain_settings_from_api", self.censorship_settings
- )
-
- def censorship_obtain_settings_from_map(self, country):
- """
- Retrieves the Circumvention Settings for this country from the
- circumvention map we have stored locally, rather than from the
- API endpoint.
-
- This is for when the user has specified the country themselves
- rather than requesting auto-detection.
- """
- try:
- # Fetch the map.
- self.censorship_obtain_map()
- self.censorship_settings = self.censorship_map[country]
- self.log(
- "Common",
- "censorship_obtain_settings_from_map",
- f"Settings are {self.censorship_settings}",
- )
- except KeyError:
- self.log(
- "Common",
- "censorship_obtain_settings_from_map",
- "No censorship settings found for this country",
- )
- return False
-
- def censorship_obtain_builtin_bridges(self):
- """
- Retrieves the list of built-in bridges from the Tor Project.
- """
- endpoint = "https://bridges.torproject.org/moat/circumvention/builtin"
- # @TODO this needs to be using domain fronting to defeat censorship
- # of the lookup itself.
- response = requests.get(endpoint)
- self.censorship_builtin_bridges = response.json()
- self.log(
- "Common",
- "censorship_obtain_builtin_bridges",
- self.censorship_builtin_bridges,
- )
-
class AutoStopTimer(threading.Thread):
"""
diff --git a/cli/onionshare_cli/onion.py b/cli/onionshare_cli/onion.py
index 7f6faa17..aa5e276b 100644
--- a/cli/onionshare_cli/onion.py
+++ b/cli/onionshare_cli/onion.py
@@ -153,6 +153,7 @@ class Onion(object):
self.tor_geo_ip_file_path,
self.tor_geo_ipv6_file_path,
self.obfs4proxy_file_path,
+ self.meek_client_file_path,
) = get_tor_paths()
# The tor process