mirror of
https://github.com/onionshare/onionshare.git
synced 2024-12-25 15:29:42 -05:00
Merge pull request #1446 from mig5/censorship_tor_api_endpoints
Censorship tor api endpoints and Meek client support for domain fronting
This commit is contained in:
commit
10147b6c6b
@ -27,13 +27,10 @@ from datetime import datetime
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from .common import Common, CannotFindTor
|
from .common import Common, CannotFindTor
|
||||||
|
from .censorship import CensorshipCircumvention
|
||||||
|
from .meek import Meek, MeekNotRunning
|
||||||
from .web import Web
|
from .web import Web
|
||||||
from .onion import (
|
from .onion import TorErrorProtocolError, TorTooOldEphemeral, TorTooOldStealth, Onion
|
||||||
TorErrorProtocolError,
|
|
||||||
TorTooOldEphemeral,
|
|
||||||
TorTooOldStealth,
|
|
||||||
Onion,
|
|
||||||
)
|
|
||||||
from .onionshare import OnionShare
|
from .onionshare import OnionShare
|
||||||
from .mode_settings import ModeSettings
|
from .mode_settings import ModeSettings
|
||||||
|
|
||||||
@ -94,12 +91,7 @@ def main(cwd=None):
|
|||||||
help="Filename of persistent session",
|
help="Filename of persistent session",
|
||||||
)
|
)
|
||||||
# General args
|
# General args
|
||||||
parser.add_argument(
|
parser.add_argument("--title", metavar="TITLE", default=None, help="Set a title")
|
||||||
"--title",
|
|
||||||
metavar="TITLE",
|
|
||||||
default=None,
|
|
||||||
help="Set a title",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--public",
|
"--public",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@ -293,6 +285,20 @@ def main(cwd=None):
|
|||||||
# Create the Web object
|
# Create the Web object
|
||||||
web = Web(common, False, mode_settings, mode)
|
web = Web(common, False, mode_settings, mode)
|
||||||
|
|
||||||
|
# Create the Meek object and start the meek client
|
||||||
|
# meek = Meek(common)
|
||||||
|
# meek.start()
|
||||||
|
|
||||||
|
# Create the CensorshipCircumvention object to make
|
||||||
|
# API calls to Tor over Meek
|
||||||
|
censorship = CensorshipCircumvention(common, meek)
|
||||||
|
# Example: request recommended bridges, pretending to be from China, using
|
||||||
|
# domain fronting.
|
||||||
|
# censorship_recommended_settings = censorship.request_settings(country="cn")
|
||||||
|
# print(censorship_recommended_settings)
|
||||||
|
# Clean up the meek subprocess once we're done working with the censorship circumvention API
|
||||||
|
# meek.cleanup()
|
||||||
|
|
||||||
# Start the Onion object
|
# Start the Onion object
|
||||||
try:
|
try:
|
||||||
onion = Onion(common, use_tmp_dir=True)
|
onion = Onion(common, use_tmp_dir=True)
|
||||||
@ -409,7 +415,7 @@ def main(cwd=None):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Warn about sending large files over Tor
|
# Warn about sending large files over Tor
|
||||||
if web.share_mode.download_filesize >= 157286400: # 150mb
|
if web.share_mode.download_filesize >= 157_286_400: # 150mb
|
||||||
print("")
|
print("")
|
||||||
print("Warning: Sending a large share could take hours")
|
print("Warning: Sending a large share could take hours")
|
||||||
print("")
|
print("")
|
||||||
|
169
cli/onionshare_cli/censorship.py
Normal file
169
cli/onionshare_cli/censorship.py
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
|
Copyright (C) 2014-2021 Micah Lee, et al. <micah@micahflee.com>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from .meek import MeekNotRunning
|
||||||
|
|
||||||
|
|
||||||
|
class CensorshipCircumvention(object):
|
||||||
|
"""
|
||||||
|
Connect to the Tor Moat APIs to retrieve censorship
|
||||||
|
circumvention recommendations, over the Meek client.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, common, meek, domain_fronting=True):
|
||||||
|
"""
|
||||||
|
Set up the CensorshipCircumvention object to hold
|
||||||
|
common and meek objects.
|
||||||
|
"""
|
||||||
|
self.common = common
|
||||||
|
self.meek = meek
|
||||||
|
self.common.log("CensorshipCircumvention", "__init__")
|
||||||
|
|
||||||
|
# Bail out if we requested domain fronting but we can't use meek
|
||||||
|
if domain_fronting and not self.meek.meek_proxies:
|
||||||
|
raise MeekNotRunning()
|
||||||
|
|
||||||
|
def request_map(self, country=False):
|
||||||
|
"""
|
||||||
|
Retrieves the Circumvention map from Tor Project and store it
|
||||||
|
locally for further look-ups if required.
|
||||||
|
|
||||||
|
Optionally pass a country code in order to get recommended settings
|
||||||
|
just for that country.
|
||||||
|
|
||||||
|
Note that this API endpoint doesn't return actual bridges,
|
||||||
|
it just returns the recommended bridge type countries.
|
||||||
|
"""
|
||||||
|
endpoint = "https://bridges.torproject.org/moat/circumvention/map"
|
||||||
|
data = {}
|
||||||
|
if country:
|
||||||
|
data = {"country": country}
|
||||||
|
|
||||||
|
r = requests.post(
|
||||||
|
endpoint,
|
||||||
|
json=data,
|
||||||
|
headers={"Content-Type": "application/vnd.api+json"},
|
||||||
|
proxies=self.meek.meek_proxies,
|
||||||
|
)
|
||||||
|
if r.status_code != 200:
|
||||||
|
self.common.log(
|
||||||
|
"CensorshipCircumvention",
|
||||||
|
"censorship_obtain_map",
|
||||||
|
f"status_code={r.status_code}",
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
result = r.json()
|
||||||
|
|
||||||
|
if "errors" in result:
|
||||||
|
self.common.log(
|
||||||
|
"CensorshipCircumvention",
|
||||||
|
"censorship_obtain_map",
|
||||||
|
f"errors={result['errors']}",
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def request_settings(self, country=False, transports=False):
|
||||||
|
"""
|
||||||
|
Retrieves the Circumvention Settings from Tor Project, which
|
||||||
|
will return recommended settings based on the country code of
|
||||||
|
the requesting IP.
|
||||||
|
|
||||||
|
Optionally, a country code can be specified in order to override
|
||||||
|
the IP detection.
|
||||||
|
|
||||||
|
Optionally, a list of transports can be specified in order to
|
||||||
|
return recommended settings for just that transport type.
|
||||||
|
"""
|
||||||
|
endpoint = "https://bridges.torproject.org/moat/circumvention/settings"
|
||||||
|
data = {}
|
||||||
|
if country:
|
||||||
|
data = {"country": country}
|
||||||
|
if transports:
|
||||||
|
data.append({"transports": transports})
|
||||||
|
r = requests.post(
|
||||||
|
endpoint,
|
||||||
|
json=data,
|
||||||
|
headers={"Content-Type": "application/vnd.api+json"},
|
||||||
|
proxies=self.meek.meek_proxies,
|
||||||
|
)
|
||||||
|
if r.status_code != 200:
|
||||||
|
self.common.log(
|
||||||
|
"CensorshipCircumvention",
|
||||||
|
"censorship_obtain_settings",
|
||||||
|
f"status_code={r.status_code}",
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
result = r.json()
|
||||||
|
|
||||||
|
if "errors" in result:
|
||||||
|
self.common.log(
|
||||||
|
"CensorshipCircumvention",
|
||||||
|
"censorship_obtain_settings",
|
||||||
|
f"errors={result['errors']}",
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# There are no settings - perhaps this country doesn't require censorship circumvention?
|
||||||
|
# This is not really an error, so we can just check if False and assume direct Tor
|
||||||
|
# connection will work.
|
||||||
|
if not "settings" in result:
|
||||||
|
self.common.log(
|
||||||
|
"CensorshipCircumvention",
|
||||||
|
"censorship_obtain_settings",
|
||||||
|
"No settings found for this country",
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def request_builtin_bridges(self):
|
||||||
|
"""
|
||||||
|
Retrieves the list of built-in bridges from the Tor Project.
|
||||||
|
"""
|
||||||
|
endpoint = "https://bridges.torproject.org/moat/circumvention/builtin"
|
||||||
|
r = requests.post(
|
||||||
|
endpoint,
|
||||||
|
headers={"Content-Type": "application/vnd.api+json"},
|
||||||
|
proxies=self.meek.meek_proxies,
|
||||||
|
)
|
||||||
|
if r.status_code != 200:
|
||||||
|
self.common.log(
|
||||||
|
"CensorshipCircumvention",
|
||||||
|
"censorship_obtain_builtin_bridges",
|
||||||
|
f"status_code={r.status_code}",
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
result = r.json()
|
||||||
|
|
||||||
|
if "errors" in result:
|
||||||
|
self.common.log(
|
||||||
|
"CensorshipCircumvention",
|
||||||
|
"censorship_obtain_builtin_bridges",
|
||||||
|
f"errors={result['errors']}",
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return result
|
@ -22,6 +22,7 @@ import hashlib
|
|||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import random
|
import random
|
||||||
|
import requests
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
@ -320,6 +321,7 @@ class Common:
|
|||||||
tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6")
|
tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6")
|
||||||
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")
|
snowflake_file_path = os.path.join(base_path, "snowflake-client")
|
||||||
|
meek_client_file_path = os.path.join(base_path, "meek-client")
|
||||||
else:
|
else:
|
||||||
# Fallback to looking in the path
|
# Fallback to looking in the path
|
||||||
self.log(
|
self.log(
|
||||||
@ -330,6 +332,7 @@ class Common:
|
|||||||
raise CannotFindTor()
|
raise CannotFindTor()
|
||||||
obfs4proxy_file_path = shutil.which("obfs4proxy")
|
obfs4proxy_file_path = shutil.which("obfs4proxy")
|
||||||
snowflake_file_path = shutil.which("snowflake-client")
|
snowflake_file_path = shutil.which("snowflake-client")
|
||||||
|
meek_client_file_path = shutil.which("meek-client")
|
||||||
prefix = os.path.dirname(os.path.dirname(tor_path))
|
prefix = os.path.dirname(os.path.dirname(tor_path))
|
||||||
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
|
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
|
||||||
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
|
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
|
||||||
@ -338,6 +341,7 @@ class Common:
|
|||||||
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")
|
snowflake_file_path = os.path.join(base_path, "Tor", "snowflake-client.exe")
|
||||||
|
meek_client_file_path = os.path.join(base_path, "Tor", "meek-client.exe")
|
||||||
tor_geo_ip_file_path = os.path.join(base_path, "Data", "Tor", "geoip")
|
tor_geo_ip_file_path = os.path.join(base_path, "Data", "Tor", "geoip")
|
||||||
tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6")
|
tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6")
|
||||||
elif self.platform == "Darwin":
|
elif self.platform == "Darwin":
|
||||||
@ -348,6 +352,7 @@ class Common:
|
|||||||
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")
|
||||||
obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy")
|
obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy")
|
||||||
|
meek_client_file_path = os.path.join(base_path, "meek-client")
|
||||||
snowflake_file_path = os.path.join(base_path, "snowflake-client")
|
snowflake_file_path = os.path.join(base_path, "snowflake-client")
|
||||||
else:
|
else:
|
||||||
# Fallback to looking in the path
|
# Fallback to looking in the path
|
||||||
@ -356,6 +361,7 @@ class Common:
|
|||||||
raise CannotFindTor()
|
raise CannotFindTor()
|
||||||
obfs4proxy_file_path = shutil.which("obfs4proxy")
|
obfs4proxy_file_path = shutil.which("obfs4proxy")
|
||||||
snowflake_file_path = shutil.which("snowflake-client")
|
snowflake_file_path = shutil.which("snowflake-client")
|
||||||
|
meek_client_file_path = shutil.which("meek-client")
|
||||||
prefix = os.path.dirname(os.path.dirname(tor_path))
|
prefix = os.path.dirname(os.path.dirname(tor_path))
|
||||||
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
|
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
|
||||||
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
|
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
|
||||||
@ -365,6 +371,7 @@ class Common:
|
|||||||
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"
|
snowflake_file_path = "/usr/local/bin/snowflake-client"
|
||||||
|
meek_client_file_path = "/usr/local/bin/meek-client"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
tor_path,
|
tor_path,
|
||||||
@ -372,6 +379,7 @@ class Common:
|
|||||||
tor_geo_ipv6_file_path,
|
tor_geo_ipv6_file_path,
|
||||||
obfs4proxy_file_path,
|
obfs4proxy_file_path,
|
||||||
snowflake_file_path,
|
snowflake_file_path,
|
||||||
|
meek_client_file_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
def build_data_dir(self):
|
def build_data_dir(self):
|
||||||
|
197
cli/onionshare_cli/meek.py
Normal file
197
cli/onionshare_cli/meek.py
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
|
Copyright (C) 2014-2021 Micah Lee, et al. <micah@micahflee.com>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from queue import Queue, Empty
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
|
||||||
|
class Meek(object):
|
||||||
|
"""
|
||||||
|
The Meek object starts the meek-client as a subprocess.
|
||||||
|
This process is used to do domain-fronting to connect to
|
||||||
|
the Tor APIs for censorship circumvention and retrieving
|
||||||
|
bridges, before connecting to Tor.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, common, get_tor_paths=None):
|
||||||
|
"""
|
||||||
|
Set up the Meek object
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.common = common
|
||||||
|
self.common.log("Meek", "__init__")
|
||||||
|
|
||||||
|
# Set the path of the meek binary
|
||||||
|
if not get_tor_paths:
|
||||||
|
get_tor_paths = self.common.get_tor_paths
|
||||||
|
(
|
||||||
|
self.tor_path,
|
||||||
|
self.tor_geo_ip_file_path,
|
||||||
|
self.tor_geo_ipv6_file_path,
|
||||||
|
self.obfs4proxy_file_path,
|
||||||
|
self.snowflake_file_path,
|
||||||
|
self.meek_client_file_path,
|
||||||
|
) = get_tor_paths()
|
||||||
|
|
||||||
|
self.meek_proxies = {}
|
||||||
|
self.meek_url = "https://moat.torproject.org.global.prod.fastly.net/"
|
||||||
|
self.meek_front = "cdn.sstatic.net"
|
||||||
|
self.meek_env = {
|
||||||
|
"TOR_PT_MANAGED_TRANSPORT_VER": "1",
|
||||||
|
"TOR_PT_CLIENT_TRANSPORTS": "meek",
|
||||||
|
}
|
||||||
|
self.meek_host = "127.0.0.1"
|
||||||
|
self.meek_port = None
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""
|
||||||
|
Start the Meek Client and populate the SOCKS proxies dict
|
||||||
|
for use with requests to the Tor Moat API.
|
||||||
|
"""
|
||||||
|
# Small method to read stdout from the subprocess.
|
||||||
|
# We use this to obtain the random port that Meek
|
||||||
|
# started on
|
||||||
|
def enqueue_output(out, queue):
|
||||||
|
for line in iter(out.readline, b""):
|
||||||
|
queue.put(line)
|
||||||
|
out.close()
|
||||||
|
|
||||||
|
# Abort early if we can't find the Meek client
|
||||||
|
if self.meek_client_file_path is None or not os.path.exists(
|
||||||
|
self.meek_client_file_path
|
||||||
|
):
|
||||||
|
raise MeekNotFound()
|
||||||
|
|
||||||
|
# Start the Meek Client as a subprocess.
|
||||||
|
self.common.log("Meek", "start", "Starting meek client")
|
||||||
|
|
||||||
|
if self.common.platform == "Windows":
|
||||||
|
# In Windows, hide console window when opening meek-client.exe subprocess
|
||||||
|
startupinfo = subprocess.STARTUPINFO()
|
||||||
|
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||||
|
self.meek_proc = subprocess.Popen(
|
||||||
|
[
|
||||||
|
self.meek_client_file_path,
|
||||||
|
"--url",
|
||||||
|
self.meek_url,
|
||||||
|
"--front",
|
||||||
|
self.meek_front,
|
||||||
|
],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
startupinfo=startupinfo,
|
||||||
|
bufsize=1,
|
||||||
|
env=self.meek_env,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.meek_proc = subprocess.Popen(
|
||||||
|
[
|
||||||
|
self.meek_client_file_path,
|
||||||
|
"--url",
|
||||||
|
self.meek_url,
|
||||||
|
"--front",
|
||||||
|
self.meek_front,
|
||||||
|
],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
bufsize=1,
|
||||||
|
env=self.meek_env,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Queue up the stdout from the subprocess for polling later
|
||||||
|
q = Queue()
|
||||||
|
t = Thread(target=enqueue_output, args=(self.meek_proc.stdout, q))
|
||||||
|
t.daemon = True # thread dies with the program
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# read stdout without blocking
|
||||||
|
try:
|
||||||
|
line = q.get_nowait()
|
||||||
|
except Empty:
|
||||||
|
# no stdout yet?
|
||||||
|
pass
|
||||||
|
else: # we got stdout
|
||||||
|
if "CMETHOD meek socks5" in line:
|
||||||
|
self.meek_host = line.split(" ")[3].split(":")[0]
|
||||||
|
self.meek_port = line.split(" ")[3].split(":")[1]
|
||||||
|
self.common.log("Meek", "start", f"Meek host is {self.meek_host}")
|
||||||
|
self.common.log("Meek", "start", f"Meek port is {self.meek_port}")
|
||||||
|
break
|
||||||
|
|
||||||
|
if self.meek_port:
|
||||||
|
self.meek_proxies = {
|
||||||
|
"http": f"socks5h://{self.meek_host}:{self.meek_port}",
|
||||||
|
"https": f"socks5h://{self.meek_host}:{self.meek_port}",
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
self.common.log("Meek", "start", "Could not obtain the meek port")
|
||||||
|
raise MeekNotRunning()
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
"""
|
||||||
|
Kill any meek subprocesses.
|
||||||
|
"""
|
||||||
|
self.common.log("Meek", "cleanup")
|
||||||
|
|
||||||
|
if self.meek_proc:
|
||||||
|
self.meek_proc.terminate()
|
||||||
|
time.sleep(0.2)
|
||||||
|
if self.meek_proc.poll() is None:
|
||||||
|
self.common.log(
|
||||||
|
"Meek",
|
||||||
|
"cleanup",
|
||||||
|
"Tried to terminate meek-client process but it's still running",
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
self.meek_proc.kill()
|
||||||
|
time.sleep(0.2)
|
||||||
|
if self.meek_proc.poll() is None:
|
||||||
|
self.common.log(
|
||||||
|
"Meek",
|
||||||
|
"cleanup",
|
||||||
|
"Tried to kill meek-client process but it's still running",
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
self.common.log(
|
||||||
|
"Meek", "cleanup", "Exception while killing meek-client process"
|
||||||
|
)
|
||||||
|
self.meek_proc = None
|
||||||
|
|
||||||
|
# Reset other Meek settings
|
||||||
|
self.meek_proxies = {}
|
||||||
|
self.meek_port = None
|
||||||
|
|
||||||
|
|
||||||
|
class MeekNotRunning(Exception):
|
||||||
|
"""
|
||||||
|
We were unable to start Meek or obtain the port
|
||||||
|
number it started on, in order to do domain fronting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class MeekNotFound(Exception):
|
||||||
|
"""
|
||||||
|
We were unable to find the Meek Client binary.
|
||||||
|
"""
|
@ -154,6 +154,7 @@ class Onion(object):
|
|||||||
self.tor_geo_ipv6_file_path,
|
self.tor_geo_ipv6_file_path,
|
||||||
self.obfs4proxy_file_path,
|
self.obfs4proxy_file_path,
|
||||||
self.snowflake_file_path,
|
self.snowflake_file_path,
|
||||||
|
self.meek_client_file_path,
|
||||||
) = get_tor_paths()
|
) = get_tor_paths()
|
||||||
|
|
||||||
# The tor process
|
# The tor process
|
||||||
|
@ -162,6 +162,9 @@ 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")
|
||||||
|
meek_client_file_path = os.path.join(
|
||||||
|
base_path, "Resources", "Tor", "meek-client"
|
||||||
|
)
|
||||||
snowflake_file_path = os.path.join(
|
snowflake_file_path = os.path.join(
|
||||||
base_path, "Resources", "Tor", "snowflake-client"
|
base_path, "Resources", "Tor", "snowflake-client"
|
||||||
)
|
)
|
||||||
@ -171,6 +174,7 @@ class TestGetTorPaths:
|
|||||||
tor_geo_ipv6_file_path,
|
tor_geo_ipv6_file_path,
|
||||||
obfs4proxy_file_path,
|
obfs4proxy_file_path,
|
||||||
snowflake_file_path,
|
snowflake_file_path,
|
||||||
|
meek_client_file_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.platform != "linux", reason="requires Linux")
|
@pytest.mark.skipif(sys.platform != "linux", reason="requires Linux")
|
||||||
@ -181,6 +185,7 @@ class TestGetTorPaths:
|
|||||||
tor_geo_ipv6_file_path,
|
tor_geo_ipv6_file_path,
|
||||||
_, # obfs4proxy is optional
|
_, # obfs4proxy is optional
|
||||||
_, # snowflake-client is optional
|
_, # snowflake-client is optional
|
||||||
|
_, # meek-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"
|
||||||
@ -207,6 +212,9 @@ class TestGetTorPaths:
|
|||||||
snowflake_file_path = os.path.join(
|
snowflake_file_path = os.path.join(
|
||||||
os.path.join(base_path, "Tor"), "snowflake-client.exe"
|
os.path.join(base_path, "Tor"), "snowflake-client.exe"
|
||||||
)
|
)
|
||||||
|
meek_client_file_path = os.path.join(
|
||||||
|
os.path.join(base_path, "Tor"), "meek-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"
|
||||||
)
|
)
|
||||||
@ -219,6 +227,7 @@ class TestGetTorPaths:
|
|||||||
tor_geo_ipv6_file_path,
|
tor_geo_ipv6_file_path,
|
||||||
obfs4proxy_file_path,
|
obfs4proxy_file_path,
|
||||||
snowflake_file_path,
|
snowflake_file_path,
|
||||||
|
meek_client_file_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -409,11 +409,13 @@ class GuiCommon:
|
|||||||
tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6")
|
tor_geo_ipv6_file_path = os.path.join(base_path, "geoip6")
|
||||||
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")
|
snowflake_file_path = os.path.join(base_path, "snowflake-client")
|
||||||
|
meek_client_file_path = os.path.join(base_path, "meek-client")
|
||||||
else:
|
else:
|
||||||
# Fallback to looking in the path
|
# Fallback to looking in the path
|
||||||
tor_path = shutil.which("tor")
|
tor_path = shutil.which("tor")
|
||||||
obfs4proxy_file_path = shutil.which("obfs4proxy")
|
obfs4proxy_file_path = shutil.which("obfs4proxy")
|
||||||
snowflake_file_path = shutil.which("snowflake-client")
|
snowflake_file_path = shutil.which("snowflake-client")
|
||||||
|
meek_client_file_path = shutil.which("meek-client")
|
||||||
prefix = os.path.dirname(os.path.dirname(tor_path))
|
prefix = os.path.dirname(os.path.dirname(tor_path))
|
||||||
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
|
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
|
||||||
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
|
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
|
||||||
@ -423,6 +425,7 @@ class GuiCommon:
|
|||||||
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")
|
snowflake_file_path = os.path.join(base_path, "Tor", "snowflake-client.exe")
|
||||||
|
meek_client_file_path = os.path.join(base_path, "Tor", "meek-client.exe")
|
||||||
tor_geo_ip_file_path = os.path.join(base_path, "Data", "Tor", "geoip")
|
tor_geo_ip_file_path = os.path.join(base_path, "Data", "Tor", "geoip")
|
||||||
tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6")
|
tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6")
|
||||||
elif self.common.platform == "Darwin":
|
elif self.common.platform == "Darwin":
|
||||||
@ -430,6 +433,7 @@ class GuiCommon:
|
|||||||
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")
|
snowflake_file_path = os.path.join(base_path, "snowflake-client")
|
||||||
|
meek_client_file_path = os.path.join(base_path, "meek-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,6 +441,7 @@ 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"
|
||||||
|
meek_client_file_path = "/usr/local/bin/meek-client"
|
||||||
snowflake_file_path = "/usr/local/bin/snowflake-client"
|
snowflake_file_path = "/usr/local/bin/snowflake-client"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -445,6 +450,7 @@ class GuiCommon:
|
|||||||
tor_geo_ipv6_file_path,
|
tor_geo_ipv6_file_path,
|
||||||
obfs4proxy_file_path,
|
obfs4proxy_file_path,
|
||||||
snowflake_file_path,
|
snowflake_file_path,
|
||||||
|
meek_client_file_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -26,6 +26,7 @@ import json
|
|||||||
|
|
||||||
from . import strings
|
from . import strings
|
||||||
from .gui_common import GuiCommon
|
from .gui_common import GuiCommon
|
||||||
|
from onionshare_cli.meek import MeekNotFound
|
||||||
|
|
||||||
|
|
||||||
class MoatDialog(QtWidgets.QDialog):
|
class MoatDialog(QtWidgets.QDialog):
|
||||||
@ -35,13 +36,15 @@ class MoatDialog(QtWidgets.QDialog):
|
|||||||
|
|
||||||
got_bridges = QtCore.Signal(str)
|
got_bridges = QtCore.Signal(str)
|
||||||
|
|
||||||
def __init__(self, common):
|
def __init__(self, common, meek):
|
||||||
super(MoatDialog, self).__init__()
|
super(MoatDialog, self).__init__()
|
||||||
|
|
||||||
self.common = common
|
self.common = common
|
||||||
|
|
||||||
self.common.log("MoatDialog", "__init__")
|
self.common.log("MoatDialog", "__init__")
|
||||||
|
|
||||||
|
self.meek = meek
|
||||||
|
|
||||||
self.setModal(True)
|
self.setModal(True)
|
||||||
self.setWindowTitle(strings._("gui_settings_bridge_moat_button"))
|
self.setWindowTitle(strings._("gui_settings_bridge_moat_button"))
|
||||||
self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png")))
|
self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png")))
|
||||||
@ -111,7 +114,7 @@ class MoatDialog(QtWidgets.QDialog):
|
|||||||
self.submit_button.hide()
|
self.submit_button.hide()
|
||||||
|
|
||||||
# BridgeDB fetch
|
# BridgeDB fetch
|
||||||
self.t_fetch = MoatThread(self.common, "fetch")
|
self.t_fetch = MoatThread(self.common, self.meek, "fetch")
|
||||||
self.t_fetch.bridgedb_error.connect(self.bridgedb_error)
|
self.t_fetch.bridgedb_error.connect(self.bridgedb_error)
|
||||||
self.t_fetch.captcha_ready.connect(self.captcha_ready)
|
self.t_fetch.captcha_ready.connect(self.captcha_ready)
|
||||||
self.t_fetch.start()
|
self.t_fetch.start()
|
||||||
@ -133,6 +136,7 @@ class MoatDialog(QtWidgets.QDialog):
|
|||||||
# BridgeDB check
|
# BridgeDB check
|
||||||
self.t_check = MoatThread(
|
self.t_check = MoatThread(
|
||||||
self.common,
|
self.common,
|
||||||
|
self.meek,
|
||||||
"check",
|
"check",
|
||||||
{
|
{
|
||||||
"transport": self.transport,
|
"transport": self.transport,
|
||||||
@ -217,16 +221,34 @@ class MoatThread(QtCore.QThread):
|
|||||||
captcha_ready = QtCore.Signal(str, str, str)
|
captcha_ready = QtCore.Signal(str, str, str)
|
||||||
bridges_ready = QtCore.Signal(str)
|
bridges_ready = QtCore.Signal(str)
|
||||||
|
|
||||||
def __init__(self, common, action, data={}):
|
def __init__(self, common, meek, action, data={}):
|
||||||
super(MoatThread, self).__init__()
|
super(MoatThread, self).__init__()
|
||||||
self.common = common
|
self.common = common
|
||||||
self.common.log("MoatThread", "__init__", f"action={action}")
|
self.common.log("MoatThread", "__init__", f"action={action}")
|
||||||
|
|
||||||
|
self.meek = meek
|
||||||
|
self.transport = "obfs4"
|
||||||
self.action = action
|
self.action = action
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
# TODO: Do all of this using domain fronting
|
|
||||||
|
# Start Meek so that we can do domain fronting
|
||||||
|
try:
|
||||||
|
self.meek.start()
|
||||||
|
except MeekNotFound:
|
||||||
|
self.common.log("MoatThread", "run", f"Could not find the Meek Client")
|
||||||
|
self.bridgedb_error.emit()
|
||||||
|
return
|
||||||
|
|
||||||
|
# We should only fetch bridges if we can domain front,
|
||||||
|
# but we can override this in local-only mode.
|
||||||
|
if not self.meek.meek_proxies and not self.common.gui.local_only:
|
||||||
|
self.common.log(
|
||||||
|
"MoatThread", "run", f"Could not identify meek proxies to make request"
|
||||||
|
)
|
||||||
|
self.bridgedb_error.emit()
|
||||||
|
return
|
||||||
|
|
||||||
if self.action == "fetch":
|
if self.action == "fetch":
|
||||||
self.common.log("MoatThread", "run", f"starting fetch")
|
self.common.log("MoatThread", "run", f"starting fetch")
|
||||||
@ -235,19 +257,20 @@ class MoatThread(QtCore.QThread):
|
|||||||
r = requests.post(
|
r = requests.post(
|
||||||
"https://bridges.torproject.org/moat/fetch",
|
"https://bridges.torproject.org/moat/fetch",
|
||||||
headers={"Content-Type": "application/vnd.api+json"},
|
headers={"Content-Type": "application/vnd.api+json"},
|
||||||
|
proxies=self.meek.meek_proxies,
|
||||||
json={
|
json={
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"type": "client-transports",
|
"type": "client-transports",
|
||||||
"supported": [
|
"supported": ["obfs4", "snowflake"],
|
||||||
"obfs4",
|
|
||||||
"snowflake",
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.meek.cleanup()
|
||||||
|
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
self.common.log("MoatThread", "run", f"status_code={r.status_code}")
|
self.common.log("MoatThread", "run", f"status_code={r.status_code}")
|
||||||
self.bridgedb_error.emit()
|
self.bridgedb_error.emit()
|
||||||
@ -285,6 +308,7 @@ class MoatThread(QtCore.QThread):
|
|||||||
r = requests.post(
|
r = requests.post(
|
||||||
"https://bridges.torproject.org/moat/check",
|
"https://bridges.torproject.org/moat/check",
|
||||||
headers={"Content-Type": "application/vnd.api+json"},
|
headers={"Content-Type": "application/vnd.api+json"},
|
||||||
|
proxies=self.meek.meek_proxies,
|
||||||
json={
|
json={
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
@ -299,6 +323,9 @@ class MoatThread(QtCore.QThread):
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.meek.cleanup()
|
||||||
|
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
self.common.log("MoatThread", "run", f"status_code={r.status_code}")
|
self.common.log("MoatThread", "run", f"status_code={r.status_code}")
|
||||||
self.bridgedb_error.emit()
|
self.bridgedb_error.emit()
|
||||||
|
@ -24,6 +24,7 @@ import platform
|
|||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from onionshare_cli.meek import Meek
|
||||||
from onionshare_cli.settings import Settings
|
from onionshare_cli.settings import Settings
|
||||||
from onionshare_cli.onion import Onion
|
from onionshare_cli.onion import Onion
|
||||||
|
|
||||||
@ -48,6 +49,8 @@ class TorSettingsDialog(QtWidgets.QDialog):
|
|||||||
|
|
||||||
self.common.log("TorSettingsDialog", "__init__")
|
self.common.log("TorSettingsDialog", "__init__")
|
||||||
|
|
||||||
|
self.meek = Meek(common, get_tor_paths=self.common.gui.get_tor_paths)
|
||||||
|
|
||||||
self.setModal(True)
|
self.setModal(True)
|
||||||
self.setWindowTitle(strings._("gui_tor_settings_window_title"))
|
self.setWindowTitle(strings._("gui_tor_settings_window_title"))
|
||||||
self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png")))
|
self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png")))
|
||||||
@ -78,6 +81,7 @@ class TorSettingsDialog(QtWidgets.QDialog):
|
|||||||
self.tor_geo_ipv6_file_path,
|
self.tor_geo_ipv6_file_path,
|
||||||
self.obfs4proxy_file_path,
|
self.obfs4proxy_file_path,
|
||||||
self.snowflake_file_path,
|
self.snowflake_file_path,
|
||||||
|
self.meek_client_file_path,
|
||||||
) = self.common.gui.get_tor_paths()
|
) = self.common.gui.get_tor_paths()
|
||||||
|
|
||||||
bridges_label = QtWidgets.QLabel(strings._("gui_settings_tor_bridges_label"))
|
bridges_label = QtWidgets.QLabel(strings._("gui_settings_tor_bridges_label"))
|
||||||
@ -497,7 +501,7 @@ class TorSettingsDialog(QtWidgets.QDialog):
|
|||||||
"""
|
"""
|
||||||
self.common.log("TorSettingsDialog", "bridge_moat_button_clicked")
|
self.common.log("TorSettingsDialog", "bridge_moat_button_clicked")
|
||||||
|
|
||||||
moat_dialog = MoatDialog(self.common)
|
moat_dialog = MoatDialog(self.common, self.meek)
|
||||||
moat_dialog.got_bridges.connect(self.bridge_moat_got_bridges)
|
moat_dialog.got_bridges.connect(self.bridge_moat_got_bridges)
|
||||||
moat_dialog.exec_()
|
moat_dialog.exec_()
|
||||||
|
|
||||||
@ -577,9 +581,7 @@ class TorSettingsDialog(QtWidgets.QDialog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
onion = Onion(
|
onion = Onion(
|
||||||
self.common,
|
self.common, use_tmp_dir=True, get_tor_paths=self.common.gui.get_tor_paths
|
||||||
use_tmp_dir=True,
|
|
||||||
get_tor_paths=self.common.gui.get_tor_paths,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
tor_con = TorConnectionDialog(self.common, settings, True, onion)
|
tor_con = TorConnectionDialog(self.common, settings, True, onion)
|
||||||
@ -781,10 +783,7 @@ class TorSettingsDialog(QtWidgets.QDialog):
|
|||||||
Alert(self.common, strings._("gui_settings_moat_bridges_invalid"))
|
Alert(self.common, strings._("gui_settings_moat_bridges_invalid"))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
settings.set(
|
settings.set("tor_bridges_use_moat_bridges", moat_bridges)
|
||||||
"tor_bridges_use_moat_bridges",
|
|
||||||
moat_bridges,
|
|
||||||
)
|
|
||||||
|
|
||||||
settings.set("tor_bridges_use_custom_bridges", "")
|
settings.set("tor_bridges_use_custom_bridges", "")
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user