mirror of
https://github.com/onionshare/onionshare.git
synced 2024-12-25 07:19:41 -05:00
Refactor to CensorshipCircumvention and Meek classes. Use Meek domain fronting when requesting bridges in frontend
This commit is contained in:
parent
bcf697574e
commit
5b4d77c363
@ -28,6 +28,7 @@ from datetime import timedelta
|
||||
|
||||
from .common import Common, CannotFindTor
|
||||
from .censorship import CensorshipCircumvention
|
||||
from .meek import Meek, MeekNotRunning
|
||||
from .web import Web
|
||||
from .onion import TorErrorProtocolError, TorTooOldEphemeral, TorTooOldStealth, Onion
|
||||
from .onionshare import OnionShare
|
||||
@ -284,6 +285,18 @@ def main(cwd=None):
|
||||
# Create the Web object
|
||||
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)
|
||||
|
||||
# Start the Onion object
|
||||
try:
|
||||
onion = Onion(common, use_tmp_dir=True)
|
||||
|
@ -18,77 +18,30 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
import requests
|
||||
import subprocess
|
||||
|
||||
from .meek import MeekNotRunning
|
||||
|
||||
|
||||
class CensorshipCircumvention:
|
||||
class CensorshipCircumvention(object):
|
||||
"""
|
||||
The CensorShipCircumvention object contains methods to detect
|
||||
and offer solutions to censorship when connecting to Tor.
|
||||
Connect to the Tor Moat APIs to retrieve censorship
|
||||
circumvention recommendations, over the Meek client.
|
||||
"""
|
||||
|
||||
def __init__(self, common):
|
||||
|
||||
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__")
|
||||
|
||||
get_tor_paths = self.common.get_tor_paths
|
||||
(
|
||||
self.tor_path,
|
||||
self.tor_geo_ip_file_path,
|
||||
self.tor_geo_ipv6_file_path,
|
||||
self.obfs4proxy_file_path,
|
||||
self.meek_client_file_path,
|
||||
) = get_tor_paths()
|
||||
# Bail out if we requested domain fronting but we can't use meek
|
||||
if domain_fronting and not self.meek.meek_proxies:
|
||||
raise MeekNotRunning()
|
||||
|
||||
meek_url = "https://moat.torproject.org.global.prod.fastly.net/"
|
||||
meek_front = "cdn.sstatic.net"
|
||||
meek_env = {
|
||||
"TOR_PT_MANAGED_TRANSPORT_VER": "1",
|
||||
"TOR_PT_CLIENT_TRANSPORTS": "meek",
|
||||
}
|
||||
|
||||
# @TODO detect the port from the subprocess output
|
||||
meek_address = "127.0.0.1"
|
||||
meek_port = "43533" # hardcoded for testing
|
||||
self.meek_proxies = {
|
||||
"http": f"socks5h://{meek_address}:{meek_port}",
|
||||
"https": f"socks5h://{meek_address}:{meek_port}",
|
||||
}
|
||||
|
||||
# Start the Meek Client as a subprocess.
|
||||
# This will be used to do domain fronting to the Tor
|
||||
# Moat API endpoints for censorship circumvention as
|
||||
# well as BridgeDB lookups.
|
||||
|
||||
if self.common.platform == "Windows":
|
||||
# In Windows, hide console window when opening tor.exe subprocess
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
self.meek_proc = subprocess.Popen(
|
||||
[self.meek_client_file_path, "--url", meek_url, "--front", meek_front],
|
||||
stdout=subprocess.PIPE,
|
||||
startupinfo=startupinfo,
|
||||
bufsize=1,
|
||||
env=meek_env,
|
||||
text=True,
|
||||
)
|
||||
else:
|
||||
self.meek_proc = subprocess.Popen(
|
||||
[self.meek_client_file_path, "--url", meek_url, "--front", meek_front],
|
||||
stdout=subprocess.PIPE,
|
||||
bufsize=1,
|
||||
env=meek_env,
|
||||
text=True,
|
||||
)
|
||||
|
||||
# if "CMETHOD meek socks5" in line:
|
||||
# self.meek_host = (line.split(" ")[3].split(":")[0])
|
||||
# self.meek_port = (line.split(" ")[3].split(":")[1])
|
||||
# self.common.log("CensorshipCircumvention", "__init__", f"Meek host is {self.meek_host}")
|
||||
# self.common.log("CensorshipCircumvention", "__init__", f"Meek port is {self.meek_port}")
|
||||
|
||||
def censorship_obtain_map(self, country=False):
|
||||
def request_map(self, country=False):
|
||||
"""
|
||||
Retrieves the Circumvention map from Tor Project and store it
|
||||
locally for further look-ups if required.
|
||||
@ -108,7 +61,7 @@ class CensorshipCircumvention:
|
||||
endpoint,
|
||||
json=data,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.meek_proxies,
|
||||
proxies=self.meek.meek_proxies,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
self.common.log(
|
||||
@ -130,7 +83,7 @@ class CensorshipCircumvention:
|
||||
|
||||
return result
|
||||
|
||||
def censorship_obtain_settings(self, country=False, transports=False):
|
||||
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
|
||||
@ -152,7 +105,7 @@ class CensorshipCircumvention:
|
||||
endpoint,
|
||||
json=data,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.meek_proxies,
|
||||
proxies=self.meek.meek_proxies,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
self.common.log(
|
||||
@ -185,7 +138,7 @@ class CensorshipCircumvention:
|
||||
|
||||
return result
|
||||
|
||||
def censorship_obtain_builtin_bridges(self):
|
||||
def request_builtin_bridges(self):
|
||||
"""
|
||||
Retrieves the list of built-in bridges from the Tor Project.
|
||||
"""
|
||||
@ -193,7 +146,7 @@ class CensorshipCircumvention:
|
||||
r = requests.post(
|
||||
endpoint,
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.meek_proxies,
|
||||
proxies=self.meek.meek_proxies,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
self.common.log(
|
||||
|
144
cli/onionshare_cli/meek.py
Normal file
144
cli/onionshare_cli/meek.py
Normal file
@ -0,0 +1,144 @@
|
||||
# -*- 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 subprocess
|
||||
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):
|
||||
"""
|
||||
Set up the Meek object
|
||||
"""
|
||||
|
||||
self.common = common
|
||||
self.common.log("Meek", "__init__")
|
||||
|
||||
get_tor_paths = self.common.get_tor_paths
|
||||
(
|
||||
self.tor_path,
|
||||
self.tor_geo_ip_file_path,
|
||||
self.tor_geo_ipv6_file_path,
|
||||
self.obfs4proxy_file_path,
|
||||
self.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()
|
||||
|
||||
# Start the Meek Client as a subprocess.
|
||||
|
||||
if self.common.platform == "Windows":
|
||||
# In Windows, hide console window when opening tor.exe subprocess
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
self.meek_proc = subprocess.Popen(
|
||||
[
|
||||
self.meek_client_file_path,
|
||||
"--url",
|
||||
self.meek_url,
|
||||
"--front",
|
||||
self.meek_front,
|
||||
],
|
||||
stdout=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,
|
||||
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()
|
||||
|
||||
|
||||
class MeekNotRunning(Exception):
|
||||
"""
|
||||
We were unable to start Meek or obtain the port
|
||||
number it started on, in order to do domain fronting.
|
||||
"""
|
@ -409,11 +409,13 @@ class GuiCommon:
|
||||
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")
|
||||
meek_client_file_path = os.path.join(base_path, "meek-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")
|
||||
meek_client_file_path = shutil.which("meek-client")
|
||||
prefix = os.path.dirname(os.path.dirname(tor_path))
|
||||
tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip")
|
||||
tor_geo_ipv6_file_path = os.path.join(prefix, "share/tor/geoip6")
|
||||
@ -423,6 +425,7 @@ class GuiCommon:
|
||||
tor_path = os.path.join(base_path, "Tor", "tor.exe")
|
||||
obfs4proxy_file_path = os.path.join(base_path, "Tor", "obfs4proxy.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_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6")
|
||||
elif self.common.platform == "Darwin":
|
||||
@ -430,6 +433,7 @@ class GuiCommon:
|
||||
tor_path = os.path.join(base_path, "tor")
|
||||
obfs4proxy_file_path = os.path.join(base_path, "obfs4proxy")
|
||||
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_ipv6_file_path = os.path.join(base_path, "geoip6")
|
||||
elif self.common.platform == "BSD":
|
||||
@ -437,6 +441,7 @@ class GuiCommon:
|
||||
tor_geo_ip_file_path = "/usr/local/share/tor/geoip"
|
||||
tor_geo_ipv6_file_path = "/usr/local/share/tor/geoip6"
|
||||
obfs4proxy_file_path = "/usr/local/bin/obfs4proxy"
|
||||
meek_client_file_path = "/usr/local/bin/meek-client"
|
||||
snowflake_file_path = "/usr/local/bin/snowflake-client"
|
||||
|
||||
return (
|
||||
@ -445,6 +450,7 @@ class GuiCommon:
|
||||
tor_geo_ipv6_file_path,
|
||||
obfs4proxy_file_path,
|
||||
snowflake_file_path,
|
||||
meek_client_file_path,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -34,13 +34,15 @@ class MoatDialog(QtWidgets.QDialog):
|
||||
|
||||
got_bridges = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, common):
|
||||
def __init__(self, common, meek):
|
||||
super(MoatDialog, self).__init__()
|
||||
|
||||
self.common = common
|
||||
|
||||
self.common.log("MoatDialog", "__init__")
|
||||
|
||||
self.meek = meek
|
||||
|
||||
self.setModal(True)
|
||||
self.setWindowTitle(strings._("gui_settings_bridge_moat_button"))
|
||||
self.setWindowIcon(QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png")))
|
||||
@ -108,7 +110,7 @@ class MoatDialog(QtWidgets.QDialog):
|
||||
self.submit_button.hide()
|
||||
|
||||
# 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.captcha_ready.connect(self.captcha_ready)
|
||||
self.t_fetch.start()
|
||||
@ -130,6 +132,7 @@ class MoatDialog(QtWidgets.QDialog):
|
||||
# BridgeDB check
|
||||
self.t_check = MoatThread(
|
||||
self.common,
|
||||
self.meek,
|
||||
"check",
|
||||
{"challenge": self.challenge, "solution": self.solution_lineedit.text()},
|
||||
)
|
||||
@ -209,17 +212,20 @@ class MoatThread(QtCore.QThread):
|
||||
captcha_ready = QtCore.Signal(str, str)
|
||||
bridges_ready = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, common, action, data={}):
|
||||
def __init__(self, common, meek, action, data={}):
|
||||
super(MoatThread, self).__init__()
|
||||
self.common = common
|
||||
self.common.log("MoatThread", "__init__", f"action={action}")
|
||||
|
||||
self.meek = meek
|
||||
self.transport = "obfs4"
|
||||
self.action = action
|
||||
self.data = data
|
||||
|
||||
def run(self):
|
||||
# TODO: Do all of this using domain fronting
|
||||
|
||||
# Start Meek so that we can do domain fronting
|
||||
self.meek.start()
|
||||
|
||||
if self.action == "fetch":
|
||||
self.common.log("MoatThread", "run", f"starting fetch")
|
||||
@ -228,6 +234,7 @@ class MoatThread(QtCore.QThread):
|
||||
r = requests.post(
|
||||
"https://bridges.torproject.org/moat/fetch",
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.meek.meek_proxies,
|
||||
json={
|
||||
"data": [
|
||||
{
|
||||
@ -280,6 +287,7 @@ class MoatThread(QtCore.QThread):
|
||||
r = requests.post(
|
||||
"https://bridges.torproject.org/moat/check",
|
||||
headers={"Content-Type": "application/vnd.api+json"},
|
||||
proxies=self.meek.meek_proxies,
|
||||
json={
|
||||
"data": [
|
||||
{
|
||||
|
@ -24,6 +24,7 @@ import platform
|
||||
import re
|
||||
import os
|
||||
|
||||
from onionshare_cli.meek import Meek
|
||||
from onionshare_cli.settings import Settings
|
||||
from onionshare_cli.onion import Onion
|
||||
|
||||
@ -48,6 +49,8 @@ class TorSettingsDialog(QtWidgets.QDialog):
|
||||
|
||||
self.common.log("TorSettingsDialog", "__init__")
|
||||
|
||||
self.meek = Meek(common)
|
||||
|
||||
self.setModal(True)
|
||||
self.setWindowTitle(strings._("gui_tor_settings_window_title"))
|
||||
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.obfs4proxy_file_path,
|
||||
self.snowflake_file_path,
|
||||
self.meek_client_file_path,
|
||||
) = self.common.gui.get_tor_paths()
|
||||
|
||||
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")
|
||||
|
||||
moat_dialog = MoatDialog(self.common)
|
||||
moat_dialog = MoatDialog(self.common, self.meek)
|
||||
moat_dialog.got_bridges.connect(self.bridge_moat_got_bridges)
|
||||
moat_dialog.exec_()
|
||||
|
||||
@ -577,9 +581,7 @@ class TorSettingsDialog(QtWidgets.QDialog):
|
||||
return
|
||||
|
||||
onion = Onion(
|
||||
self.common,
|
||||
use_tmp_dir=True,
|
||||
get_tor_paths=self.common.gui.get_tor_paths,
|
||||
self.common, use_tmp_dir=True, get_tor_paths=self.common.gui.get_tor_paths
|
||||
)
|
||||
|
||||
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"))
|
||||
return False
|
||||
|
||||
settings.set(
|
||||
"tor_bridges_use_moat_bridges",
|
||||
moat_bridges,
|
||||
)
|
||||
settings.set("tor_bridges_use_moat_bridges", moat_bridges)
|
||||
|
||||
settings.set("tor_bridges_use_custom_bridges", "")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user