Merge pull request #1444 from onionshare/1442_snowflake

Snowflake support, and updated Tor Settings
This commit is contained in:
Micah Lee 2021-10-20 08:50:34 -07:00 committed by GitHub
commit 7a45f801d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1854 additions and 1138 deletions

View File

@ -309,38 +309,69 @@ class Common:
def get_tor_paths(self): def get_tor_paths(self):
if self.platform == "Linux": if self.platform == "Linux":
tor_path = shutil.which("tor") # Look in resources first
if not tor_path: base_path = self.get_resource_path("tor")
raise CannotFindTor() if os.path.exists(base_path):
obfs4proxy_file_path = shutil.which("obfs4proxy") self.log(
prefix = os.path.dirname(os.path.dirname(tor_path)) "Common", "get_tor_paths", f"using tor binaries in {base_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") tor_path = os.path.join(base_path, "tor")
tor_geo_ip_file_path = os.path.join(base_path, "geoip")
tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6")
obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy")
snowflake_file_path = os.path.join(base_path, "snowflake-client")
else:
# Fallback to looking in the path
self.log(
"Common", "get_tor_paths", f"using tor binaries in system path"
)
tor_path = shutil.which("tor")
if not tor_path:
raise CannotFindTor()
obfs4proxy_file_path = shutil.which("obfs4proxy")
snowflake_file_path = shutil.which("snowflake-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")
elif self.platform == "Windows": elif self.platform == "Windows":
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")
snowflake_file_path = os.path.join(base_path, "Tor", "snowflake-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":
tor_path = shutil.which("tor") # Look in resources first
if not tor_path: base_path = self.get_resource_path("tor")
raise CannotFindTor() if os.path.exists(base_path):
obfs4proxy_file_path = shutil.which("obfs4proxy") tor_path = os.path.join(base_path, "tor")
prefix = os.path.dirname(os.path.dirname(tor_path)) tor_geo_ip_file_path = os.path.join(base_path, "geoip")
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip") tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6")
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6") obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy")
snowflake_file_path = os.path.join(base_path, "snowflake-client")
else:
# Fallback to looking in the path
tor_path = shutil.which("tor")
if not tor_path:
raise CannotFindTor()
obfs4proxy_file_path = shutil.which("obfs4proxy")
snowflake_file_path = shutil.which("snowflake-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")
elif self.platform == "BSD": elif self.platform == "BSD":
tor_path = "/usr/local/bin/tor" tor_path = "/usr/local/bin/tor"
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"
snowflake_file_path = "/usr/local/bin/snowflake-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,
snowflake_file_path,
) )
def build_data_dir(self): def build_data_dir(self):

View File

@ -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.snowflake_file_path,
) = get_tor_paths() ) = get_tor_paths()
# The tor process # The tor process
@ -178,10 +179,10 @@ class Onion(object):
key_bytes = bytes(key) key_bytes = bytes(key)
key_b32 = base64.b32encode(key_bytes) key_b32 = base64.b32encode(key_bytes)
# strip trailing ==== # strip trailing ====
assert key_b32[-4:] == b'====' assert key_b32[-4:] == b"===="
key_b32 = key_b32[:-4] key_b32 = key_b32[:-4]
# change from b'ASDF' to ASDF # change from b'ASDF' to ASDF
s = key_b32.decode('utf-8') s = key_b32.decode("utf-8")
return s return s
def connect( def connect(
@ -302,43 +303,51 @@ class Onion(object):
torrc_template = torrc_template.replace( torrc_template = torrc_template.replace(
"{{socks_port}}", str(self.tor_socks_port) "{{socks_port}}", str(self.tor_socks_port)
) )
torrc_template = torrc_template.replace(
"{{obfs4proxy_path}}", str(self.obfs4proxy_file_path)
)
torrc_template = torrc_template.replace(
"{{snowflake_path}}", str(self.snowflake_file_path)
)
with open(self.tor_torrc, "w") as f: with open(self.tor_torrc, "w") as f:
f.write(torrc_template) f.write(torrc_template)
# Bridge support # Bridge support
if self.settings.get("tor_bridges_use_obfs4"): if self.settings.get("tor_bridges_use_obfs4"):
f.write(
f"ClientTransportPlugin obfs4 exec {self.obfs4proxy_file_path}\n"
)
with open( with open(
self.common.get_resource_path("torrc_template-obfs4") self.common.get_resource_path("torrc_template-obfs4")
) as o: ) as o:
for line in o: for line in o:
f.write(line) f.write(line)
elif self.settings.get("tor_bridges_use_meek_lite_azure"): elif self.settings.get("tor_bridges_use_meek_lite_azure"):
f.write(
f"ClientTransportPlugin meek_lite exec {self.obfs4proxy_file_path}\n"
)
with open( with open(
self.common.get_resource_path("torrc_template-meek_lite_azure") self.common.get_resource_path("torrc_template-meek_lite_azure")
) as o: ) as o:
for line in o: for line in o:
f.write(line) f.write(line)
elif self.settings.get("tor_bridges_use_snowflake"):
with open(
self.common.get_resource_path("torrc_template-snowflake")
) as o:
for line in o:
f.write(line)
if self.settings.get("tor_bridges_use_custom_bridges"): elif self.settings.get("tor_bridges_use_moat"):
if "obfs4" in self.settings.get("tor_bridges_use_custom_bridges"): for line in self.settings.get("tor_bridges_use_moat_bridges").split(
f.write( "\n"
f"ClientTransportPlugin obfs4 exec {self.obfs4proxy_file_path}\n"
)
elif "meek_lite" in self.settings.get(
"tor_bridges_use_custom_bridges"
): ):
f.write( if line.strip() != "":
f"ClientTransportPlugin meek_lite exec {self.obfs4proxy_file_path}\n" f.write(f"Bridge {line}\n")
) f.write("\nUseBridges 1\n")
f.write(self.settings.get("tor_bridges_use_custom_bridges"))
f.write("\nUseBridges 1") elif self.settings.get("tor_bridges_use_custom_bridges"):
for line in self.settings.get(
"tor_bridges_use_custom_bridges"
).split("\n"):
if line.strip() != "":
f.write(f"Bridge {line}\n")
f.write("\nUseBridges 1\n")
# Execute a tor subprocess # Execute a tor subprocess
start_ts = time.time() start_ts = time.time()
@ -357,6 +366,7 @@ class Onion(object):
[self.tor_path, "-f", self.tor_torrc], [self.tor_path, "-f", self.tor_torrc],
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
env={"LD_LIBRARY_PATH": os.path.dirname(self.tor_path)},
) )
# Wait for the tor controller to start # Wait for the tor controller to start
@ -650,16 +660,24 @@ class Onion(object):
) )
raise TorTooOldStealth() raise TorTooOldStealth()
else: else:
if key_type == "NEW" or not mode_settings.get("onion", "client_auth_priv_key"): if key_type == "NEW" or not mode_settings.get(
"onion", "client_auth_priv_key"
):
# Generate a new key pair for Client Auth on new onions, or if # Generate a new key pair for Client Auth on new onions, or if
# it's a persistent onion but for some reason we don't them # it's a persistent onion but for some reason we don't them
client_auth_priv_key_raw = nacl.public.PrivateKey.generate() client_auth_priv_key_raw = nacl.public.PrivateKey.generate()
client_auth_priv_key = self.key_str(client_auth_priv_key_raw) client_auth_priv_key = self.key_str(client_auth_priv_key_raw)
client_auth_pub_key = self.key_str(client_auth_priv_key_raw.public_key) client_auth_pub_key = self.key_str(
client_auth_priv_key_raw.public_key
)
else: else:
# These should have been saved in settings from the previous run of a persistent onion # These should have been saved in settings from the previous run of a persistent onion
client_auth_priv_key = mode_settings.get("onion", "client_auth_priv_key") client_auth_priv_key = mode_settings.get(
client_auth_pub_key = mode_settings.get("onion", "client_auth_pub_key") "onion", "client_auth_priv_key"
)
client_auth_pub_key = mode_settings.get(
"onion", "client_auth_pub_key"
)
try: try:
if not self.supports_stealth: if not self.supports_stealth:

View File

@ -6,3 +6,7 @@ AvoidDiskWrites 1
Log notice stdout Log notice stdout
GeoIPFile {{geo_ip_file}} GeoIPFile {{geo_ip_file}}
GeoIPv6File {{geo_ipv6_file}} GeoIPv6File {{geo_ipv6_file}}
# Bridge configurations
ClientTransportPlugin meek_lite,obfs2,obfs3,obfs4,scramblesuit exec {{obfs4proxy_path}}
ClientTransportPlugin snowflake exec {{snowflake_path}} -url https://snowflake-broker.torproject.net.global.prod.fastly.net/ -front cdn.sstatic.net -ice stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478

View File

@ -1,2 +0,0 @@
Bridge meek_lite 0.0.2.0:2 B9E7141C594AF25699E0079C1F0146F409495296 url=https://d2cly7j4zqgua7.cloudfront.net/ front=a0.awsstatic.com
UseBridges 1

View File

@ -1,2 +1,3 @@
# Enable built-in meek-azure bridge
Bridge meek_lite 0.0.2.0:3 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com Bridge meek_lite 0.0.2.0:3 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
UseBridges 1 UseBridges 1

View File

@ -1,3 +1,4 @@
# Enable built-in obfs4-bridge
Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1 Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1 Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1 Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1

View File

@ -0,0 +1,3 @@
# Enable built-in snowflake bridge
Bridge snowflake 192.0.2.3:1 2B280B23E1107BB62ABFC40DDCC8824814F80A72
UseBridges 1

View File

@ -108,6 +108,9 @@ class Settings(object):
"no_bridges": True, "no_bridges": True,
"tor_bridges_use_obfs4": False, "tor_bridges_use_obfs4": False,
"tor_bridges_use_meek_lite_azure": False, "tor_bridges_use_meek_lite_azure": False,
"tor_bridges_use_snowflake": False,
"tor_bridges_use_moat": False,
"tor_bridges_use_moat_bridges": "",
"tor_bridges_use_custom_bridges": "", "tor_bridges_use_custom_bridges": "",
"persistent_tabs": [], "persistent_tabs": [],
"locale": None, # this gets defined in fill_in_defaults() "locale": None, # this gets defined in fill_in_defaults()

536
cli/poetry.lock generated
View File

