mirror of
https://github.com/onionshare/onionshare.git
synced 2025-01-11 23:39:31 -05:00
Move Censorship stuff into its own class. Early attempt at subprocessing out to meek (unfinished)
This commit is contained in:
parent
c9fa2308a7
commit
0989f2b133
@ -27,13 +27,9 @@ from datetime import datetime
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from .common import Common, CannotFindTor
|
from .common import Common, CannotFindTor
|
||||||
|
from .censorship import CensorshipCircumvention
|
||||||
from .web import Web
|
from .web import Web
|
||||||
from .onion import (
|
from .onion import TorErrorProtocolError, TorTooOldEphemeral, TorTooOldStealth, Onion
|
||||||
TorErrorProtocolError,
|
|
||||||
TorTooOldEphemeral,
|
|
||||||
TorTooOldStealth,
|
|
||||||
Onion,
|
|
||||||
)
|
|
||||||
from .onionshare import OnionShare
|
from .onionshare import OnionShare
|
||||||
from .mode_settings import ModeSettings
|
from .mode_settings import ModeSettings
|
||||||
|
|
||||||
@ -94,12 +90,7 @@ def main(cwd=None):
|
|||||||
help="Filename of persistent session",
|
help="Filename of persistent session",
|
||||||
)
|
)
|
||||||
# General args
|
# General args
|
||||||
parser.add_argument(
|
parser.add_argument("--title", metavar="TITLE", default=None, help="Set a title")
|
||||||
"--title",
|
|
||||||
metavar="TITLE",
|
|
||||||
default=None,
|
|
||||||
help="Set a title",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--public",
|
"--public",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@ -409,7 +400,7 @@ def main(cwd=None):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Warn about sending large files over Tor
|
# 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("")
|
||||||
print("Warning: Sending a large share could take hours")
|
print("Warning: Sending a large share could take hours")
|
||||||
print("")
|
print("")
|
||||||
|
216
cli/onionshare_cli/censorship.py
Normal file
216
cli/onionshare_cli/censorship.py
Normal file
@ -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
|
@ -314,6 +314,7 @@ class Common:
|
|||||||
if not tor_path:
|
if not tor_path:
|
||||||
raise CannotFindTor()
|
raise CannotFindTor()
|
||||||
obfs4proxy_file_path = shutil.which("obfs4proxy")
|
obfs4proxy_file_path = shutil.which("obfs4proxy")
|
||||||
|
meek_client_file_path = shutil.which("meek-client")
|
||||||
prefix = os.path.dirname(os.path.dirname(tor_path))
|
prefix = os.path.dirname(os.path.dirname(tor_path))
|
||||||
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
|
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
|
||||||
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
|
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")
|
base_path = self.get_resource_path("tor")
|
||||||
tor_path = os.path.join(base_path, "Tor", "tor.exe")
|
tor_path = os.path.join(base_path, "Tor", "tor.exe")
|
||||||
obfs4proxy_file_path = os.path.join(base_path, "Tor", "obfs4proxy.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_ip_file_path = os.path.join(base_path, "Data", "Tor", "geoip")
|
||||||
tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6")
|
tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6")
|
||||||
elif self.platform == "Darwin":
|
elif self.platform == "Darwin":
|
||||||
@ -328,6 +330,7 @@ class Common:
|
|||||||
if not tor_path:
|
if not tor_path:
|
||||||
raise CannotFindTor()
|
raise CannotFindTor()
|
||||||
obfs4proxy_file_path = shutil.which("obfs4proxy")
|
obfs4proxy_file_path = shutil.which("obfs4proxy")
|
||||||
|
meek_client_file_path = shutil.which("meek-client")
|
||||||
prefix = os.path.dirname(os.path.dirname(tor_path))
|
prefix = os.path.dirname(os.path.dirname(tor_path))
|
||||||
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
|
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
|
||||||
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
|
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_ip_file_path = "/usr/local/share/tor/geoip"
|
||||||
tor_geo_ipv6_file_path = "/usr/local/share/tor/geoip6"
|
tor_geo_ipv6_file_path = "/usr/local/share/tor/geoip6"
|
||||||
obfs4proxy_file_path = "/usr/local/bin/obfs4proxy"
|
obfs4proxy_file_path = "/usr/local/bin/obfs4proxy"
|
||||||
|
meek_client_file_path = "/usr/local/bin/meek-client"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
tor_path,
|
tor_path,
|
||||||
tor_geo_ip_file_path,
|
tor_geo_ip_file_path,
|
||||||
tor_geo_ipv6_file_path,
|
tor_geo_ipv6_file_path,
|
||||||
obfs4proxy_file_path,
|
obfs4proxy_file_path,
|
||||||
|
meek_client_file_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
def build_data_dir(self):
|
def build_data_dir(self):
|
||||||
@ -505,74 +510,6 @@ class Common:
|
|||||||
total_size += os.path.getsize(fp)
|
total_size += os.path.getsize(fp)
|
||||||
return total_size
|
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):
|
class AutoStopTimer(threading.Thread):
|
||||||
"""
|
"""
|
||||||
|
@ -153,6 +153,7 @@ class Onion(object):
|
|||||||
self.tor_geo_ip_file_path,
|
self.tor_geo_ip_file_path,
|
||||||
self.tor_geo_ipv6_file_path,
|
self.tor_geo_ipv6_file_path,
|
||||||
self.obfs4proxy_file_path,
|
self.obfs4proxy_file_path,
|
||||||
|
self.meek_client_file_path,
|
||||||
) = get_tor_paths()
|
) = get_tor_paths()
|
||||||
|
|
||||||
# The tor process
|
# The tor process
|
||||||
|
Loading…
Reference in New Issue
Block a user