@ -1,122 +1,120 @@
[[package]] [[package]]
category = "dev"
description = "Atomic file writes."
marker = "sys_platform == \"win32\""
name = "atomicwrites" name = "atomicwrites"
version = "1.4.0"
description = "Atomic file writes."
category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.4.0"
[[package]] [[package]]
category = "dev"
description = "Classes Without Boilerplate"
name = "attrs" name = "attrs"
version = "21.2.0"
description = "Classes Without Boilerplate"
category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "21.2.0"
[package.extras] [package.extras]
dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
[[package]] [[package]]
category = "main"
description = "The bidirectional mapping library for Python."
name = "bidict" name = "bidict"
version = "0.21.3"
description = "The bidirectional mapping library for Python."
category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
version = "0.21.2"
[package.extras]
coverage = ["coverage (<6)", "pytest-cov (<3)"]
dev = ["setuptools-scm", "hypothesis (<6)", "py (<2)", "pytest (<7)", "pytest-benchmark (>=3.2.0,<4)", "sortedcollections (<2)", "sortedcontainers (<3)", "Sphinx (<4)", "sphinx-autodoc-typehints (<2)", "coverage (<6)", "pytest-cov (<3)", "pre-commit (<3)", "tox (<4)"]
docs = ["Sphinx (<4)", "sphinx-autodoc-typehints (<2)"]
precommit = ["pre-commit (<3)"]
test = ["hypothesis (<6)", "py (<2)", "pytest (<7)", "pytest-benchmark (>=3.2.0,<4)", "sortedcollections (<2)", "sortedcontainers (<3)", "Sphinx (<4)", "sphinx-autodoc-typehints (<2)"]
[[package]] [[package]]
category = "main"
description = "Python package for providing Mozilla's CA Bundle."
name = "certifi" name = "certifi"
version = "2021.10.8"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "2021.5.30"
[[package]] [[package]]
category = "main"
description = "Foreign Function Interface for Python calling C code."
name = "cffi" name = "cffi"
version = "1.14.6"
description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.14.6"
[package.dependencies] [package.dependencies]
pycparser = "*" pycparser = "*"
[[package]] [[package]]
name = "charset-normalizer"
version = "2.0.7"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main" category = "main"
description = "Universal encoding detector for Python 2 and 3"
name = "chardet"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=3.5.0"
version = "4.0.0"
[[package]]
category = "main"
description = "Composable command line interface toolkit"
name = "click"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "7.1.2"
[[package]]
category = "main"
description = "Cross-platform colored terminal text."
name = "colorama"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.4.4"
[[package]]
category = "main"
description = "DNS toolkit"
name = "dnspython"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.16.0"
[package.extras] [package.extras]
DNSSEC = ["pycryptodome", "ecdsa (>=0.13)"] unicode_backport = ["unicodedata2"]
IDNA = ["idna (>=2.1)"]
[[package]] [[package]]
name = "click"
version = "7.1.2"
description = "Composable command line interface toolkit"
category = "main" category = "main"
description = "Highly concurrent networking library" optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "colorama"
version = "0.4.4"
description = "Cross-platform colored terminal text."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "dnspython"
version = "2.1.0"
description = "DNS toolkit"
category = "main"
optional = false
python-versions = ">=3.6"
[package.extras]
dnssec = ["cryptography (>=2.6)"]
doh = ["requests", "requests-toolbelt"]
idna = ["idna (>=2.1)"]
curio = ["curio (>=1.2)", "sniffio (>=1.1)"]
trio = ["trio (>=0.14.0)", "sniffio (>=1.1)"]
[[package]]
name = "eventlet" name = "eventlet"
version = "0.32.0"
description = "Highly concurrent networking library"
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.31.0"
[package.dependencies] [package.dependencies]
dnspython = ">=1.15.0,<2.0.0" dnspython = ">=1.15.0"
greenlet = ">=0.3" greenlet = ">=0.3"
six = ">=1.10.0" six = ">=1.10.0"
[[package]] [[package]]
category = "main"
description = "A simple framework for building complex web applications."
name = "flask" name = "flask"
version = "1.1.4"
description = "A simple framework for building complex web applications."
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "1.1.4"
[package.dependencies] [package.dependencies]
Jinja2 = ">=2.10.1,<3.0"
Werkzeug = ">=0.15,<2.0"
click = ">=5.1,<8.0" click = ">=5.1,<8.0"
itsdangerous = ">=0.24,<2.0" itsdangerous = ">=0.24,<2.0"
Jinja2 = ">=2.10.1,<3.0"
Werkzeug = ">=0.15,<2.0"
[package.extras] [package.extras]
dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
@ -124,79 +122,76 @@ docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-
dotenv = ["python-dotenv"] dotenv = ["python-dotenv"]
[[package]] [[package]]
category = "main"
description = "Socket.IO integration for Flask applications"
name = "flask-socketio" name = "flask-socketio"
version = "5.0.1"
description = "Socket.IO integration for Flask applications"
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "5.0.1"
[package.dependencies] [package.dependencies]
Flask = ">=0.9" Flask = ">=0.9"
python-socketio = ">=5.0.2" python-socketio = ">=5.0.2"
[[package]] [[package]]
category = "main"
description = "Lightweight in-process concurrent programming"
name = "greenlet" name = "greenlet"
version = "1.1.2"
description = "Lightweight in-process concurrent programming"
category = "main"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
version = "1.1.0"
[package.extras] [package.extras]
docs = ["sphinx"] docs = ["sphinx"]
[[package]] [[package]]
category = "main"
description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna" name = "idna"
version = "3.2"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=3.5"
version = "2.10"
[[package]] [[package]]
category = "dev"
description = "Read metadata from Python packages"
marker = "python_version < \"3.8\""
name = "importlib-metadata" name = "importlib-metadata"
version = "4.8.1"
description = "Read metadata from Python packages"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
version = "4.4.0"
[package.dependencies] [package.dependencies]
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
zipp = ">=0.5" zipp = ">=0.5"
[package.dependencies.typing-extensions]
python = "<3.8"
version = ">=3.6.4"
[package.extras] [package.extras]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] perf = ["ipython"]
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
[[package]] [[package]]
category = "dev"
description = "iniconfig: brain-dead simple config-ini parsing"
name = "iniconfig" name = "iniconfig"
version = "1.1.1"
description = "iniconfig: brain-dead simple config-ini parsing"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.1.1"
[[package]] [[package]]
category = "main"
description = "Various helpers to pass data to untrusted environments and back."
name = "itsdangerous" name = "itsdangerous"
version = "1.1.0"
description = "Various helpers to pass data to untrusted environments and back."
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.1.0"
[[package]] [[package]]
category = "main"
description = "A very fast and expressive template engine."
name = "jinja2" name = "jinja2"
version = "2.11.3"
description = "A very fast and expressive template engine."
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.11.3"
[package.dependencies] [package.dependencies]
MarkupSafe = ">=0.23" MarkupSafe = ">=0.23"
@ -205,74 +200,73 @@ MarkupSafe = ">=0.23"
i18n = ["Babel (>=0.8)"] i18n = ["Babel (>=0.8)"]
[[package]] [[package]]
category = "main"
description = "Safely add untrusted strings to HTML/XML markup."
name = "markupsafe" name = "markupsafe"
version = "2.0.1"
description = "Safely add untrusted strings to HTML/XML markup."
category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
version = "2.0.1"
[[package]] [[package]]
category = "dev"
description = "Core utilities for Python packages"
name = "packaging" name = "packaging"
version = "21.0"
description = "Core utilities for Python packages"
category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=3.6"
version = "20.9"
[package.dependencies] [package.dependencies]
pyparsing = ">=2.0.2" pyparsing = ">=2.0.2"
[[package]] [[package]]
category = "dev"
description = "plugin and hook calling mechanisms for python"
name = "pluggy" name = "pluggy"
version = "1.0.0"
description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=3.6"
version = "0.13.1"
[package.dependencies] [package.dependencies]
[package.dependencies.importlib-metadata] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
python = "<3.8"
version = ">=0.12"
[package.extras] [package.extras]
dev = ["pre-commit", "tox"] dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]] [[package]]
category = "main"
description = "Cross-platform lib for process and system monitoring in Python."
name = "psutil" name = "psutil"
version = "5.8.0"
description = "Cross-platform lib for process and system monitoring in Python."
category = "main"
optional = false optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "5.8.0"
[package.extras] [package.extras]
test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"] test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"]
[[package]] [[package]]
category = "dev"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
name = "py" name = "py"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.10.0" version = "1.10.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]] [[package]]
category = "main"
description = "C parser in Python"
name = "pycparser" name = "pycparser"
version = "2.20"
description = "C parser in Python"
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.20"
[[package]] [[package]]
category = "main"
description = "Python binding to the Networking and Cryptography (NaCl) library"
name = "pynacl" name = "pynacl"
version = "1.4.0"
description = "Python binding to the Networking and Cryptography (NaCl) library"
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.4.0"
[package.dependencies] [package.dependencies]
cffi = ">=1.4.1" cffi = ">=1.4.1"
@ -280,186 +274,181 @@ six = "*"
[package.extras] [package.extras]
docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"]
tests = ["pytest (>=3.2.1,<3.3.0 || >3.3.0)", "hypothesis (>=3.27.0)"] tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"]
[[package]] [[package]]
category = "dev"
description = "Python parsing module"
name = "pyparsing" name = "pyparsing"
version = "2.4.7"
description = "Python parsing module"
category = "dev"
optional = false optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "2.4.7"
[[package]] [[package]]
category = "main"
description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information."
name = "pysocks" name = "pysocks"
version = "1.7.1"
description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information."
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.7.1"
[[package]] [[package]]
category = "dev"
description = "pytest: simple powerful testing with Python"
name = "pytest" name = "pytest"
version = "6.2.5"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
version = "6.2.4"
[package.dependencies] [package.dependencies]
atomicwrites = ">=1.0" atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
attrs = ">=19.2.0" attrs = ">=19.2.0"
colorama = "*" colorama = {version = "*", markers = "sys_platform == \"win32\""}
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
iniconfig = "*" iniconfig = "*"
packaging = "*" packaging = "*"
pluggy = ">=0.12,<1.0.0a1" pluggy = ">=0.12,<2.0"
py = ">=1.8.2" py = ">=1.8.2"
toml = "*" toml = "*"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12"
[package.extras] [package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
[[package]] [[package]]
category = "main"
description = "Engine.IO server"
name = "python-engineio" name = "python-engineio"
version = "4.2.1"
description = "Engine.IO server and client for Python"
category = "main"
optional = false optional = false
python-versions = "*" python-versions = ">=3.6"
version = "4.2.0"
[package.extras] [package.extras]
asyncio_client = ["aiohttp (>=3.4)"] asyncio_client = ["aiohttp (>=3.4)"]
client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
[[package]] [[package]]
category = "main"
description = "Socket.IO server"
name = "python-socketio" name = "python-socketio"
version = "5.4.0"
description = "Socket.IO server and client for Python"
category = "main"
optional = false optional = false
python-versions = "*" python-versions = ">=3.6"
version = "5.3.0"
[package.dependencies] [package.dependencies]
bidict = ">=0.21.0" bidict = ">=0.21.0"
python-engineio = ">=4.1.0" python-engineio = ">=4.1.0"
[package.extras] [package.extras]
asyncio_client = ["aiohttp (>=3.4)", "websockets (>=7.0)"] asyncio_client = ["aiohttp (>=3.4)"]
client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
[[package]] [[package]]
category = "main"
description = "Python HTTP for Humans."
name = "requests" name = "requests"
version = "2.26.0"
description = "Python HTTP for Humans."
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
version = "2.25.1"
[package.dependencies] [package.dependencies]
certifi = ">=2017.4.17" certifi = ">=2017.4.17"
chardet = ">=3.0.2,<5" charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
idna = ">=2.5,<3" idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
PySocks = {version = ">=1.5.6,<1.5.7 || >1.5.7", optional = true, markers = "extra == \"socks\""}
urllib3 = ">=1.21.1,<1.27" urllib3 = ">=1.21.1,<1.27"
[package.dependencies.PySocks]
optional = true
version = ">=1.5.6,<1.5.7 || >1.5.7"
[package.extras] [package.extras]
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
[[package]] [[package]]
category = "main"
description = "Python 2 and 3 compatibility utilities"
name = "six" name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
version = "1.16.0"
[[package]] [[package]]
category = "main"
description = ""
name = "stem" name = "stem"
version = "1.8.1"
description = "Stem is a Python controller library that allows applications to interact with Tor (https://www.torproject.org/)."
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.8.1" develop = false
[package.source] [package.source]
reference = "de3d03a03c7ee57c74c80e9c63cb88072d833717"
type = "git" type = "git"
url = "https://github.com/onionshare/stem.git" url = "https://github.com/onionshare/stem.git"
reference = "1.8.1"
resolved_reference = "de3d03a03c7ee57c74c80e9c63cb88072d833717"
[[package]] [[package]]
category = "dev"
description = "Python Library for Tom's Obvious, Minimal Language"
name = "toml" name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
category = "dev"
optional = false optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "0.10.2"
[[package]] [[package]]
category = "dev"
description = "Backported and Experimental Type Hints for Python 3.5+"
marker = "python_version < \"3.8\""
name = "typing-extensions" name = "typing-extensions"
version = "3.10.0.2"
description = "Backported and Experimental Type Hints for Python 3.5+"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "3.10.0.0"
[[package]] [[package]]
category = "main"
description = "ASCII transliterations of Unicode text"
name = "unidecode" name = "unidecode"
version = "1.3.2"
description = "ASCII transliterations of Unicode text"
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=3.5"
version = "1.2.0"
[[package]] [[package]]
category = "main"
description = "HTTP library with thread-safe connection pooling, file post, and more."
name = "urllib3" name = "urllib3"
version = "1.26.7"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
version = "1.26.5"
[package.extras] [package.extras]
brotli = ["brotlipy (>=0.6.0)"] brotli = ["brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]] [[package]]
category = "main"
description = "The comprehensive WSGI web application library."
name = "werkzeug" name = "werkzeug"
version = "1.0.1"
description = "The comprehensive WSGI web application library."
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "1.0.1"
[package.extras] [package.extras]
dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"]
watchdog = ["watchdog"] watchdog = ["watchdog"]
[[package]] [[package]]
category = "dev"
description = "Backport of pathlib-compatible object wrapper for zip files"
marker = "python_version < \"3.8\""
name = "zipp" name = "zipp"
version = "3.6.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
version = "3.4.1"
[package.extras] [package.extras]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
[metadata] [metadata]
content-hash = "181891640e59dac730905019444d42ef8e99da0c34c96fb8a616781661bae537" lock-version = "1.1"
python-versions = "^3.6" python-versions = "^3.6"
content-hash = "181891640e59dac730905019444d42ef8e99da0c34c96fb8a616781661bae537"
[metadata.files] [metadata.files]
atomicwrites = [ atomicwrites = [
@ -471,12 +460,12 @@ attrs = [
{file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
] ]
bidict = [ bidict = [
{file = "bidict-0.21.2-py2.py3-none-any.whl", hash = "sha256:929d056e8d0d9b17ceda20ba5b24ac388e2a4d39802b87f9f4d3f45ecba070bf"}, {file = "bidict-0.21.3-py3-none-any.whl", hash = "sha256:2cce0d01eb3db9b3fa85db501c00aaa3389ee4cab7ef82178604552dfa943a1b"},
{file = "bidict-0.21.2.tar.gz", hash = "sha256:4fa46f7ff96dc244abfc437383d987404ae861df797e2fd5b190e233c302be09"}, {file = "bidict-0.21.3.tar.gz", hash = "sha256:d50bd81fae75e34198ffc94979a0eb0939ff9adb3ef32bcc93a913d8b3e3ed1d"},
] ]
certifi = [ certifi = [
{file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
{file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
] ]
cffi = [ cffi = [
{file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"}, {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"},
@ -525,9 +514,9 @@ cffi = [
{file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"}, {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"},
{file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"}, {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"},
] ]
chardet = [ charset-normalizer = [
{file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"},
{file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"},
] ]
click = [ click = [
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
@ -538,12 +527,12 @@ colorama = [
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
] ]
dnspython = [ dnspython = [
{file = "dnspython-1.16.0-py2.py3-none-any.whl", hash = "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d"}, {file = "dnspython-2.1.0-py3-none-any.whl", hash = "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216"},
{file = "dnspython-1.16.0.zip", hash = "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01"}, {file = "dnspython-2.1.0.zip", hash = "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4"},
] ]
eventlet = [ eventlet = [
{file = "eventlet-0.31.0-py2.py3-none-any.whl", hash = "sha256:27ae41fad9deed9bbf4166f3e3b65acc15d524d42210a518e5877da85a6b8c5d"}, {file = "eventlet-0.32.0-py2.py3-none-any.whl", hash = "sha256:a3a67b02f336e97a1894b277bc33b695831525758781eb024f4da00e75ce5e25"},
{file = "eventlet-0.31.0.tar.gz", hash = "sha256:b36ec2ecc003de87fc87b93197d77fea528aa0f9204a34fdf3b2f8d0f01e017b"}, {file = "eventlet-0.32.0.tar.gz", hash = "sha256:2f0bb8ed0dc0ab21d683975d5d8ab3c054d588ce61def9faf7a465ee363e839b"},
] ]
flask = [ flask = [
{file = "Flask-1.1.4-py2.py3-none-any.whl", hash = "sha256:c34f04500f2cbbea882b1acb02002ad6fe6b7ffa64a6164577995657f50aed22"}, {file = "Flask-1.1.4-py2.py3-none-any.whl", hash = "sha256:c34f04500f2cbbea882b1acb02002ad6fe6b7ffa64a6164577995657f50aed22"},
@ -554,63 +543,64 @@ flask-socketio = [
{file = "Flask_SocketIO-5.0.1-py2.py3-none-any.whl", hash = "sha256:5d9a4438bafd806c5a3b832e74b69758781a8ee26fb6c9b1dbdda9b4fced432e"}, {file = "Flask_SocketIO-5.0.1-py2.py3-none-any.whl", hash = "sha256:5d9a4438bafd806c5a3b832e74b69758781a8ee26fb6c9b1dbdda9b4fced432e"},
] ]
greenlet = [ greenlet = [
{file = "greenlet-1.1.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:60848099b76467ef09b62b0f4512e7e6f0a2c977357a036de602b653667f5f4c"}, {file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"},
{file = "greenlet-1.1.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f42ad188466d946f1b3afc0a9e1a266ac8926461ee0786c06baac6bd71f8a6f3"}, {file = "greenlet-1.1.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a"},
{file = "greenlet-1.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:76ed710b4e953fc31c663b079d317c18f40235ba2e3d55f70ff80794f7b57922"}, {file = "greenlet-1.1.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d"},
{file = "greenlet-1.1.0-cp27-cp27m-win32.whl", hash = "sha256:b33b51ab057f8a20b497ffafdb1e79256db0c03ef4f5e3d52e7497200e11f821"}, {file = "greenlet-1.1.2-cp27-cp27m-win32.whl", hash = "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713"},
{file = "greenlet-1.1.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed1377feed808c9c1139bdb6a61bcbf030c236dd288d6fca71ac26906ab03ba6"}, {file = "greenlet-1.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40"},
{file = "greenlet-1.1.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:da862b8f7de577bc421323714f63276acb2f759ab8c5e33335509f0b89e06b8f"}, {file = "greenlet-1.1.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d"},
{file = "greenlet-1.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5f75e7f237428755d00e7460239a2482fa7e3970db56c8935bd60da3f0733e56"}, {file = "greenlet-1.1.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8"},
{file = "greenlet-1.1.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:258f9612aba0d06785143ee1cbf2d7361801c95489c0bd10c69d163ec5254a16"}, {file = "greenlet-1.1.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d"},
{file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d928e2e3c3906e0a29b43dc26d9b3d6e36921eee276786c4e7ad9ff5665c78a"}, {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497"},
{file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cc407b68e0a874e7ece60f6639df46309376882152345508be94da608cc0b831"}, {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1"},
{file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c557c809eeee215b87e8a7cbfb2d783fb5598a78342c29ade561440abae7d22"}, {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58"},
{file = "greenlet-1.1.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:3d13da093d44dee7535b91049e44dd2b5540c2a0e15df168404d3dd2626e0ec5"}, {file = "greenlet-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708"},
{file = "greenlet-1.1.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b3090631fecdf7e983d183d0fad7ea72cfb12fa9212461a9b708ff7907ffff47"}, {file = "greenlet-1.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23"},
{file = "greenlet-1.1.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:06ecb43b04480e6bafc45cb1b4b67c785e183ce12c079473359e04a709333b08"}, {file = "greenlet-1.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee"},
{file = "greenlet-1.1.0-cp35-cp35m-win32.whl", hash = "sha256:944fbdd540712d5377a8795c840a97ff71e7f3221d3fddc98769a15a87b36131"}, {file = "greenlet-1.1.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c"},
{file = "greenlet-1.1.0-cp35-cp35m-win_amd64.whl", hash = "sha256:c767458511a59f6f597bfb0032a1c82a52c29ae228c2c0a6865cfeaeaac4c5f5"}, {file = "greenlet-1.1.2-cp35-cp35m-win32.whl", hash = "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963"},
{file = "greenlet-1.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2325123ff3a8ecc10ca76f062445efef13b6cf5a23389e2df3c02a4a527b89bc"}, {file = "greenlet-1.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e"},
{file = "greenlet-1.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:598bcfd841e0b1d88e32e6a5ea48348a2c726461b05ff057c1b8692be9443c6e"}, {file = "greenlet-1.1.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073"},
{file = "greenlet-1.1.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:be9768e56f92d1d7cd94185bab5856f3c5589a50d221c166cc2ad5eb134bd1dc"}, {file = "greenlet-1.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c"},
{file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe7eac0d253915116ed0cd160a15a88981a1d194c1ef151e862a5c7d2f853d3"}, {file = "greenlet-1.1.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e"},
{file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a6b035aa2c5fcf3dbbf0e3a8a5bc75286fc2d4e6f9cfa738788b433ec894919"}, {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce"},
{file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca1c4a569232c063615f9e70ff9a1e2fee8c66a6fb5caf0f5e8b21a396deec3e"}, {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08"},
{file = "greenlet-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:3096286a6072553b5dbd5efbefc22297e9d06a05ac14ba017233fedaed7584a8"}, {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168"},
{file = "greenlet-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c35872b2916ab5a240d52a94314c963476c989814ba9b519bc842e5b61b464bb"}, {file = "greenlet-1.1.2-cp36-cp36m-win32.whl", hash = "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa"},
{file = "greenlet-1.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b97c9a144bbeec7039cca44df117efcbeed7209543f5695201cacf05ba3b5857"}, {file = "greenlet-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d"},
{file = "greenlet-1.1.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:16183fa53bc1a037c38d75fdc59d6208181fa28024a12a7f64bb0884434c91ea"}, {file = "greenlet-1.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4"},
{file = "greenlet-1.1.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6b1d08f2e7f2048d77343279c4d4faa7aef168b3e36039cba1917fffb781a8ed"}, {file = "greenlet-1.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b"},
{file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14927b15c953f8f2d2a8dffa224aa78d7759ef95284d4c39e1745cf36e8cdd2c"}, {file = "greenlet-1.1.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c"},
{file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bdcff4b9051fb1aa4bba4fceff6a5f770c6be436408efd99b76fc827f2a9319"}, {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1"},
{file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70c7dd733a4c56838d1f1781e769081a25fade879510c5b5f0df76956abfa05"}, {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28"},
{file = "greenlet-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:0de64d419b1cb1bfd4ea544bedea4b535ef3ae1e150b0f2609da14bbf48a4a5f"}, {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5"},
{file = "greenlet-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8833e27949ea32d27f7e96930fa29404dd4f2feb13cce483daf52e8842ec246a"}, {file = "greenlet-1.1.2-cp37-cp37m-win32.whl", hash = "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc"},
{file = "greenlet-1.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c1580087ab493c6b43e66f2bdd165d9e3c1e86ef83f6c2c44a29f2869d2c5bd5"}, {file = "greenlet-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06"},
{file = "greenlet-1.1.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ad80bb338cf9f8129c049837a42a43451fc7c8b57ad56f8e6d32e7697b115505"}, {file = "greenlet-1.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0"},
{file = "greenlet-1.1.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:a9017ff5fc2522e45562882ff481128631bf35da444775bc2776ac5c61d8bcae"}, {file = "greenlet-1.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627"},
{file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7920e3eccd26b7f4c661b746002f5ec5f0928076bd738d38d894bb359ce51927"}, {file = "greenlet-1.1.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478"},
{file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:408071b64e52192869129a205e5b463abda36eff0cebb19d6e63369440e4dc99"}, {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43"},
{file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be13a18cec649ebaab835dff269e914679ef329204704869f2f167b2c163a9da"}, {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711"},
{file = "greenlet-1.1.0-cp38-cp38-win32.whl", hash = "sha256:22002259e5b7828b05600a762579fa2f8b33373ad95a0ee57b4d6109d0e589ad"}, {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b"},
{file = "greenlet-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:206295d270f702bc27dbdbd7651e8ebe42d319139e0d90217b2074309a200da8"}, {file = "greenlet-1.1.2-cp38-cp38-win32.whl", hash = "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd"},
{file = "greenlet-1.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:096cb0217d1505826ba3d723e8981096f2622cde1eb91af9ed89a17c10aa1f3e"}, {file = "greenlet-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3"},
{file = "greenlet-1.1.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:03f28a5ea20201e70ab70518d151116ce939b412961c33827519ce620957d44c"}, {file = "greenlet-1.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67"},
{file = "greenlet-1.1.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:7db68f15486d412b8e2cfcd584bf3b3a000911d25779d081cbbae76d71bd1a7e"}, {file = "greenlet-1.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab"},
{file = "greenlet-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70bd1bb271e9429e2793902dfd194b653221904a07cbf207c3139e2672d17959"}, {file = "greenlet-1.1.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5"},
{file = "greenlet-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f92731609d6625e1cc26ff5757db4d32b6b810d2a3363b0ff94ff573e5901f6f"}, {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88"},
{file = "greenlet-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06d7ac89e6094a0a8f8dc46aa61898e9e1aec79b0f8b47b2400dd51a44dbc832"}, {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b"},
{file = "greenlet-1.1.0-cp39-cp39-win32.whl", hash = "sha256:adb94a28225005890d4cf73648b5131e885c7b4b17bc762779f061844aabcc11"}, {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3"},
{file = "greenlet-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa4230234d02e6f32f189fd40b59d5a968fe77e80f59c9c933384fe8ba535535"}, {file = "greenlet-1.1.2-cp39-cp39-win32.whl", hash = "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf"},
{file = "greenlet-1.1.0.tar.gz", hash = "sha256:c87df8ae3f01ffb4483c796fe1b15232ce2b219f0b18126948616224d3f658ee"}, {file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"},
{file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"},
] ]
idna = [ idna = [
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"},
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"},
] ]
importlib-metadata = [ importlib-metadata = [
{file = "importlib_metadata-4.4.0-py3-none-any.whl", hash = "sha256:960d52ba7c21377c990412aca380bf3642d734c2eaab78a2c39319f67c6a5786"}, {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"},
{file = "importlib_metadata-4.4.0.tar.gz", hash = "sha256:e592faad8de1bda9fe920cf41e15261e7131bcf266c30306eec00e8e225c1dd5"}, {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"},
] ]
iniconfig = [ iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
@ -681,12 +671,12 @@ markupsafe = [
{file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
] ]
packaging = [ packaging = [
{file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"},
{file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
] ]
pluggy = [ pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
] ]
psutil = [ psutil = [
{file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"}, {file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"},
@ -756,20 +746,20 @@ pysocks = [
{file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"},
] ]
pytest = [ pytest = [
{file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
{file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"},
] ]
python-engineio = [ python-engineio = [
{file = "python-engineio-4.2.0.tar.gz", hash = "sha256:4e97c1189c23923858f5bb6dc47cfcd915005383c3c039ff01c89f2c00d62077"}, {file = "python-engineio-4.2.1.tar.gz", hash = "sha256:d510329b6d8ed5662547862f58bc73659ae62defa66b66d745ba021de112fa62"},
{file = "python_engineio-4.2.0-py2.py3-none-any.whl", hash = "sha256:c6c119c2039fcb6f64d260211ca92c0c61b2b888a28678732a961f2aaebcc848"}, {file = "python_engineio-4.2.1-py3-none-any.whl", hash = "sha256:f3ef9a2c048d08990f294c5f8991f6f162c3b12ecbd368baa0d90441de907d1c"},
] ]
python-socketio = [ python-socketio = [
{file = "python-socketio-5.3.0.tar.gz", hash = "sha256:3dcc9785aaeef3a9eeb36c3818095662342744bdcdabd050fe697cdb826a1c2b"}, {file = "python-socketio-5.4.0.tar.gz", hash = "sha256:ca807c9e1f168e96dea412d64dd834fb47c470d27fd83da0504aa4b248ba2544"},
{file = "python_socketio-5.3.0-py2.py3-none-any.whl", hash = "sha256:d74314fd4241342c8a55c4f66d5cfea8f1a8fffd157af216c67e1c3a649a2444"}, {file = "python_socketio-5.4.0-py3-none-any.whl", hash = "sha256:7ed57f6c024abdfeb9b25c74c0c00ffc18da47d903e8d72deecb87584370d1fc"},
] ]
requests = [ requests = [
{file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
{file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
] ]
six = [ six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
@ -781,23 +771,23 @@ toml = [
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
] ]
typing-extensions = [ typing-extensions = [
{file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
{file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
{file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
] ]
unidecode = [ unidecode = [
{file = "Unidecode-1.2.0-py2.py3-none-any.whl", hash = "sha256:12435ef2fc4cdfd9cf1035a1db7e98b6b047fe591892e81f34e94959591fad00"}, {file = "Unidecode-1.3.2-py3-none-any.whl", hash = "sha256:215fe33c9d1c889fa823ccb66df91b02524eb8cc8c9c80f9c5b8129754d27829"},
{file = "Unidecode-1.2.0.tar.gz", hash = "sha256:8d73a97d387a956922344f6b74243c2c6771594659778744b2dbdaad8f6b727d"}, {file = "Unidecode-1.3.2.tar.gz", hash = "sha256:669898c1528912bcf07f9819dc60df18d057f7528271e31f8ec28cc88ef27504"},
] ]
urllib3 = [ urllib3 = [
{file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"}, {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"},
{file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"}, {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"},
] ]
werkzeug = [ werkzeug = [
{file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"},
{file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, {file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"},
] ]
zipp = [ zipp = [
{file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"},
{file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"},
] ]

View File

@ -162,11 +162,15 @@ class TestGetTorPaths:
tor_geo_ip_file_path = os.path.join(base_path, "Resources", "Tor", "geoip") tor_geo_ip_file_path = os.path.join(base_path, "Resources", "Tor", "geoip")
tor_geo_ipv6_file_path = os.path.join(base_path, "Resources", "Tor", "geoip6") tor_geo_ipv6_file_path = os.path.join(base_path, "Resources", "Tor", "geoip6")
obfs4proxy_file_path = os.path.join(base_path, "Resources", "Tor", "obfs4proxy") obfs4proxy_file_path = os.path.join(base_path, "Resources", "Tor", "obfs4proxy")
snowflake_file_path = os.path.join(
base_path, "Resources", "Tor", "snowflake-client"
)
assert common_obj.get_tor_paths() == ( assert common_obj.get_tor_paths() == (
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,
snowflake_file_path,
) )
@pytest.mark.skipif(sys.platform != "linux", reason="requires Linux") @pytest.mark.skipif(sys.platform != "linux", reason="requires Linux")
@ -176,6 +180,7 @@ class TestGetTorPaths:
tor_geo_ip_file_path, tor_geo_ip_file_path,
tor_geo_ipv6_file_path, tor_geo_ipv6_file_path,
_, # obfs4proxy is optional _, # obfs4proxy is optional
_, # snowflake-client is optional
) = common_obj.get_tor_paths() ) = common_obj.get_tor_paths()
assert os.path.basename(tor_path) == "tor" assert os.path.basename(tor_path) == "tor"
@ -199,6 +204,9 @@ class TestGetTorPaths:
obfs4proxy_file_path = os.path.join( obfs4proxy_file_path = os.path.join(
os.path.join(base_path, "Tor"), "obfs4proxy.exe" os.path.join(base_path, "Tor"), "obfs4proxy.exe"
) )
snowflake_file_path = os.path.join(
os.path.join(base_path, "Tor"), "snowflake-client.exe"
)
tor_geo_ip_file_path = os.path.join( tor_geo_ip_file_path = os.path.join(
os.path.join(os.path.join(base_path, "Data"), "Tor"), "geoip" os.path.join(os.path.join(base_path, "Data"), "Tor"), "geoip"
) )
@ -210,6 +218,7 @@ class TestGetTorPaths:
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,
snowflake_file_path,
) )

View File

@ -32,9 +32,12 @@ class TestSettings:
"no_bridges": True, "no_bridges": True,
"tor_bridges_use_obfs4": False, "tor_bridges_use_obfs4": False,
"tor_bridges_use_meek_lite_azure": False, "tor_bridges_use_meek_lite_azure": False,
"tor_bridges_use_snowflake": False,
"tor_bridges_use_moat": False,
"tor_bridges_use_moat_bridges": "",
"tor_bridges_use_custom_bridges": "", "tor_bridges_use_custom_bridges": "",
"persistent_tabs": [], "persistent_tabs": [],
"theme":0 "theme": 0,
} }
for key in settings_obj._settings: for key in settings_obj._settings:
# Skip locale, it will not always default to the same thing # Skip locale, it will not always default to the same thing

View File

@ -13,9 +13,19 @@ cd onionshare/desktop
#### Linux #### Linux
If you're using Linux, install `tor` and `obfs4proxy` from either the [official Debian repository](https://support.torproject.org/apt/tor-deb-repo/), or from your package manager. In Ubuntu 20.04 you need the `libxcb-xinerama0` package installed.
In Ubuntu 20.04 you also need the `libxcb-xinerama0` package installed. Install python dependencies:
```sh
pip3 install --user poetry requests
```
Download Tor Browser and extract the binaries:
```sh
./scripts/get-tor-linux.py
```
#### macOS #### macOS

131
desktop/scripts/get-tor-linux.py Executable file
View File

@ -0,0 +1,131 @@
#!/usr/bin/env python3
# -*- 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/>.
"""
"""
This script downloads a pre-built tor binary to bundle with OnionShare.
In order to avoid a Mac gnupg dependency, I manually verify the signature
and hard-code the sha256 hash.
"""
import inspect
import os
import sys
import hashlib
import shutil
import subprocess
import requests
def main():
tarball_url = "https://dist.torproject.org/torbrowser/11.0a9/tor-browser-linux64-11.0a9_en-US.tar.xz"
tarball_filename = "tor-browser-linux64-11.0a9_en-US.tar.xz"
expected_tarball_sha256 = (
"cba4a2120b4f847d1ade637e41e69bd01b2e70b4a13e41fe8e69d0424fcf7ca7"
)
# Build paths
root_path = os.path.dirname(
os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
)
working_path = os.path.join(root_path, "build", "tor")
tarball_path = os.path.join(working_path, tarball_filename)
dist_path = os.path.join(root_path, "src", "onionshare", "resources", "tor")
# Make sure dirs exist
if not os.path.exists(working_path):
os.makedirs(working_path, exist_ok=True)
if not os.path.exists(dist_path):
os.makedirs(dist_path, exist_ok=True)
# Make sure the tarball is downloaded
if not os.path.exists(tarball_path):
print("Downloading {}".format(tarball_url))
r = requests.get(tarball_url)
open(tarball_path, "wb").write(r.content)
tarball_sha256 = hashlib.sha256(r.content).hexdigest()
else:
tarball_data = open(tarball_path, "rb").read()
tarball_sha256 = hashlib.sha256(tarball_data).hexdigest()
# Compare the hash
if tarball_sha256 != expected_tarball_sha256:
print("ERROR! The sha256 doesn't match:")
print("expected: {}".format(expected_tarball_sha256))
print(" actual: {}".format(tarball_sha256))
sys.exit(-1)
# Delete extracted tarball, if it's there
shutil.rmtree(os.path.join(working_path, "tor-browser_en-US"), ignore_errors=True)
# Extract the tarball
subprocess.call(["tar", "-xvf", tarball_path], cwd=working_path)
tarball_tor_path = os.path.join(
working_path, "tor-browser_en-US", "Browser", "TorBrowser"
)
# Copy into dist
shutil.copyfile(
os.path.join(tarball_tor_path, "Data", "Tor", "geoip"),
os.path.join(dist_path, "geoip"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Data", "Tor", "geoip6"),
os.path.join(dist_path, "geoip6"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "tor"),
os.path.join(dist_path, "tor"),
)
os.chmod(os.path.join(dist_path, "tor"), 0o755)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "libcrypto.so.1.1"),
os.path.join(dist_path, "libcrypto.so.1.1"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "libevent-2.1.so.7"),
os.path.join(dist_path, "libevent-2.1.so.7"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "libssl.so.1.1"),
os.path.join(dist_path, "libssl.so.1.1"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "libstdc++", "libstdc++.so.6"),
os.path.join(dist_path, "libstdc++.so.6"),
)
shutil.copyfile(
os.path.join(tarball_tor_path, "Tor", "PluggableTransports", "obfs4proxy"),
os.path.join(dist_path, "obfs4proxy"),
)
os.chmod(os.path.join(dist_path, "obfs4proxy"), 0o755)
shutil.copyfile(
os.path.join(
tarball_tor_path, "Tor", "PluggableTransports", "snowflake-client"
),
os.path.join(dist_path, "snowflake-client"),
)
os.chmod(os.path.join(dist_path, "snowflake-client"), 0o755)
print(f"Tor binaries extracted to: {dist_path}")
if __name__ == "__main__":
main()

View File

@ -38,6 +38,7 @@ def main():
# Reinstall the new wheel # Reinstall the new wheel
subprocess.call(["pip", "uninstall", "onionshare-cli", "-y"]) subprocess.call(["pip", "uninstall", "onionshare-cli", "-y"])
subprocess.call(["pip", "install", os.path.join(desktop_path, wheel_basename)]) subprocess.call(["pip", "install", os.path.join(desktop_path, wheel_basename)])
subprocess.call(["pip", "install", "typing-extensions"])
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -205,14 +205,14 @@ class GuiCommon:
"downloads_uploads_not_empty": """ "downloads_uploads_not_empty": """
QWidget{ QWidget{
background-color: """ background-color: """
+ history_background_color + history_background_color
+"""; + """;
}""", }""",
"downloads_uploads_empty": """ "downloads_uploads_empty": """
QWidget { QWidget {
background-color: """ background-color: """
+ history_background_color + history_background_color
+"""; + """;
border: 1px solid #999999; border: 1px solid #999999;
} }
QWidget QLabel { QWidget QLabel {
@ -263,7 +263,7 @@ class GuiCommon:
+ """; + """;
width: 10px; width: 10px;
}""", }""",
"history_default_label" : """ "history_default_label": """
QLabel { QLabel {
color: """ color: """
+ history_label_color + history_label_color
@ -392,44 +392,44 @@ class GuiCommon:
QPushButton { QPushButton {
padding: 5px 10px; padding: 5px 10px;
}""", }""",
# Settings dialog # Moat dialog
"settings_version": """ "moat_error": """
QLabel { QLabel {
color: #666666; color: #990000;
}""", }
"settings_tor_status": """ """,
QLabel {
background-color: #ffffff;
color: #000000;
padding: 10px;
}""",
"settings_whats_this": """
QLabel {
font-size: 12px;
}""",
"settings_connect_to_tor": """
QLabel {
font-style: italic;
}""",
} }
def get_tor_paths(self): def get_tor_paths(self):
if self.common.platform == "Linux": if self.common.platform == "Linux":
tor_path = shutil.which("tor") base_path = self.get_resource_path("tor")
obfs4proxy_file_path = shutil.which("obfs4proxy") if os.path.exists(base_path):
prefix = os.path.dirname(os.path.dirname(tor_path)) tor_path = os.path.join(base_path, "tor")
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip") tor_geo_ip_file_path = os.path.join(base_path, "geoip")
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6") tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6")
elif self.common.platform == "Windows": obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy")
snowflake_file_path = os.path.join(base_path, "snowflake-client")
else:
# Fallback to looking in the path
tor_path = shutil.which("tor")
obfs4proxy_file_path = shutil.which("obfs4proxy")
snowflake_file_path = shutil.which("snowflake-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")
if self.common.platform == "Windows":
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")
snowflake_file_path = os.path.join(base_path, "Tor", "snowflake-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.common.platform == "Darwin": elif self.common.platform == "Darwin":
base_path = self.get_resource_path("tor") base_path = self.get_resource_path("tor")
tor_path = os.path.join(base_path, "tor") tor_path = os.path.join(base_path, "tor")
obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy") obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy")
snowflake_file_path = os.path.join(base_path, "snowflake-client")
tor_geo_ip_file_path = os.path.join(base_path, "geoip") tor_geo_ip_file_path = os.path.join(base_path, "geoip")
tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6") tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6")
elif self.common.platform == "BSD": elif self.common.platform == "BSD":
@ -437,12 +437,14 @@ class GuiCommon:
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"
snowflake_file_path = "/usr/local/bin/snowflake-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,
snowflake_file_path,
) )
@staticmethod @staticmethod

View File

@ -18,11 +18,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
import os
import time import time
from PySide2 import QtCore, QtWidgets, QtGui from PySide2 import QtCore, QtWidgets, QtGui
from . import strings from . import strings
from .tor_connection_dialog import TorConnectionDialog from .tor_connection_dialog import TorConnectionDialog
from .tor_settings_dialog import TorSettingsDialog
from .settings_dialog import SettingsDialog from .settings_dialog import SettingsDialog
from .widgets import Alert from .widgets import Alert
from .update_checker import UpdateThread from .update_checker import UpdateThread
@ -106,6 +108,24 @@ class MainWindow(QtWidgets.QMainWindow):
) )
self.status_bar.addPermanentWidget(self.status_bar.server_status_indicator) self.status_bar.addPermanentWidget(self.status_bar.server_status_indicator)
# Tor settings button
self.tor_settings_button = QtWidgets.QPushButton()
self.tor_settings_button.setDefault(False)
self.tor_settings_button.setFixedSize(40, 50)
self.tor_settings_button.setIcon(
QtGui.QIcon(
GuiCommon.get_resource_path(
"images/{}_tor_settings.png".format(self.common.gui.color_mode)
)
)
)
self.tor_settings_button.clicked.connect(self.open_tor_settings)
self.tor_settings_button.setStyleSheet(self.common.gui.css["settings_button"])
self.status_bar.addPermanentWidget(self.tor_settings_button)
if os.environ.get("ONIONSHARE_HIDE_TOR_SETTINGS") == "1":
self.tor_settings_button.hide()
# Settings button # Settings button
self.settings_button = QtWidgets.QPushButton() self.settings_button = QtWidgets.QPushButton()
self.settings_button.setDefault(False) self.settings_button.setDefault(False)
@ -145,7 +165,7 @@ class MainWindow(QtWidgets.QMainWindow):
# Start the "Connecting to Tor" dialog, which calls onion.connect() # Start the "Connecting to Tor" dialog, which calls onion.connect()
tor_con = TorConnectionDialog(self.common) tor_con = TorConnectionDialog(self.common)
tor_con.canceled.connect(self.tor_connection_canceled) tor_con.canceled.connect(self.tor_connection_canceled)
tor_con.open_settings.connect(self.tor_connection_open_settings) tor_con.open_tor_settings.connect(self.tor_connection_open_tor_settings)
if not self.common.gui.local_only: if not self.common.gui.local_only:
tor_con.start() tor_con.start()
self.settings_have_changed() self.settings_have_changed()
@ -200,7 +220,7 @@ class MainWindow(QtWidgets.QMainWindow):
"_tor_connection_canceled", "_tor_connection_canceled",
"Settings button clicked", "Settings button clicked",
) )
self.open_settings() self.open_tor_settings()
if a.clickedButton() == quit_button: if a.clickedButton() == quit_button:
# Quit # Quit
@ -214,14 +234,23 @@ class MainWindow(QtWidgets.QMainWindow):
# Wait 100ms before asking # Wait 100ms before asking
QtCore.QTimer.singleShot(100, ask) QtCore.QTimer.singleShot(100, ask)
def tor_connection_open_settings(self): def tor_connection_open_tor_settings(self):
""" """
The TorConnectionDialog wants to open the Settings dialog The TorConnectionDialog wants to open the Tor Settings dialog
""" """
self.common.log("MainWindow", "tor_connection_open_settings") self.common.log("MainWindow", "tor_connection_open_tor_settings")
# Wait 1ms for the event loop to finish closing the TorConnectionDialog # Wait 1ms for the event loop to finish closing the TorConnectionDialog
QtCore.QTimer.singleShot(1, self.open_settings) QtCore.QTimer.singleShot(1, self.open_tor_settings)
def open_tor_settings(self):
"""
Open the TorSettingsDialog.
"""
self.common.log("MainWindow", "open_tor_settings")
d = TorSettingsDialog(self.common)
d.settings_saved.connect(self.settings_have_changed)
d.exec_()
def open_settings(self): def open_settings(self):
""" """

View File

@ -0,0 +1,339 @@
# -*- 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/>.
"""
from PySide2 import QtCore, QtWidgets, QtGui
import requests
import os
import base64
import json
from . import strings
from .gui_common import GuiCommon
class MoatDialog(QtWidgets.QDialog):
"""
Moat dialog: Request a bridge from torproject.org
"""
got_bridges = QtCore.Signal(str)
def __init__(self, common):
super(MoatDialog, self).__init__()
self.common = common
self.common.log("MoatDialog", "__init__")
self.setModal(True)
self.setWindowTitle(strings._("gui_settings_bridge_moat_button"))
self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png")))
# Label
self.label = QtWidgets.QLabel()
# CAPTCHA image
self.captcha = QtWidgets.QLabel()
self.captcha.setFixedSize(400, 125) # this is the size of the CAPTCHA image
# Solution input
self.solution_lineedit = QtWidgets.QLineEdit()
self.solution_lineedit.setPlaceholderText(strings._("moat_captcha_placeholder"))
self.solution_lineedit.editingFinished.connect(
self.solution_lineedit_editing_finished
)
self.submit_button = QtWidgets.QPushButton(strings._("moat_captcha_submit"))
self.submit_button.clicked.connect(self.submit_clicked)
solution_layout = QtWidgets.QHBoxLayout()
solution_layout.addWidget(self.solution_lineedit)
solution_layout.addWidget(self.submit_button)
# Error label
self.error_label = QtWidgets.QLabel()
self.error_label.setStyleSheet(self.common.gui.css["moat_error"])
self.error_label.hide()
# Buttons
self.reload_button = QtWidgets.QPushButton(strings._("moat_captcha_reload"))
self.reload_button.clicked.connect(self.reload_clicked)
self.cancel_button = QtWidgets.QPushButton(
strings._("gui_settings_button_cancel")
)
self.cancel_button.clicked.connect(self.cancel_clicked)
buttons_layout = QtWidgets.QHBoxLayout()
buttons_layout.addStretch()
buttons_layout.addWidget(self.reload_button)
buttons_layout.addWidget(self.cancel_button)
# Layout
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.label)
layout.addWidget(self.captcha)
layout.addLayout(solution_layout)
layout.addStretch()
layout.addWidget(self.error_label)
layout.addLayout(buttons_layout)
self.setLayout(layout)
self.cancel_button.setFocus()
self.reload_clicked()
def reload_clicked(self):
"""
Reload button clicked.
"""
self.common.log("MoatDialog", "reload_clicked")
self.label.setText(strings._("moat_contact_label"))
self.error_label.hide()
self.captcha.hide()
self.solution_lineedit.hide()
self.reload_button.hide()
self.submit_button.hide()
# BridgeDB fetch
self.t_fetch = MoatThread(self.common, "fetch")
self.t_fetch.bridgedb_error.connect(self.bridgedb_error)
self.t_fetch.captcha_ready.connect(self.captcha_ready)
self.t_fetch.start()
def submit_clicked(self):
"""
Submit button clicked.
"""
self.error_label.hide()
self.solution_lineedit.setEnabled(False)
solution = self.solution_lineedit.text().strip()
if len(solution) == 0:
self.common.log("MoatDialog", "submit_clicked", "solution is blank")
self.error_label.setText(strings._("moat_solution_empty_error"))
self.error_label.show()
return
# BridgeDB check
self.t_check = MoatThread(
self.common,
"check",
{
"transport": self.transport,
"challenge": self.challenge,
"solution": self.solution_lineedit.text(),
},
)
self.t_check.bridgedb_error.connect(self.bridgedb_error)
self.t_check.captcha_error.connect(self.captcha_error)
self.t_check.bridges_ready.connect(self.bridges_ready)
self.t_check.start()
def cancel_clicked(self):
"""
Cancel button clicked.
"""
self.common.log("MoatDialog", "cancel_clicked")
self.close()
def bridgedb_error(self):
self.common.log("MoatDialog", "bridgedb_error")
self.error_label.setText(strings._("moat_bridgedb_error"))
self.error_label.show()
self.solution_lineedit.setEnabled(True)
def captcha_error(self, msg):
self.common.log("MoatDialog", "captcha_error")
if msg == "":
self.error_label.setText(strings._("moat_captcha_error"))
else:
self.error_label.setText(msg)
self.error_label.show()
self.solution_lineedit.setEnabled(True)
def captcha_ready(self, transport, image, challenge):
self.common.log("MoatDialog", "captcha_ready")
self.transport = transport
self.challenge = challenge
# Save captcha image to disk, so we can load it
captcha_data = base64.b64decode(image)
captcha_filename = os.path.join(self.common.build_tmp_dir(), "captcha.jpg")
with open(captcha_filename, "wb") as f:
f.write(captcha_data)
self.captcha.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(captcha_filename)))
os.remove(captcha_filename)
self.label.setText(strings._("moat_captcha_label"))
self.captcha.show()
self.solution_lineedit.setEnabled(True)
self.solution_lineedit.setText("")
self.solution_lineedit.show()
self.solution_lineedit.setFocus()
self.reload_button.show()
self.submit_button.show()
def solution_lineedit_editing_finished(self):
self.common.log("MoatDialog", "solution_lineedit_editing_finished")
def bridges_ready(self, bridges):
self.common.log("MoatDialog", "bridges_ready", bridges)
self.got_bridges.emit(bridges)
self.close()
class MoatThread(QtCore.QThread):
"""
This does all of the communicating with BridgeDB in a separate thread.
Valid actions are:
- "fetch": requests a new CAPTCHA
- "check": sends a CAPTCHA solution
"""
bridgedb_error = QtCore.Signal()
captcha_error = QtCore.Signal(str)
captcha_ready = QtCore.Signal(str, str, str)
bridges_ready = QtCore.Signal(str)
def __init__(self, common, action, data={}):
super(MoatThread, self).__init__()
self.common = common
self.common.log("MoatThread", "__init__", f"action={action}")
self.action = action
self.data = data
def run(self):
# TODO: Do all of this using domain fronting
if self.action == "fetch":
self.common.log("MoatThread", "run", f"starting fetch")
# Request a bridge
r = requests.post(
"https://bridges.torproject.org/moat/fetch",
headers={"Content-Type": "application/vnd.api+json"},
json={
"data": [
{
"version": "0.1.0",
"type": "client-transports",
"supported": [
"obfs4",
"snowflake",
],
}
]
},
)
if r.status_code != 200:
self.common.log("MoatThread", "run", f"status_code={r.status_code}")
self.bridgedb_error.emit()
return
try:
moat_res = r.json()
if "errors" in moat_res:
self.common.log("MoatThread", "run", f"errors={moat_res['errors']}")
self.bridgedb_error.emit()
return
if "data" not in moat_res:
self.common.log("MoatThread", "run", f"no data")
self.bridgedb_error.emit()
return
if moat_res["data"][0]["type"] != "moat-challenge":
self.common.log("MoatThread", "run", f"type != moat-challange")
self.bridgedb_error.emit()
return
transport = moat_res["data"][0]["transport"]
image = moat_res["data"][0]["image"]
challenge = moat_res["data"][0]["challenge"]
self.captcha_ready.emit(transport, image, challenge)
except Exception as e:
self.common.log("MoatThread", "run", f"hit exception: {e}")
self.bridgedb_error.emit()
return
elif self.action == "check":
self.common.log("MoatThread", "run", f"starting check")
# Check the CAPTCHA
r = requests.post(
"https://bridges.torproject.org/moat/check",
headers={"Content-Type": "application/vnd.api+json"},
json={
"data": [
{
"id": "2",
"type": "moat-solution",
"version": "0.1.0",
"transport": self.data["transport"],
"challenge": self.data["challenge"],
"solution": self.data["solution"],
"qrcode": "false",
}
]
},
)
if r.status_code != 200:
self.common.log("MoatThread", "run", f"status_code={r.status_code}")
self.bridgedb_error.emit()
return
try:
moat_res = r.json()
self.common.log(
"MoatThread",
"run",
f"got bridges:\n{json.dumps(moat_res,indent=2)}",
)
if "errors" in moat_res:
self.common.log("MoatThread", "run", f"errors={moat_res['errors']}")
if moat_res["errors"][0]["code"] == 419:
self.captcha_error.emit("")
return
else:
errors = " ".join([e["detail"] for e in moat_res["errors"]])
self.captcha_error.emit(errors)
return
if moat_res["data"][0]["type"] != "moat-bridges":
self.common.log("MoatThread", "run", f"type != moat-bridges")
self.bridgedb_error.emit()
return
bridges = moat_res["data"][0]["bridges"]
self.bridges_ready.emit("\n".join(bridges))
except Exception as e:
self.common.log("MoatThread", "run", f"hit exception: {e}")
self.bridgedb_error.emit()
return
else:
self.common.log("MoatThread", "run", f"invalid action: {self.action}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -40,6 +40,7 @@
"gui_please_wait_no_button": "Starting…", "gui_please_wait_no_button": "Starting…",
"gui_please_wait": "Starting… Click to cancel.", "gui_please_wait": "Starting… Click to cancel.",
"zip_progress_bar_format": "Compressing: %p%", "zip_progress_bar_format": "Compressing: %p%",
"gui_tor_settings_window_title": "Tor Settings",
"gui_settings_window_title": "Settings", "gui_settings_window_title": "Settings",
"gui_settings_autoupdate_label": "Check for new version", "gui_settings_autoupdate_label": "Check for new version",
"gui_settings_autoupdate_option": "Notify me when a new version is available", "gui_settings_autoupdate_option": "Notify me when a new version is available",
@ -49,29 +50,33 @@
"gui_settings_connection_type_label": "How should OnionShare connect to Tor?", "gui_settings_connection_type_label": "How should OnionShare connect to Tor?",
"gui_settings_connection_type_bundled_option": "Use the Tor version built into OnionShare", "gui_settings_connection_type_bundled_option": "Use the Tor version built into OnionShare",
"gui_settings_connection_type_automatic_option": "Attempt auto-configuration with Tor Browser", "gui_settings_connection_type_automatic_option": "Attempt auto-configuration with Tor Browser",
"gui_settings_controller_extras_label": "Tor settings",
"gui_settings_connection_type_control_port_option": "Connect using control port", "gui_settings_connection_type_control_port_option": "Connect using control port",
"gui_settings_connection_type_socket_file_option": "Connect using socket file", "gui_settings_connection_type_socket_file_option": "Connect using socket file",
"gui_settings_connection_type_test_button": "Test Connection to Tor", "gui_settings_connection_type_test_button": "Test Connection to Tor",
"gui_settings_control_port_label": "Control port", "gui_settings_control_port_label": "Control port",
"gui_settings_socket_file_label": "Socket file", "gui_settings_socket_file_label": "Socket file",
"gui_settings_socks_label": "SOCKS port", "gui_settings_socks_label": "SOCKS port",
"gui_settings_authenticate_label": "Tor authentication settings",
"gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication",
"gui_settings_authenticate_password_option": "Password", "gui_settings_authenticate_password_option": "Password",
"gui_settings_password_label": "Password", "gui_settings_password_label": "Password",
"gui_settings_tor_bridges": "Tor bridge support", "gui_settings_tor_bridges": "Connect using a Tor bridge?",
"gui_settings_tor_bridges_no_bridges_radio_option": "Don't use bridges", "gui_settings_tor_bridges_label": "Bridges help you access the Tor Network in places where Tor is blocked. Depending on where you are, one bridge may work better than another.",
"gui_settings_tor_bridges_obfs4_radio_option": "Use built-in obfs4 pluggable transports", "gui_settings_bridge_use_checkbox": "Use a bridge",
"gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Use built-in obfs4 pluggable transports (requires obfs4proxy)", "gui_settings_bridge_radio_builtin": "Select a built-in bridge",
"gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek_lite (Azure) pluggable transports", "gui_settings_bridge_none_radio_option": "Don't use a bridge",
"gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Use built-in meek_lite (Azure) pluggable transports (requires obfs4proxy)", "gui_settings_meek_lite_expensive_warning": "Warning: The meek-azure bridges are very costly for the Tor Project to run.<br><br>Only use them if unable to connect to Tor directly, via obfs4 transports, or other normal bridges.",
"gui_settings_meek_lite_expensive_warning": "Warning: The meek_lite bridges are very costly for the Tor Project to run.<br><br>Only use them if unable to connect to Tor directly, via obfs4 transports, or other normal bridges.", "gui_settings_bridge_moat_radio_option": "Request a bridge from torproject.org",
"gui_settings_tor_bridges_custom_radio_option": "Use custom bridges", "gui_settings_bridge_moat_button": "Request a New Bridge",
"gui_settings_tor_bridges_custom_label": "You can get bridges from <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>", "gui_settings_bridge_custom_radio_option": "Provide a bridge you learned about from a trusted source",
"gui_settings_bridge_custom_placeholder": "type address:port (one per line)",
"gui_settings_moat_bridges_invalid": "You have not requested a bridge from torproject.org yet.",
"gui_settings_tor_bridges_invalid": "None of the bridges you added work.\nDouble-check them or add others.", "gui_settings_tor_bridges_invalid": "None of the bridges you added work.\nDouble-check them or add others.",
"gui_settings_button_save": "Save", "gui_settings_button_save": "Save",
"gui_settings_button_cancel": "Cancel", "gui_settings_button_cancel": "Cancel",
"gui_settings_button_help": "Help", "gui_settings_button_help": "Help",
"gui_settings_version_label": "You are using OnionShare {}",
"gui_settings_help_label": "Need help? See <a href='https://docs.onionshare.org'>docs.onionshare.org</a>",
"settings_test_success": "Connected to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}.\nSupports client authentication: {}.\nSupports next-gen .onion addresses: {}.", "settings_test_success": "Connected to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}.\nSupports client authentication: {}.\nSupports next-gen .onion addresses: {}.",
"connecting_to_tor": "Connecting to the Tor network", "connecting_to_tor": "Connecting to the Tor network",
"update_available": "New OnionShare out. <a href='{}'>Click here</a> to get it.<br><br>You are using {} and the latest is {}.", "update_available": "New OnionShare out. <a href='{}'>Click here</a> to get it.<br><br>You are using {} and the latest is {}.",
@ -125,7 +130,7 @@
"error_cannot_create_data_dir": "Could not create OnionShare data folder: {}", "error_cannot_create_data_dir": "Could not create OnionShare data folder: {}",
"gui_receive_mode_warning": "Receive mode lets people upload files to your computer.<br><br><b>Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.</b>", "gui_receive_mode_warning": "Receive mode lets people upload files to your computer.<br><br><b>Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.</b>",
"gui_open_folder_error": "Failed to open folder with xdg-open. The file is here: {}", "gui_open_folder_error": "Failed to open folder with xdg-open. The file is here: {}",
"gui_settings_language_label": "Preferred language", "gui_settings_language_label": "Language",
"gui_settings_theme_label": "Theme", "gui_settings_theme_label": "Theme",
"gui_settings_theme_auto": "Auto", "gui_settings_theme_auto": "Auto",
"gui_settings_theme_light": "Light", "gui_settings_theme_light": "Light",
@ -215,5 +220,13 @@
"gui_rendezvous_cleanup_quit_early": "Quit Early", "gui_rendezvous_cleanup_quit_early": "Quit Early",
"error_port_not_available": "OnionShare port not available", "error_port_not_available": "OnionShare port not available",
"history_receive_read_message_button": "Read Message", "history_receive_read_message_button": "Read Message",
"error_tor_protocol_error": "There was an error with Tor: {}" "error_tor_protocol_error": "There was an error with Tor: {}",
"moat_contact_label": "Contacting BridgeDB...",
"moat_captcha_label": "Solve the CAPTCHA to request a bridge.",
"moat_captcha_placeholder": "Enter the characters from the image",
"moat_captcha_submit": "Submit",
"moat_captcha_reload": "Reload",
"moat_bridgedb_error": "Error contacting BridgeDB.",
"moat_captcha_error": "The solution is not correct. Please try again.",
"moat_solution_empty_error": "You must enter the characters from the image"
} }

View File

@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
from PySide2 import QtCore, QtWidgets, QtGui from PySide2 import QtCore, QtWidgets, QtGui
from PySide2.QtCore import Slot,Qt from PySide2.QtCore import Slot, Qt
from PySide2.QtGui import QPalette, QColor from PySide2.QtGui import QPalette, QColor
import sys import sys
import platform import platform
@ -46,8 +46,7 @@ from onionshare_cli.onion import (
from . import strings from . import strings
from .widgets import Alert from .widgets import Alert
from .update_checker import ( from .update_checker import UpdateThread
UpdateThread)
from .tor_connection_dialog import TorConnectionDialog from .tor_connection_dialog import TorConnectionDialog
from .gui_common import GuiCommon from .gui_common import GuiCommon
@ -72,9 +71,6 @@ class SettingsDialog(QtWidgets.QDialog):
self.system = platform.system() self.system = platform.system()
# If ONIONSHARE_HIDE_TOR_SETTINGS=1, hide Tor settings in the dialog
self.hide_tor_settings = os.environ.get("ONIONSHARE_HIDE_TOR_SETTINGS") == "1"
# Automatic updates options # Automatic updates options
# Autoupdate # Autoupdate
@ -125,13 +121,13 @@ class SettingsDialog(QtWidgets.QDialog):
language_layout.addWidget(self.language_combobox) language_layout.addWidget(self.language_combobox)
language_layout.addStretch() language_layout.addStretch()
#Theme Settings # Theme Settings
theme_label = QtWidgets.QLabel(strings._("gui_settings_theme_label")) theme_label = QtWidgets.QLabel(strings._("gui_settings_theme_label"))
self.theme_combobox = QtWidgets.QComboBox() self.theme_combobox = QtWidgets.QComboBox()
theme_choices = [ theme_choices = [
strings._("gui_settings_theme_auto"), strings._("gui_settings_theme_auto"),
strings._("gui_settings_theme_light"), strings._("gui_settings_theme_light"),
strings._("gui_settings_theme_dark") strings._("gui_settings_theme_dark"),
] ]
self.theme_combobox.addItems(theme_choices) self.theme_combobox.addItems(theme_choices)
theme_layout = QtWidgets.QHBoxLayout() theme_layout = QtWidgets.QHBoxLayout()
@ -139,294 +135,13 @@ class SettingsDialog(QtWidgets.QDialog):
theme_layout.addWidget(self.theme_combobox) theme_layout.addWidget(self.theme_combobox)
theme_layout.addStretch() theme_layout.addStretch()
# Connection type: either automatic, control port, or socket file # Version and help
version_label = QtWidgets.QLabel(
# Bundled Tor strings._("gui_settings_version_label").format(self.common.version)
self.connection_type_bundled_radio = QtWidgets.QRadioButton(
strings._("gui_settings_connection_type_bundled_option")
) )
self.connection_type_bundled_radio.toggled.connect( help_label = QtWidgets.QLabel(strings._("gui_settings_help_label"))
self.connection_type_bundled_toggled help_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
) help_label.setOpenExternalLinks(True)
# Bundled Tor doesn't work on dev mode in Windows or Mac
if (self.system == "Windows" or self.system == "Darwin") and getattr(
sys, "onionshare_dev_mode", False
):
self.connection_type_bundled_radio.setEnabled(False)
# Bridge options for bundled tor
# No bridges option radio
self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton(
strings._("gui_settings_tor_bridges_no_bridges_radio_option")
)
self.tor_bridges_no_bridges_radio.toggled.connect(
self.tor_bridges_no_bridges_radio_toggled
)
# obfs4 option radio
# if the obfs4proxy binary is missing, we can't use obfs4 transports
(
self.tor_path,
self.tor_geo_ip_file_path,
self.tor_geo_ipv6_file_path,
self.obfs4proxy_file_path,
) = self.common.gui.get_tor_paths()
if not self.obfs4proxy_file_path or not os.path.isfile(
self.obfs4proxy_file_path
):
self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(
strings._("gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy")
)
self.tor_bridges_use_obfs4_radio.setEnabled(False)
else:
self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(
strings._("gui_settings_tor_bridges_obfs4_radio_option")
)
self.tor_bridges_use_obfs4_radio.toggled.connect(
self.tor_bridges_use_obfs4_radio_toggled
)
# meek_lite-azure option radio
# if the obfs4proxy binary is missing, we can't use meek_lite-azure transports
(
self.tor_path,
self.tor_geo_ip_file_path,
self.tor_geo_ipv6_file_path,
self.obfs4proxy_file_path,
) = self.common.gui.get_tor_paths()
if not self.obfs4proxy_file_path or not os.path.isfile(
self.obfs4proxy_file_path
):
self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(
strings._(
"gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy"
)
)
self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False)
else:
self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(
strings._("gui_settings_tor_bridges_meek_lite_azure_radio_option")
)
self.tor_bridges_use_meek_lite_azure_radio.toggled.connect(
self.tor_bridges_use_meek_lite_azure_radio_toggled
)
# Custom bridges radio and textbox
self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton(
strings._("gui_settings_tor_bridges_custom_radio_option")
)
self.tor_bridges_use_custom_radio.toggled.connect(
self.tor_bridges_use_custom_radio_toggled
)
self.tor_bridges_use_custom_label = QtWidgets.QLabel(
strings._("gui_settings_tor_bridges_custom_label")
)
self.tor_bridges_use_custom_label.setTextInteractionFlags(
QtCore.Qt.TextBrowserInteraction
)
self.tor_bridges_use_custom_label.setOpenExternalLinks(True)
self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit()
self.tor_bridges_use_custom_textbox.setMaximumHeight(200)
self.tor_bridges_use_custom_textbox.setPlaceholderText(
"[address:port] [identifier]"
)
tor_bridges_use_custom_textbox_options_layout = QtWidgets.QVBoxLayout()
tor_bridges_use_custom_textbox_options_layout.addWidget(
self.tor_bridges_use_custom_label
)
tor_bridges_use_custom_textbox_options_layout.addWidget(
self.tor_bridges_use_custom_textbox
)
self.tor_bridges_use_custom_textbox_options = QtWidgets.QWidget()
self.tor_bridges_use_custom_textbox_options.setLayout(
tor_bridges_use_custom_textbox_options_layout
)
self.tor_bridges_use_custom_textbox_options.hide()
# Bridges layout/widget
bridges_layout = QtWidgets.QVBoxLayout()
bridges_layout.addWidget(self.tor_bridges_no_bridges_radio)
bridges_layout.addWidget(self.tor_bridges_use_obfs4_radio)
bridges_layout.addWidget(self.tor_bridges_use_meek_lite_azure_radio)
bridges_layout.addWidget(self.tor_bridges_use_custom_radio)
bridges_layout.addWidget(self.tor_bridges_use_custom_textbox_options)
self.bridges = QtWidgets.QWidget()
self.bridges.setLayout(bridges_layout)
# Automatic
self.connection_type_automatic_radio = QtWidgets.QRadioButton(
strings._("gui_settings_connection_type_automatic_option")
)
self.connection_type_automatic_radio.toggled.connect(
self.connection_type_automatic_toggled
)
# Control port
self.connection_type_control_port_radio = QtWidgets.QRadioButton(
strings._("gui_settings_connection_type_control_port_option")
)
self.connection_type_control_port_radio.toggled.connect(
self.connection_type_control_port_toggled
)
connection_type_control_port_extras_label = QtWidgets.QLabel(
strings._("gui_settings_control_port_label")
)
self.connection_type_control_port_extras_address = QtWidgets.QLineEdit()
self.connection_type_control_port_extras_port = QtWidgets.QLineEdit()
connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout()
connection_type_control_port_extras_layout.addWidget(
connection_type_control_port_extras_label
)
connection_type_control_port_extras_layout.addWidget(
self.connection_type_control_port_extras_address
)
connection_type_control_port_extras_layout.addWidget(
self.connection_type_control_port_extras_port
)
self.connection_type_control_port_extras = QtWidgets.QWidget()
self.connection_type_control_port_extras.setLayout(
connection_type_control_port_extras_layout
)
self.connection_type_control_port_extras.hide()
# Socket file
self.connection_type_socket_file_radio = QtWidgets.QRadioButton(
strings._("gui_settings_connection_type_socket_file_option")
)
self.connection_type_socket_file_radio.toggled.connect(
self.connection_type_socket_file_toggled
)
connection_type_socket_file_extras_label = QtWidgets.QLabel(
strings._("gui_settings_socket_file_label")
)
self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit()
connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout()
connection_type_socket_file_extras_layout.addWidget(
connection_type_socket_file_extras_label
)
connection_type_socket_file_extras_layout.addWidget(
self.connection_type_socket_file_extras_path
)
self.connection_type_socket_file_extras = QtWidgets.QWidget()
self.connection_type_socket_file_extras.setLayout(
connection_type_socket_file_extras_layout
)
self.connection_type_socket_file_extras.hide()
# Tor SOCKS address and port
gui_settings_socks_label = QtWidgets.QLabel(
strings._("gui_settings_socks_label")
)
self.connection_type_socks_address = QtWidgets.QLineEdit()
self.connection_type_socks_port = QtWidgets.QLineEdit()
connection_type_socks_layout = QtWidgets.QHBoxLayout()
connection_type_socks_layout.addWidget(gui_settings_socks_label)
connection_type_socks_layout.addWidget(self.connection_type_socks_address)
connection_type_socks_layout.addWidget(self.connection_type_socks_port)
self.connection_type_socks = QtWidgets.QWidget()
self.connection_type_socks.setLayout(connection_type_socks_layout)
self.connection_type_socks.hide()
# Authentication options
# No authentication
self.authenticate_no_auth_radio = QtWidgets.QRadioButton(
strings._("gui_settings_authenticate_no_auth_option")
)
self.authenticate_no_auth_radio.toggled.connect(
self.authenticate_no_auth_toggled
)
# Password
self.authenticate_password_radio = QtWidgets.QRadioButton(
strings._("gui_settings_authenticate_password_option")
)
self.authenticate_password_radio.toggled.connect(
self.authenticate_password_toggled
)
authenticate_password_extras_label = QtWidgets.QLabel(
strings._("gui_settings_password_label")
)
self.authenticate_password_extras_password = QtWidgets.QLineEdit("")
authenticate_password_extras_layout = QtWidgets.QHBoxLayout()
authenticate_password_extras_layout.addWidget(
authenticate_password_extras_label
)
authenticate_password_extras_layout.addWidget(
self.authenticate_password_extras_password
)
self.authenticate_password_extras = QtWidgets.QWidget()
self.authenticate_password_extras.setLayout(authenticate_password_extras_layout)
self.authenticate_password_extras.hide()
# Authentication options layout
authenticate_group_layout = QtWidgets.QVBoxLayout()
authenticate_group_layout.addWidget(self.authenticate_no_auth_radio)
authenticate_group_layout.addWidget(self.authenticate_password_radio)
authenticate_group_layout.addWidget(self.authenticate_password_extras)
self.authenticate_group = QtWidgets.QGroupBox(
strings._("gui_settings_authenticate_label")
)
self.authenticate_group.setLayout(authenticate_group_layout)
# Put the radios into their own group so they are exclusive
connection_type_radio_group_layout = QtWidgets.QVBoxLayout()
connection_type_radio_group_layout.addWidget(self.connection_type_bundled_radio)
connection_type_radio_group_layout.addWidget(
self.connection_type_automatic_radio
)
connection_type_radio_group_layout.addWidget(
self.connection_type_control_port_radio
)
connection_type_radio_group_layout.addWidget(
self.connection_type_socket_file_radio
)
connection_type_radio_group = QtWidgets.QGroupBox(
strings._("gui_settings_connection_type_label")
)
connection_type_radio_group.setLayout(connection_type_radio_group_layout)
# The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges)
connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout()
connection_type_bridges_radio_group_layout.addWidget(self.bridges)
self.connection_type_bridges_radio_group = QtWidgets.QGroupBox(
strings._("gui_settings_tor_bridges")
)
self.connection_type_bridges_radio_group.setLayout(
connection_type_bridges_radio_group_layout
)
self.connection_type_bridges_radio_group.hide()
# Test tor settings button
self.connection_type_test_button = QtWidgets.QPushButton(
strings._("gui_settings_connection_type_test_button")
)
self.connection_type_test_button.clicked.connect(self.test_tor_clicked)
connection_type_test_button_layout = QtWidgets.QHBoxLayout()
connection_type_test_button_layout.addWidget(self.connection_type_test_button)
connection_type_test_button_layout.addStretch()
# Connection type layout
connection_type_layout = QtWidgets.QVBoxLayout()
connection_type_layout.addWidget(self.connection_type_control_port_extras)
connection_type_layout.addWidget(self.connection_type_socket_file_extras)
connection_type_layout.addWidget(self.connection_type_socks)
connection_type_layout.addWidget(self.authenticate_group)
connection_type_layout.addWidget(self.connection_type_bridges_radio_group)
connection_type_layout.addLayout(connection_type_test_button_layout)
# Buttons # Buttons
self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save")) self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save"))
@ -435,41 +150,23 @@ class SettingsDialog(QtWidgets.QDialog):
strings._("gui_settings_button_cancel") strings._("gui_settings_button_cancel")
) )
self.cancel_button.clicked.connect(self.cancel_clicked) self.cancel_button.clicked.connect(self.cancel_clicked)
version_label = QtWidgets.QLabel(f"OnionShare {self.common.version}")
version_label.setStyleSheet(self.common.gui.css["settings_version"])
self.help_button = QtWidgets.QPushButton(strings._("gui_settings_button_help"))
self.help_button.clicked.connect(self.help_clicked)
buttons_layout = QtWidgets.QHBoxLayout() buttons_layout = QtWidgets.QHBoxLayout()
buttons_layout.addWidget(version_label)
buttons_layout.addWidget(self.help_button)
buttons_layout.addStretch() buttons_layout.addStretch()
buttons_layout.addWidget(self.save_button) buttons_layout.addWidget(self.save_button)
buttons_layout.addWidget(self.cancel_button) buttons_layout.addWidget(self.cancel_button)
# Tor network connection status
self.tor_status = QtWidgets.QLabel()
self.tor_status.setStyleSheet(self.common.gui.css["settings_tor_status"])
self.tor_status.hide()
# Layout # Layout
tor_layout = QtWidgets.QVBoxLayout()
tor_layout.addWidget(connection_type_radio_group)
tor_layout.addLayout(connection_type_layout)
tor_layout.addWidget(self.tor_status)
tor_layout.addStretch()
layout = QtWidgets.QVBoxLayout() layout = QtWidgets.QVBoxLayout()
if not self.hide_tor_settings:
layout.addLayout(tor_layout)
layout.addSpacing(20)
layout.addWidget(autoupdate_group) layout.addWidget(autoupdate_group)
if autoupdate_group.isVisible(): if autoupdate_group.isVisible():
layout.addSpacing(20) layout.addSpacing(20)
layout.addLayout(language_layout) layout.addLayout(language_layout)
layout.addSpacing(20)
layout.addLayout(theme_layout) layout.addLayout(theme_layout)
layout.addSpacing(20) layout.addSpacing(20)
layout.addStretch() layout.addStretch()
layout.addWidget(version_label)
layout.addWidget(help_label)
layout.addSpacing(20)
layout.addLayout(buttons_layout) layout.addLayout(buttons_layout)
self.setLayout(layout) self.setLayout(layout)
@ -498,257 +195,6 @@ class SettingsDialog(QtWidgets.QDialog):
theme_choice = self.old_settings.get("theme") theme_choice = self.old_settings.get("theme")
self.theme_combobox.setCurrentIndex(theme_choice) self.theme_combobox.setCurrentIndex(theme_choice)
connection_type = self.old_settings.get("connection_type")
if connection_type == "bundled":
if self.connection_type_bundled_radio.isEnabled():
self.connection_type_bundled_radio.setChecked(True)
else:
# If bundled tor is disabled, fallback to automatic
self.connection_type_automatic_radio.setChecked(True)
elif connection_type == "automatic":
self.connection_type_automatic_radio.setChecked(True)
elif connection_type == "control_port":
self.connection_type_control_port_radio.setChecked(True)
elif connection_type == "socket_file":
self.connection_type_socket_file_radio.setChecked(True)
self.connection_type_control_port_extras_address.setText(
self.old_settings.get("control_port_address")
)
self.connection_type_control_port_extras_port.setText(
str(self.old_settings.get("control_port_port"))
)
self.connection_type_socket_file_extras_path.setText(
self.old_settings.get("socket_file_path")
)
self.connection_type_socks_address.setText(
self.old_settings.get("socks_address")
)
self.connection_type_socks_port.setText(
str(self.old_settings.get("socks_port"))
)
auth_type = self.old_settings.get("auth_type")
if auth_type == "no_auth":
self.authenticate_no_auth_radio.setChecked(True)
elif auth_type == "password":
self.authenticate_password_radio.setChecked(True)
self.authenticate_password_extras_password.setText(
self.old_settings.get("auth_password")
)
if self.old_settings.get("no_bridges"):
self.tor_bridges_no_bridges_radio.setChecked(True)
self.tor_bridges_use_obfs4_radio.setChecked(False)
self.tor_bridges_use_meek_lite_azure_radio.setChecked(False)
self.tor_bridges_use_custom_radio.setChecked(False)
else:
self.tor_bridges_no_bridges_radio.setChecked(False)
self.tor_bridges_use_obfs4_radio.setChecked(
self.old_settings.get("tor_bridges_use_obfs4")
)
self.tor_bridges_use_meek_lite_azure_radio.setChecked(
self.old_settings.get("tor_bridges_use_meek_lite_azure")
)
if self.old_settings.get("tor_bridges_use_custom_bridges"):
self.tor_bridges_use_custom_radio.setChecked(True)
# Remove the 'Bridge' lines at the start of each bridge.
# They are added automatically to provide compatibility with
# copying/pasting bridges provided from https://bridges.torproject.org
new_bridges = []
bridges = self.old_settings.get("tor_bridges_use_custom_bridges").split(
"Bridge "
)
for bridge in bridges:
new_bridges.append(bridge)
new_bridges = "".join(new_bridges)
self.tor_bridges_use_custom_textbox.setPlainText(new_bridges)
def connection_type_bundled_toggled(self, checked):
"""
Connection type bundled was toggled. If checked, hide authentication fields.
"""
self.common.log("SettingsDialog", "connection_type_bundled_toggled")
if self.hide_tor_settings:
return
if checked:
self.authenticate_group.hide()
self.connection_type_socks.hide()
self.connection_type_bridges_radio_group.show()
def tor_bridges_no_bridges_radio_toggled(self, checked):
"""
'No bridges' option was toggled. If checked, enable other bridge options.
"""
if self.hide_tor_settings:
return
if checked:
self.tor_bridges_use_custom_textbox_options.hide()
def tor_bridges_use_obfs4_radio_toggled(self, checked):
"""
obfs4 bridges option was toggled. If checked, disable custom bridge options.
"""
if self.hide_tor_settings:
return
if checked:
self.tor_bridges_use_custom_textbox_options.hide()
def tor_bridges_use_meek_lite_azure_radio_toggled(self, checked):
"""
meek_lite_azure bridges option was toggled. If checked, disable custom bridge options.
"""
if self.hide_tor_settings:
return
if checked:
self.tor_bridges_use_custom_textbox_options.hide()
# Alert the user about meek's costliness if it looks like they're turning it on
if not self.old_settings.get("tor_bridges_use_meek_lite_azure"):
Alert(
self.common,
strings._("gui_settings_meek_lite_expensive_warning"),
QtWidgets.QMessageBox.Warning,
)
def tor_bridges_use_custom_radio_toggled(self, checked):
"""
Custom bridges option was toggled. If checked, show custom bridge options.
"""
if self.hide_tor_settings:
return
if checked:
self.tor_bridges_use_custom_textbox_options.show()
def connection_type_automatic_toggled(self, checked):
"""
Connection type automatic was toggled. If checked, hide authentication fields.
"""
self.common.log("SettingsDialog", "connection_type_automatic_toggled")
if self.hide_tor_settings:
return
if checked:
self.authenticate_group.hide()
self.connection_type_socks.hide()
self.connection_type_bridges_radio_group.hide()
def connection_type_control_port_toggled(self, checked):
"""
Connection type control port was toggled. If checked, show extra fields
for Tor control address and port. If unchecked, hide those extra fields.
"""
self.common.log("SettingsDialog", "connection_type_control_port_toggled")
if self.hide_tor_settings:
return
if checked:
self.authenticate_group.show()
self.connection_type_control_port_extras.show()
self.connection_type_socks.show()
self.connection_type_bridges_radio_group.hide()
else:
self.connection_type_control_port_extras.hide()
def connection_type_socket_file_toggled(self, checked):
"""
Connection type socket file was toggled. If checked, show extra fields
for socket file. If unchecked, hide those extra fields.
"""
self.common.log("SettingsDialog", "connection_type_socket_file_toggled")
if self.hide_tor_settings:
return
if checked:
self.authenticate_group.show()
self.connection_type_socket_file_extras.show()
self.connection_type_socks.show()
self.connection_type_bridges_radio_group.hide()
else:
self.connection_type_socket_file_extras.hide()
def authenticate_no_auth_toggled(self, checked):
"""
Authentication option no authentication was toggled.
"""
self.common.log("SettingsDialog", "authenticate_no_auth_toggled")
def authenticate_password_toggled(self, checked):
"""
Authentication option password was toggled. If checked, show extra fields
for password auth. If unchecked, hide those extra fields.
"""
self.common.log("SettingsDialog", "authenticate_password_toggled")
if checked:
self.authenticate_password_extras.show()
else:
self.authenticate_password_extras.hide()
def test_tor_clicked(self):
"""
Test Tor Settings button clicked. With the given settings, see if we can
successfully connect and authenticate to Tor.
"""
self.common.log("SettingsDialog", "test_tor_clicked")
settings = self.settings_from_fields()
try:
# Show Tor connection status if connection type is bundled tor
if settings.get("connection_type") == "bundled":
self.tor_status.show()
self._disable_buttons()
def tor_status_update_func(progress, summary):
self._tor_status_update(progress, summary)
return True
else:
tor_status_update_func = None
onion = Onion(
self.common,
use_tmp_dir=True,
get_tor_paths=self.common.gui.get_tor_paths,
)
onion.connect(
custom_settings=settings,
tor_status_update_func=tor_status_update_func,
)
# If an exception hasn't been raised yet, the Tor settings work
Alert(
self.common,
strings._("settings_test_success").format(
onion.tor_version,
onion.supports_ephemeral,
onion.supports_stealth,
onion.supports_v3_onions,
),
)
# Clean up
onion.cleanup()
except (
TorErrorInvalidSetting,
TorErrorAutomatic,
TorErrorSocketPort,
TorErrorSocketFile,
TorErrorMissingPassword,
TorErrorUnreadableCookieFile,
TorErrorAuthError,
TorErrorProtocolError,
BundledTorTimeout,
BundledTorBroken,
TorTooOldEphemeral,
TorTooOldStealth,
PortNotAvailable,
) as e:
message = self.common.gui.get_translated_tor_error(e)
Alert(
self.common,
message,
QtWidgets.QMessageBox.Warning,
)
if settings.get("connection_type") == "bundled":
self.tor_status.hide()
self._enable_buttons()
def check_for_updates(self): def check_for_updates(self):
""" """
Check for Updates button clicked. Manually force an update check. Check for Updates button clicked. Manually force an update check.
@ -802,7 +248,9 @@ class SettingsDialog(QtWidgets.QDialog):
) )
close_forced_update_thread() close_forced_update_thread()
forced_update_thread = UpdateThread(self.common, self.common.gui.onion, force=True) forced_update_thread = UpdateThread(
self.common, self.common.gui.onion, force=True
)
forced_update_thread.update_available.connect(update_available) forced_update_thread.update_available.connect(update_available)
forced_update_thread.update_not_available.connect(update_not_available) forced_update_thread.update_not_available.connect(update_not_available)
forced_update_thread.update_error.connect(update_error) forced_update_thread.update_error.connect(update_error)
@ -843,7 +291,6 @@ class SettingsDialog(QtWidgets.QDialog):
notice = strings._("gui_settings_language_changed_notice") notice = strings._("gui_settings_language_changed_notice")
Alert(self.common, notice, QtWidgets.QMessageBox.Information) Alert(self.common, notice, QtWidgets.QMessageBox.Information)
# If color mode changed, inform user they need to restart OnionShare # If color mode changed, inform user they need to restart OnionShare
if changed(settings, self.old_settings, ["theme"]): if changed(settings, self.old_settings, ["theme"]):
notice = strings._("gui_color_mode_changed_notice") notice = strings._("gui_color_mode_changed_notice")
@ -851,74 +298,8 @@ class SettingsDialog(QtWidgets.QDialog):
# Save the new settings # Save the new settings
settings.save() settings.save()
self.settings_saved.emit()
# If Tor isn't connected, or if Tor settings have changed, Reinitialize self.close()
# the Onion object
reboot_onion = False
if not self.common.gui.local_only:
if self.common.gui.onion.is_authenticated():
self.common.log(
"SettingsDialog", "save_clicked", "Connected to Tor"
)
if changed(
settings,
self.old_settings,
[
"connection_type",
"control_port_address",
"control_port_port",
"socks_address",
"socks_port",
"socket_file_path",
"auth_type",
"auth_password",
"no_bridges",
"tor_bridges_use_obfs4",
"tor_bridges_use_meek_lite_azure",
"tor_bridges_use_custom_bridges",
],
):
reboot_onion = True
else:
self.common.log(
"SettingsDialog", "save_clicked", "Not connected to Tor"
)
# Tor isn't connected, so try connecting
reboot_onion = True
# Do we need to reinitialize Tor?
if reboot_onion:
# Reinitialize the Onion object
self.common.log(
"SettingsDialog", "save_clicked", "rebooting the Onion"
)
self.common.gui.onion.cleanup()
tor_con = TorConnectionDialog(self.common, settings)
tor_con.start()
self.common.log(
"SettingsDialog",
"save_clicked",
f"Onion done rebooting, connected to Tor: {self.common.gui.onion.connected_to_tor}",
)
if (
self.common.gui.onion.is_authenticated()
and not tor_con.wasCanceled()
):
self.settings_saved.emit()
self.close()
else:
self.settings_saved.emit()
self.close()
else:
self.settings_saved.emit()
self.close()
def cancel_clicked(self): def cancel_clicked(self):
""" """
@ -960,119 +341,15 @@ class SettingsDialog(QtWidgets.QDialog):
# Theme # Theme
theme_index = self.theme_combobox.currentIndex() theme_index = self.theme_combobox.currentIndex()
settings.set("theme",theme_index) settings.set("theme", theme_index)
# Language # Language
locale_index = self.language_combobox.currentIndex() locale_index = self.language_combobox.currentIndex()
locale = self.language_combobox.itemData(locale_index) locale = self.language_combobox.itemData(locale_index)
settings.set("locale", locale) settings.set("locale", locale)
# Tor connection
if self.connection_type_bundled_radio.isChecked():
settings.set("connection_type", "bundled")
if self.connection_type_automatic_radio.isChecked():
settings.set("connection_type", "automatic")
if self.connection_type_control_port_radio.isChecked():
settings.set("connection_type", "control_port")
if self.connection_type_socket_file_radio.isChecked():
settings.set("connection_type", "socket_file")
if self.autoupdate_checkbox.isChecked():
settings.set("use_autoupdate", True)
else:
settings.set("use_autoupdate", False)
settings.set(
"control_port_address",
self.connection_type_control_port_extras_address.text(),
)
settings.set(
"control_port_port", self.connection_type_control_port_extras_port.text()
)
settings.set(
"socket_file_path", self.connection_type_socket_file_extras_path.text()
)
settings.set("socks_address", self.connection_type_socks_address.text())
settings.set("socks_port", self.connection_type_socks_port.text())
if self.authenticate_no_auth_radio.isChecked():
settings.set("auth_type", "no_auth")
if self.authenticate_password_radio.isChecked():
settings.set("auth_type", "password")
settings.set("auth_password", self.authenticate_password_extras_password.text())
# Whether we use bridges
if self.tor_bridges_no_bridges_radio.isChecked():
settings.set("no_bridges", True)
settings.set("tor_bridges_use_obfs4", False)
settings.set("tor_bridges_use_meek_lite_azure", False)
settings.set("tor_bridges_use_custom_bridges", "")
if self.tor_bridges_use_obfs4_radio.isChecked():
settings.set("no_bridges", False)
settings.set("tor_bridges_use_obfs4", True)
settings.set("tor_bridges_use_meek_lite_azure", False)
settings.set("tor_bridges_use_custom_bridges", "")
if self.tor_bridges_use_meek_lite_azure_radio.isChecked():
settings.set("no_bridges", False)
settings.set("tor_bridges_use_obfs4", False)
settings.set("tor_bridges_use_meek_lite_azure", True)
settings.set("tor_bridges_use_custom_bridges", "")
if self.tor_bridges_use_custom_radio.isChecked():
settings.set("no_bridges", False)
settings.set("tor_bridges_use_obfs4", False)
settings.set("tor_bridges_use_meek_lite_azure", False)
# Insert a 'Bridge' line at the start of each bridge.
# This makes it easier to copy/paste a set of bridges
# provided from https://bridges.torproject.org
new_bridges = []
bridges = self.tor_bridges_use_custom_textbox.toPlainText().split("\n")
bridges_valid = False
for bridge in bridges:
if bridge != "":
# Check the syntax of the custom bridge to make sure it looks legitimate
ipv4_pattern = re.compile(
"(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$"
)
ipv6_pattern = re.compile(
"(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$"
)
meek_lite_pattern = re.compile(
"(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)"
)
if (
ipv4_pattern.match(bridge)
or ipv6_pattern.match(bridge)
or meek_lite_pattern.match(bridge)
):
new_bridges.append("".join(["Bridge ", bridge, "\n"]))
bridges_valid = True
if bridges_valid:
new_bridges = "".join(new_bridges)
settings.set("tor_bridges_use_custom_bridges", new_bridges)
else:
Alert(self.common, strings._("gui_settings_tor_bridges_invalid"))
settings.set("no_bridges", True)
return False
return settings return settings
def closeEvent(self, e):
self.common.log("SettingsDialog", "closeEvent")
# On close, if Tor isn't connected, then quit OnionShare altogether
if not self.common.gui.local_only:
if not self.common.gui.onion.is_authenticated():
self.common.log(
"SettingsDialog", "closeEvent", "Closing while not connected to Tor"
)
# Wait 1ms for the event loop to finish, then quit
QtCore.QTimer.singleShot(1, self.common.gui.qtapp.quit)
def _update_autoupdate_timestamp(self, autoupdate_timestamp): def _update_autoupdate_timestamp(self, autoupdate_timestamp):
self.common.log("SettingsDialog", "_update_autoupdate_timestamp") self.common.log("SettingsDialog", "_update_autoupdate_timestamp")
@ -1085,20 +362,10 @@ class SettingsDialog(QtWidgets.QDialog):
strings._("gui_settings_autoupdate_timestamp").format(last_checked) strings._("gui_settings_autoupdate_timestamp").format(last_checked)
) )
def _tor_status_update(self, progress, summary):
self.tor_status.setText(
f"<strong>{strings._('connecting_to_tor')}</strong><br>{progress}% {summary}"
)
self.common.gui.qtapp.processEvents()
if "Done" in summary:
self.tor_status.hide()
self._enable_buttons()
def _disable_buttons(self): def _disable_buttons(self):
self.common.log("SettingsDialog", "_disable_buttons") self.common.log("SettingsDialog", "_disable_buttons")
self.check_for_updates_button.setEnabled(False) self.check_for_updates_button.setEnabled(False)
self.connection_type_test_button.setEnabled(False)
self.save_button.setEnabled(False) self.save_button.setEnabled(False)
self.cancel_button.setEnabled(False) self.cancel_button.setEnabled(False)
@ -1109,6 +376,5 @@ class SettingsDialog(QtWidgets.QDialog):
self.check_for_updates_button.setEnabled(False) self.check_for_updates_button.setEnabled(False)
else: else:
self.check_for_updates_button.setEnabled(True) self.check_for_updates_button.setEnabled(True)
self.connection_type_test_button.setEnabled(True)
self.save_button.setEnabled(True) self.save_button.setEnabled(True)
self.cancel_button.setEnabled(True) self.cancel_button.setEnabled(True)

View File

@ -48,12 +48,16 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
Connecting to Tor dialog. Connecting to Tor dialog.
""" """
open_settings = QtCore.Signal() open_tor_settings = QtCore.Signal()
success = QtCore.Signal()
def __init__(self, common, custom_settings=False): def __init__(
self, common, custom_settings=False, testing_settings=False, onion=None
):
super(TorConnectionDialog, self).__init__(None) super(TorConnectionDialog, self).__init__(None)
self.common = common self.common = common
self.testing_settings = testing_settings
if custom_settings: if custom_settings:
self.settings = custom_settings self.settings = custom_settings
@ -62,7 +66,15 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
self.common.log("TorConnectionDialog", "__init__") self.common.log("TorConnectionDialog", "__init__")
self.setWindowTitle("OnionShare") if self.testing_settings:
self.title = strings._("gui_settings_connection_type_test_button")
self.onion = onion
else:
self.title = "OnionShare"
self.onion = self.common.gui.onion
self.setWindowTitle(self.title)
self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png"))) self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png")))
self.setModal(True) self.setModal(True)
self.setFixedSize(400, 150) self.setFixedSize(400, 150)
@ -112,7 +124,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
def _canceled_connecting_to_tor(self): def _canceled_connecting_to_tor(self):
self.common.log("TorConnectionDialog", "_canceled_connecting_to_tor") self.common.log("TorConnectionDialog", "_canceled_connecting_to_tor")
self.active = False self.active = False
self.common.gui.onion.cleanup() self.onion.cleanup()
# Cancel connecting to Tor # Cancel connecting to Tor
QtCore.QTimer.singleShot(1, self.cancel) QtCore.QTimer.singleShot(1, self.cancel)
@ -121,18 +133,25 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
self.common.log("TorConnectionDialog", "_error_connecting_to_tor") self.common.log("TorConnectionDialog", "_error_connecting_to_tor")
self.active = False self.active = False
def alert_and_open_settings(): if self.testing_settings:
# Display the exception in an alert box # If testing, just display the error but don't open settings
Alert( def alert():
self.common, Alert(self.common, msg, QtWidgets.QMessageBox.Warning, title=self.title)
f"{msg}\n\n{strings._('gui_tor_connection_error_settings')}",
QtWidgets.QMessageBox.Warning,
)
# Open settings else:
self.open_settings.emit() # If not testing, open settings after displaying the error
def alert():
Alert(
self.common,
f"{msg}\n\n{strings._('gui_tor_connection_error_settings')}",
QtWidgets.QMessageBox.Warning,
title=self.title,
)
QtCore.QTimer.singleShot(1, alert_and_open_settings) # Open settings
self.open_tor_settings.emit()
QtCore.QTimer.singleShot(1, alert)
# Cancel connecting to Tor # Cancel connecting to Tor
QtCore.QTimer.singleShot(1, self.cancel) QtCore.QTimer.singleShot(1, self.cancel)
@ -146,13 +165,9 @@ class TorConnectionThread(QtCore.QThread):
def __init__(self, common, settings, dialog): def __init__(self, common, settings, dialog):
super(TorConnectionThread, self).__init__() super(TorConnectionThread, self).__init__()
self.common = common self.common = common
self.common.log("TorConnectionThread", "__init__") self.common.log("TorConnectionThread", "__init__")
self.settings = settings self.settings = settings
self.dialog = dialog self.dialog = dialog
def run(self): def run(self):
@ -160,8 +175,8 @@ class TorConnectionThread(QtCore.QThread):
# Connect to the Onion # Connect to the Onion
try: try:
self.common.gui.onion.connect(self.settings, False, self._tor_status_update) self.dialog.onion.connect(self.settings, False, self._tor_status_update)
if self.common.gui.onion.connected_to_tor: if self.dialog.onion.connected_to_tor:
self.connected_to_tor.emit() self.connected_to_tor.emit()
else: else:
self.canceled_connecting_to_tor.emit() self.canceled_connecting_to_tor.emit()

View File

@ -0,0 +1,848 @@
# -*- 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/>.
"""
from PySide2 import QtCore, QtWidgets, QtGui
import sys
import platform
import re
import os
from onionshare_cli.settings import Settings
from onionshare_cli.onion import Onion
from . import strings
from .widgets import Alert
from .tor_connection_dialog import TorConnectionDialog
from .moat_dialog import MoatDialog
from .gui_common import GuiCommon
class TorSettingsDialog(QtWidgets.QDialog):
"""
Settings dialog.
"""
settings_saved = QtCore.Signal()
def __init__(self, common):
super(TorSettingsDialog, self).__init__()
self.common = common
self.common.log("TorSettingsDialog", "__init__")
self.setModal(True)
self.setWindowTitle(strings._("gui_tor_settings_window_title"))
self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png")))
self.system = platform.system()
# Connection type: either automatic, control port, or socket file
# Bundled Tor
self.connection_type_bundled_radio = QtWidgets.QRadioButton(
strings._("gui_settings_connection_type_bundled_option")
)
self.connection_type_bundled_radio.toggled.connect(
self.connection_type_bundled_toggled
)
# Bundled Tor doesn't work on dev mode in Windows or Mac
if (self.system == "Windows" or self.system == "Darwin") and getattr(
sys, "onionshare_dev_mode", False
):
self.connection_type_bundled_radio.setEnabled(False)
# Bridge options for bundled tor
(
self.tor_path,
self.tor_geo_ip_file_path,
self.tor_geo_ipv6_file_path,
self.obfs4proxy_file_path,
self.snowflake_file_path,
) = self.common.gui.get_tor_paths()
bridges_label = QtWidgets.QLabel(strings._("gui_settings_tor_bridges_label"))
bridges_label.setWordWrap(True)
self.bridge_use_checkbox = QtWidgets.QCheckBox(
strings._("gui_settings_bridge_use_checkbox")
)
self.bridge_use_checkbox.stateChanged.connect(
self.bridge_use_checkbox_state_changed
)
# Built-in bridge
self.bridge_builtin_radio = QtWidgets.QRadioButton(
strings._("gui_settings_bridge_radio_builtin")
)
self.bridge_builtin_radio.toggled.connect(self.bridge_builtin_radio_toggled)
self.bridge_builtin_dropdown = QtWidgets.QComboBox()
self.bridge_builtin_dropdown.currentTextChanged.connect(
self.bridge_builtin_dropdown_changed
)
if self.obfs4proxy_file_path and os.path.isfile(self.obfs4proxy_file_path):
self.bridge_builtin_dropdown.addItem("obfs4")
self.bridge_builtin_dropdown.addItem("meek-azure")
if self.snowflake_file_path and os.path.isfile(self.snowflake_file_path):
self.bridge_builtin_dropdown.addItem("snowflake")
# Request a bridge from torproject.org (moat)
self.bridge_moat_radio = QtWidgets.QRadioButton(
strings._("gui_settings_bridge_moat_radio_option")
)
self.bridge_moat_radio.toggled.connect(self.bridge_moat_radio_toggled)
self.bridge_moat_button = QtWidgets.QPushButton(
strings._("gui_settings_bridge_moat_button")
)
self.bridge_moat_button.clicked.connect(self.bridge_moat_button_clicked)
self.bridge_moat_textbox = QtWidgets.QPlainTextEdit()
self.bridge_moat_textbox.setMinimumHeight(100)
self.bridge_moat_textbox.setMaximumHeight(100)
self.bridge_moat_textbox.setReadOnly(True)
self.bridge_moat_textbox.setWordWrapMode(QtGui.QTextOption.NoWrap)
bridge_moat_textbox_options_layout = QtWidgets.QVBoxLayout()
bridge_moat_textbox_options_layout.addWidget(self.bridge_moat_button)
bridge_moat_textbox_options_layout.addWidget(self.bridge_moat_textbox)
self.bridge_moat_textbox_options = QtWidgets.QWidget()
self.bridge_moat_textbox_options.setLayout(bridge_moat_textbox_options_layout)
self.bridge_moat_textbox_options.hide()
# Custom bridges radio and textbox
self.bridge_custom_radio = QtWidgets.QRadioButton(
strings._("gui_settings_bridge_custom_radio_option")
)
self.bridge_custom_radio.toggled.connect(self.bridge_custom_radio_toggled)
self.bridge_custom_textbox = QtWidgets.QPlainTextEdit()
self.bridge_custom_textbox.setMinimumHeight(100)
self.bridge_custom_textbox.setMaximumHeight(100)
self.bridge_custom_textbox.setPlaceholderText(
strings._("gui_settings_bridge_custom_placeholder")
)
bridge_custom_textbox_options_layout = QtWidgets.QVBoxLayout()
bridge_custom_textbox_options_layout.addWidget(self.bridge_custom_textbox)
self.bridge_custom_textbox_options = QtWidgets.QWidget()
self.bridge_custom_textbox_options.setLayout(
bridge_custom_textbox_options_layout
)
self.bridge_custom_textbox_options.hide()
# Bridge settings layout
bridge_settings_layout = QtWidgets.QVBoxLayout()
bridge_settings_layout.addWidget(self.bridge_builtin_radio)
bridge_settings_layout.addWidget(self.bridge_builtin_dropdown)
bridge_settings_layout.addWidget(self.bridge_moat_radio)
bridge_settings_layout.addWidget(self.bridge_moat_textbox_options)
bridge_settings_layout.addWidget(self.bridge_custom_radio)
bridge_settings_layout.addWidget(self.bridge_custom_textbox_options)
self.bridge_settings = QtWidgets.QWidget()
self.bridge_settings.setLayout(bridge_settings_layout)
# Bridges layout/widget
bridges_layout = QtWidgets.QVBoxLayout()
bridges_layout.addWidget(bridges_label)
bridges_layout.addWidget(self.bridge_use_checkbox)
bridges_layout.addWidget(self.bridge_settings)
self.bridges = QtWidgets.QWidget()
self.bridges.setLayout(bridges_layout)
# Automatic
self.connection_type_automatic_radio = QtWidgets.QRadioButton(
strings._("gui_settings_connection_type_automatic_option")
)
self.connection_type_automatic_radio.toggled.connect(
self.connection_type_automatic_toggled
)
# Control port
self.connection_type_control_port_radio = QtWidgets.QRadioButton(
strings._("gui_settings_connection_type_control_port_option")
)
self.connection_type_control_port_radio.toggled.connect(
self.connection_type_control_port_toggled
)
connection_type_control_port_extras_label = QtWidgets.QLabel(
strings._("gui_settings_control_port_label")
)
self.connection_type_control_port_extras_address = QtWidgets.QLineEdit()
self.connection_type_control_port_extras_port = QtWidgets.QLineEdit()
connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout()
connection_type_control_port_extras_layout.addWidget(
connection_type_control_port_extras_label
)
connection_type_control_port_extras_layout.addWidget(
self.connection_type_control_port_extras_address
)
connection_type_control_port_extras_layout.addWidget(
self.connection_type_control_port_extras_port
)
self.connection_type_control_port_extras = QtWidgets.QWidget()
self.connection_type_control_port_extras.setLayout(
connection_type_control_port_extras_layout
)
self.connection_type_control_port_extras.hide()
# Socket file
self.connection_type_socket_file_radio = QtWidgets.QRadioButton(
strings._("gui_settings_connection_type_socket_file_option")
)
self.connection_type_socket_file_radio.toggled.connect(
self.connection_type_socket_file_toggled
)
connection_type_socket_file_extras_label = QtWidgets.QLabel(
strings._("gui_settings_socket_file_label")
)
self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit()
connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout()
connection_type_socket_file_extras_layout.addWidget(
connection_type_socket_file_extras_label
)
connection_type_socket_file_extras_layout.addWidget(
self.connection_type_socket_file_extras_path
)
self.connection_type_socket_file_extras = QtWidgets.QWidget()
self.connection_type_socket_file_extras.setLayout(
connection_type_socket_file_extras_layout
)
self.connection_type_socket_file_extras.hide()
# Tor SOCKS address and port
gui_settings_socks_label = QtWidgets.QLabel(
strings._("gui_settings_socks_label")
)
self.connection_type_socks_address = QtWidgets.QLineEdit()
self.connection_type_socks_port = QtWidgets.QLineEdit()
connection_type_socks_layout = QtWidgets.QHBoxLayout()
connection_type_socks_layout.addWidget(gui_settings_socks_label)
connection_type_socks_layout.addWidget(self.connection_type_socks_address)
connection_type_socks_layout.addWidget(self.connection_type_socks_port)
self.connection_type_socks = QtWidgets.QWidget()
self.connection_type_socks.setLayout(connection_type_socks_layout)
self.connection_type_socks.hide()
# Authentication options
self.authenticate_no_auth_checkbox = QtWidgets.QCheckBox(
strings._("gui_settings_authenticate_no_auth_option")
)
self.authenticate_no_auth_checkbox.toggled.connect(
self.authenticate_no_auth_toggled
)
authenticate_password_extras_label = QtWidgets.QLabel(
strings._("gui_settings_password_label")
)
self.authenticate_password_extras_password = QtWidgets.QLineEdit("")
authenticate_password_extras_layout = QtWidgets.QHBoxLayout()
authenticate_password_extras_layout.addWidget(
authenticate_password_extras_label
)
authenticate_password_extras_layout.addWidget(
self.authenticate_password_extras_password
)
self.authenticate_password_extras = QtWidgets.QWidget()
self.authenticate_password_extras.setLayout(authenticate_password_extras_layout)
self.authenticate_password_extras.hide()
# Group for Tor settings
tor_settings_layout = QtWidgets.QVBoxLayout()
tor_settings_layout.addWidget(self.connection_type_control_port_extras)
tor_settings_layout.addWidget(self.connection_type_socket_file_extras)
tor_settings_layout.addWidget(self.connection_type_socks)
tor_settings_layout.addWidget(self.authenticate_no_auth_checkbox)
tor_settings_layout.addWidget(self.authenticate_password_extras)
self.tor_settings_group = QtWidgets.QGroupBox(
strings._("gui_settings_controller_extras_label")
)
self.tor_settings_group.setLayout(tor_settings_layout)
self.tor_settings_group.hide()
# Put the radios into their own group so they are exclusive
connection_type_radio_group_layout = QtWidgets.QVBoxLayout()
connection_type_radio_group_layout.addWidget(self.connection_type_bundled_radio)
connection_type_radio_group_layout.addWidget(
self.connection_type_automatic_radio
)
connection_type_radio_group_layout.addWidget(
self.connection_type_control_port_radio
)
connection_type_radio_group_layout.addWidget(
self.connection_type_socket_file_radio
)
connection_type_radio_group = QtWidgets.QGroupBox(
strings._("gui_settings_connection_type_label")
)
connection_type_radio_group.setLayout(connection_type_radio_group_layout)
# The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges)
connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout()
connection_type_bridges_radio_group_layout.addWidget(self.bridges)
self.connection_type_bridges_radio_group = QtWidgets.QGroupBox(
strings._("gui_settings_tor_bridges")
)
self.connection_type_bridges_radio_group.setLayout(
connection_type_bridges_radio_group_layout
)
self.connection_type_bridges_radio_group.hide()
# Connection type layout
connection_type_layout = QtWidgets.QVBoxLayout()
connection_type_layout.addWidget(self.tor_settings_group)
connection_type_layout.addWidget(self.connection_type_bridges_radio_group)
# Buttons
self.test_tor_button = QtWidgets.QPushButton(
strings._("gui_settings_connection_type_test_button")
)
self.test_tor_button.clicked.connect(self.test_tor_clicked)
self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save"))
self.save_button.clicked.connect(self.save_clicked)
self.cancel_button = QtWidgets.QPushButton(
strings._("gui_settings_button_cancel")
)
self.cancel_button.clicked.connect(self.cancel_clicked)
buttons_layout = QtWidgets.QHBoxLayout()
buttons_layout.addWidget(self.test_tor_button)
buttons_layout.addStretch()
buttons_layout.addWidget(self.save_button)
buttons_layout.addWidget(self.cancel_button)
# Layout
layout = QtWidgets.QVBoxLayout()
layout.addWidget(connection_type_radio_group)
layout.addLayout(connection_type_layout)
layout.addStretch()
layout.addLayout(buttons_layout)
self.setLayout(layout)
self.cancel_button.setFocus()
self.reload_settings()
def reload_settings(self):
# Load settings, and fill them in
self.old_settings = Settings(self.common)
self.old_settings.load()
connection_type = self.old_settings.get("connection_type")
if connection_type == "bundled":
if self.connection_type_bundled_radio.isEnabled():
self.connection_type_bundled_radio.setChecked(True)
else:
# If bundled tor is disabled, fallback to automatic
self.connection_type_automatic_radio.setChecked(True)
elif connection_type == "automatic":
self.connection_type_automatic_radio.setChecked(True)
elif connection_type == "control_port":
self.connection_type_control_port_radio.setChecked(True)
elif connection_type == "socket_file":
self.connection_type_socket_file_radio.setChecked(True)
self.connection_type_control_port_extras_address.setText(
self.old_settings.get("control_port_address")
)
self.connection_type_control_port_extras_port.setText(
str(self.old_settings.get("control_port_port"))
)
self.connection_type_socket_file_extras_path.setText(
self.old_settings.get("socket_file_path")
)
self.connection_type_socks_address.setText(
self.old_settings.get("socks_address")
)
self.connection_type_socks_port.setText(
str(self.old_settings.get("socks_port"))
)
auth_type = self.old_settings.get("auth_type")
if auth_type == "no_auth":
self.authenticate_no_auth_checkbox.setCheckState(QtCore.Qt.Checked)
else:
self.authenticate_no_auth_checkbox.setChecked(QtCore.Qt.Unchecked)
self.authenticate_password_extras_password.setText(
self.old_settings.get("auth_password")
)
if self.old_settings.get("no_bridges"):
self.bridge_use_checkbox.setCheckState(QtCore.Qt.Unchecked)
self.bridge_settings.hide()
else:
self.bridge_use_checkbox.setCheckState(QtCore.Qt.Checked)
self.bridge_settings.show()
builtin_obfs4 = self.old_settings.get("tor_bridges_use_obfs4")
builtin_meek_azure = self.old_settings.get(
"tor_bridges_use_meek_lite_azure"
)
builtin_snowflake = self.old_settings.get("tor_bridges_use_snowflake")
if builtin_obfs4 or builtin_meek_azure or builtin_snowflake:
self.bridge_builtin_radio.setChecked(True)
self.bridge_builtin_dropdown.show()
if builtin_obfs4:
self.bridge_builtin_dropdown.setCurrentText("obfs4")
elif builtin_meek_azure:
self.bridge_builtin_dropdown.setCurrentText("meek-azure")
elif builtin_snowflake:
self.bridge_builtin_dropdown.setCurrentText("snowflake")
self.bridge_moat_textbox_options.hide()
self.bridge_custom_textbox_options.hide()
else:
self.bridge_builtin_radio.setChecked(False)
self.bridge_builtin_dropdown.hide()
use_moat = self.old_settings.get("tor_bridges_use_moat")
self.bridge_moat_radio.setChecked(use_moat)
if use_moat:
self.bridge_builtin_dropdown.hide()
self.bridge_custom_textbox_options.hide()
moat_bridges = self.old_settings.get("tor_bridges_use_moat_bridges")
self.bridge_moat_textbox.document().setPlainText(moat_bridges)
if len(moat_bridges.strip()) > 0:
self.bridge_moat_textbox_options.show()
else:
self.bridge_moat_textbox_options.hide()
custom_bridges = self.old_settings.get("tor_bridges_use_custom_bridges")
if len(custom_bridges.strip()) != 0:
self.bridge_custom_radio.setChecked(True)
self.bridge_custom_textbox.setPlainText(custom_bridges)
self.bridge_builtin_dropdown.hide()
self.bridge_moat_textbox_options.hide()
self.bridge_custom_textbox_options.show()
def connection_type_bundled_toggled(self, checked):
"""
Connection type bundled was toggled
"""
self.common.log("TorSettingsDialog", "connection_type_bundled_toggled")
if checked:
self.tor_settings_group.hide()
self.connection_type_socks.hide()
self.connection_type_bridges_radio_group.show()
def bridge_use_checkbox_state_changed(self, state):
"""
'Use a bridge' checkbox changed
"""
if state == QtCore.Qt.Checked:
self.bridge_settings.show()
self.bridge_builtin_radio.click()
self.bridge_builtin_dropdown.setCurrentText("obfs4")
else:
self.bridge_settings.hide()
def bridge_builtin_radio_toggled(self, checked):
"""
'Select a built-in bridge' radio button toggled
"""
if checked:
self.bridge_builtin_dropdown.show()
self.bridge_custom_textbox_options.hide()
self.bridge_moat_textbox_options.hide()
def bridge_builtin_dropdown_changed(self, selection):
"""
Build-in bridge selection changed
"""
if selection == "meek-azure":
# Alert the user about meek's costliness if it looks like they're turning it on
if not self.old_settings.get("tor_bridges_use_meek_lite_azure"):
Alert(
self.common,
strings._("gui_settings_meek_lite_expensive_warning"),
QtWidgets.QMessageBox.Warning,
)
def bridge_moat_radio_toggled(self, checked):
"""
Moat (request bridge) bridges option was toggled. If checked, show moat bridge options.
"""
if checked:
self.bridge_builtin_dropdown.hide()
self.bridge_custom_textbox_options.hide()
self.bridge_moat_textbox_options.show()
def bridge_moat_button_clicked(self):
"""
Request new bridge button clicked
"""
self.common.log("TorSettingsDialog", "bridge_moat_button_clicked")
moat_dialog = MoatDialog(self.common)
moat_dialog.got_bridges.connect(self.bridge_moat_got_bridges)
moat_dialog.exec_()
def bridge_moat_got_bridges(self, bridges):
"""
Got new bridges from moat
"""
self.common.log("TorSettingsDialog", "bridge_moat_got_bridges")
self.bridge_moat_textbox.document().setPlainText(bridges)
self.bridge_moat_textbox.show()
def bridge_custom_radio_toggled(self, checked):
"""
Custom bridges option was toggled. If checked, show custom bridge options.
"""
if checked:
self.bridge_builtin_dropdown.hide()
self.bridge_moat_textbox_options.hide()
self.bridge_custom_textbox_options.show()
def connection_type_automatic_toggled(self, checked):
"""
Connection type automatic was toggled. If checked, hide authentication fields.
"""
self.common.log("TorSettingsDialog", "connection_type_automatic_toggled")
if checked:
self.tor_settings_group.hide()
self.connection_type_socks.hide()
self.connection_type_bridges_radio_group.hide()
def connection_type_control_port_toggled(self, checked):
"""
Connection type control port was toggled. If checked, show extra fields
for Tor control address and port. If unchecked, hide those extra fields.
"""
self.common.log("TorSettingsDialog", "connection_type_control_port_toggled")
if checked:
self.tor_settings_group.show()
self.connection_type_control_port_extras.show()
self.connection_type_socks.show()
self.connection_type_bridges_radio_group.hide()
else:
self.connection_type_control_port_extras.hide()
def connection_type_socket_file_toggled(self, checked):
"""
Connection type socket file was toggled. If checked, show extra fields
for socket file. If unchecked, hide those extra fields.
"""
self.common.log("TorSettingsDialog", "connection_type_socket_file_toggled")
if checked:
self.tor_settings_group.show()
self.connection_type_socket_file_extras.show()
self.connection_type_socks.show()
self.connection_type_bridges_radio_group.hide()
else:
self.connection_type_socket_file_extras.hide()
def authenticate_no_auth_toggled(self, checked):
"""
Authentication option no authentication was toggled.
"""
self.common.log("TorSettingsDialog", "authenticate_no_auth_toggled")
if checked:
self.authenticate_password_extras.hide()
else:
self.authenticate_password_extras.show()
def test_tor_clicked(self):
"""
Test Tor Settings button clicked. With the given settings, see if we can
successfully connect and authenticate to Tor.
"""
self.common.log("TorSettingsDialog", "test_tor_clicked")
settings = self.settings_from_fields()
if not settings:
return
onion = Onion(
self.common,
use_tmp_dir=True,
get_tor_paths=self.common.gui.get_tor_paths,
)
tor_con = TorConnectionDialog(self.common, settings, True, onion)
tor_con.start()
# If Tor settings worked, show results
if onion.connected_to_tor:
Alert(
self.common,
strings._("settings_test_success").format(
onion.tor_version,
onion.supports_ephemeral,
onion.supports_stealth,
onion.supports_v3_onions,
),
title=strings._("gui_settings_connection_type_test_button"),
)
# Clean up
onion.cleanup()
def save_clicked(self):
"""
Save button clicked. Save current settings to disk.
"""
self.common.log("TorSettingsDialog", "save_clicked")
def changed(s1, s2, keys):
"""
Compare the Settings objects s1 and s2 and return true if any values
have changed for the given keys.
"""
for key in keys:
if s1.get(key) != s2.get(key):
return True
return False
settings = self.settings_from_fields()
if settings:
# Save the new settings
settings.save()
# If Tor isn't connected, or if Tor settings have changed, Reinitialize
# the Onion object
reboot_onion = False
if not self.common.gui.local_only:
if self.common.gui.onion.is_authenticated():
self.common.log(
"TorSettingsDialog", "save_clicked", "Connected to Tor"
)
if changed(
settings,
self.old_settings,
[
"connection_type",
"control_port_address",
"control_port_port",
"socks_address",
"socks_port",
"socket_file_path",
"auth_type",
"auth_password",
"no_bridges",
"tor_bridges_use_obfs4",
"tor_bridges_use_meek_lite_azure",
"tor_bridges_use_custom_bridges",
],
):
reboot_onion = True
else:
self.common.log(
"TorSettingsDialog", "save_clicked", "Not connected to Tor"
)
# Tor isn't connected, so try connecting
reboot_onion = True
# Do we need to reinitialize Tor?
if reboot_onion:
# Reinitialize the Onion object
self.common.log(
"TorSettingsDialog", "save_clicked", "rebooting the Onion"
)
self.common.gui.onion.cleanup()
tor_con = TorConnectionDialog(self.common, settings)
tor_con.start()
self.common.log(
"TorSettingsDialog",
"save_clicked",
f"Onion done rebooting, connected to Tor: {self.common.gui.onion.connected_to_tor}",
)
if (
self.common.gui.onion.is_authenticated()
and not tor_con.wasCanceled()
):
self.settings_saved.emit()
self.close()
else:
self.settings_saved.emit()
self.close()
else:
self.settings_saved.emit()
self.close()
def cancel_clicked(self):
"""
Cancel button clicked.
"""
self.common.log("TorSettingsDialog", "cancel_clicked")
if (
not self.common.gui.local_only
and not self.common.gui.onion.is_authenticated()
):
Alert(
self.common,
strings._("gui_tor_connection_canceled"),
QtWidgets.QMessageBox.Warning,
)
sys.exit()
else:
self.close()
def settings_from_fields(self):
"""
Return a Settings object that's full of values from the settings dialog.
"""
self.common.log("TorSettingsDialog", "settings_from_fields")
settings = Settings(self.common)
settings.load() # To get the last update timestamp
# Tor connection
if self.connection_type_bundled_radio.isChecked():
settings.set("connection_type", "bundled")
if self.connection_type_automatic_radio.isChecked():
settings.set("connection_type", "automatic")
if self.connection_type_control_port_radio.isChecked():
settings.set("connection_type", "control_port")
if self.connection_type_socket_file_radio.isChecked():
settings.set("connection_type", "socket_file")
settings.set(
"control_port_address",
self.connection_type_control_port_extras_address.text(),
)
settings.set(
"control_port_port", self.connection_type_control_port_extras_port.text()
)
settings.set(
"socket_file_path", self.connection_type_socket_file_extras_path.text()
)
settings.set("socks_address", self.connection_type_socks_address.text())
settings.set("socks_port", self.connection_type_socks_port.text())
if self.authenticate_no_auth_checkbox.checkState() == QtCore.Qt.Checked:
settings.set("auth_type", "no_auth")
else:
settings.set("auth_type", "password")
settings.set("auth_password", self.authenticate_password_extras_password.text())
# Whether we use bridges
if self.bridge_use_checkbox.checkState() == QtCore.Qt.Checked:
settings.set("no_bridges", False)
if self.bridge_builtin_radio.isChecked():
selection = self.bridge_builtin_dropdown.currentText()
if selection == "obfs4":
settings.set("tor_bridges_use_obfs4", True)
settings.set("tor_bridges_use_meek_lite_azure", False)
settings.set("tor_bridges_use_snowflake", False)
elif selection == "meek-azure":
settings.set("tor_bridges_use_obfs4", False)
settings.set("tor_bridges_use_meek_lite_azure", True)
settings.set("tor_bridges_use_snowflake", False)
elif selection == "snowflake":
settings.set("tor_bridges_use_obfs4", False)
settings.set("tor_bridges_use_meek_lite_azure", False)
settings.set("tor_bridges_use_snowflake", True)
settings.set("tor_bridges_use_moat", False)
settings.set("tor_bridges_use_custom_bridges", "")
if self.bridge_moat_radio.isChecked():
settings.set("tor_bridges_use_obfs4", False)
settings.set("tor_bridges_use_meek_lite_azure", False)
settings.set("tor_bridges_use_snowflake", False)
settings.set("tor_bridges_use_moat", True)
moat_bridges = self.bridge_moat_textbox.toPlainText()
if moat_bridges.strip() == "":
Alert(self.common, strings._("gui_settings_moat_bridges_invalid"))
return False
settings.set(
"tor_bridges_use_moat_bridges",
moat_bridges,
)
settings.set("tor_bridges_use_custom_bridges", "")
if self.bridge_custom_radio.isChecked():
settings.set("tor_bridges_use_obfs4", False)
settings.set("tor_bridges_use_meek_lite_azure", False)
settings.set("tor_bridges_use_snowflake", False)
settings.set("tor_bridges_use_moat", False)
new_bridges = []
bridges = self.bridge_custom_textbox.toPlainText().split("\n")
bridges_valid = False
for bridge in bridges:
if bridge != "":
# Check the syntax of the custom bridge to make sure it looks legitimate
ipv4_pattern = re.compile(
"(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$"
)
ipv6_pattern = re.compile(
"(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$"
)
meek_lite_pattern = re.compile(
"(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)"
)
snowflake_pattern = re.compile(
"(snowflake)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)"
)
if (
ipv4_pattern.match(bridge)
or ipv6_pattern.match(bridge)
or meek_lite_pattern.match(bridge)
or snowflake_pattern.match(bridge)
):
new_bridges.append(bridge)
bridges_valid = True
if bridges_valid:
new_bridges = "\n".join(new_bridges) + "\n"
settings.set("tor_bridges_use_custom_bridges", new_bridges)
else:
Alert(self.common, strings._("gui_settings_tor_bridges_invalid"))
return False
else:
settings.set("no_bridges", True)
return settings
def closeEvent(self, e):
self.common.log("TorSettingsDialog", "closeEvent")
# On close, if Tor isn't connected, then quit OnionShare altogether
if not self.common.gui.local_only:
if not self.common.gui.onion.is_authenticated():
self.common.log(
"TorSettingsDialog",
"closeEvent",
"Closing while not connected to Tor",
)
# Wait 1ms for the event loop to finish, then quit
QtCore.QTimer.singleShot(1, self.common.gui.qtapp.quit)

View File

@ -37,6 +37,7 @@ class Alert(QtWidgets.QMessageBox):
icon=QtWidgets.QMessageBox.NoIcon, icon=QtWidgets.QMessageBox.NoIcon,
buttons=QtWidgets.QMessageBox.Ok, buttons=QtWidgets.QMessageBox.Ok,
autostart=True, autostart=True,
title="OnionShare",
): ):
super(Alert, self).__init__(None) super(Alert, self).__init__(None)
@ -44,7 +45,7 @@ class Alert(QtWidgets.QMessageBox):
self.common.log("Alert", "__init__") self.common.log("Alert", "__init__")
self.setWindowTitle("OnionShare") self.setWindowTitle(title)
self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png"))) self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png")))
self.setText(message) self.setText(message)
self.setIcon(icon) self.setIcon(icon)