From 169be518eb9140cfdffa611afa82eb5592cf9873 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 13 Apr 2017 22:22:34 -0700 Subject: [PATCH] Connecting to Tor in bundled mode now creates a temporary tor data dir, starts a new tor process, and connects to it. Also, refactored Settings dialog to allow Linux to use bundled tor as well --- onionshare/onion.py | 86 ++++++++++++++++++++++++++----- onionshare_gui/settings_dialog.py | 29 +++-------- share/locale/en.json | 3 +- share/torrc_template | 9 ++++ 4 files changed, 91 insertions(+), 36 deletions(-) create mode 100644 share/torrc_template diff --git a/onionshare/onion.py b/onionshare/onion.py index ccfdd4b0..10181536 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -21,7 +21,7 @@ along with this program. If not, see . from stem.control import Controller from stem import ProtocolError from stem.connection import MissingPassword, UnreadableCookieFile, AuthenticationFailure -import os, sys, tempfile, shutil, urllib, platform +import os, sys, tempfile, shutil, urllib, platform, subprocess, time, shlex from . import socks from . import helpers, strings @@ -103,6 +103,8 @@ class Onion(object): self.stealth = stealth self.service_id = None + system = platform.system() + # Either use settings that are passed in, or load them from disk if settings: self.settings = settings @@ -110,21 +112,73 @@ class Onion(object): self.settings = Settings() self.settings.load() + # Is bundled tor supported? + if (system == 'Windows' or system == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False): + bundle_tor_supported = False + else: + bundle_tor_supported = True + + # Set the path of the tor binary, for bundled tor + if system == 'Linux': + self.tor_path = '/usr/bin/tor' + self.tor_geo_ip_file_path = '/usr/share/tor/geoip' + self.tor_geo_ipv6_file_path = '/usr/share/tor/geoip6' + elif system == 'Windows': + # TODO: implement + pass + elif system == 'Darwin': + # TODO: implement + pass + + # The tor process + self.tor_p = None + # Try to connect to Tor self.c = None if self.settings.get('connection_type') == 'bundled': - dev_mode = getattr(sys, 'onionshare_dev_mode', False) - p = platform.system() - - if (p != 'Windows' and p != 'Darwin') or dev_mode: + if not bundle_tor_supported: raise BundledTorNotSupported(strings._('settings_error_bundled_tor_not_supported')) - # TODO: actually implement bundled Tor + # Create a torrc for this session + self.tor_data_directory = tempfile.TemporaryDirectory() + self.tor_control_socket = os.path.join(self.tor_data_directory.name, 'control_socket') + self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie') + self.tor_socks_port_file = os.path.join(self.tor_data_directory.name, 'socks_socket') + self.tor_socks_port = 'unix:{}'.format(self.tor_socks_port_file) + self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc') + torrc_template = open(helpers.get_resource_path('torrc_template')).read() + torrc_template = torrc_template.replace('{{data_directory}}', self.tor_data_directory.name) + torrc_template = torrc_template.replace('{{control_socket}}', self.tor_control_socket) + torrc_template = torrc_template.replace('{{cookie_auth_file}}', self.tor_cookie_auth_file) + torrc_template = torrc_template.replace('{{geo_ip_file}}', self.tor_geo_ip_file_path) + torrc_template = torrc_template.replace('{{geo_ipv6_file}}', self.tor_geo_ipv6_file_path) + torrc_template = torrc_template.replace('{{socks_port}}', self.tor_socks_port) + open(self.tor_torrc, 'w').write(torrc_template) - if self.settings.get('connection_type') == 'automatic': + # Open tor in a subprocess, wait for the controller to start + self.tor_proc = subprocess.Popen([self.tor_path, '-f', self.tor_torrc], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + time.sleep(0.2) + + # Connect to the controller + self.c = Controller.from_socket_file(path=self.tor_control_socket) + self.c.authenticate() + + while True: + res = self.c.get_info("status/bootstrap-phase") + res_parts = shlex.split(res) + progress = res_parts[2].split('=')[1] + summary = res_parts[4].split('=')[1] + + # "\033[K" clears the rest of the line + print("{}: {}% - {}{}".format(strings._('connecting_to_tor'), progress, summary, "\033[K"), end="\r") + if summary == 'Done': + print("") + break + time.sleep(0.2) + + elif self.settings.get('connection_type') == 'automatic': # Automatically try to guess the right way to connect to Tor Browser - p = platform.system() # Try connecting to control port found_tor = False @@ -152,7 +206,7 @@ class Onion(object): socket_file_path = '' if not found_tor: try: - if p == 'Darwin': + if system == 'Darwin': socket_file_path = os.path.expanduser('~/Library/Application Support/TorBrowser-Data/Tor/control.socket') self.c = Controller.from_socket_file(path=socket_file_path) @@ -164,12 +218,12 @@ class Onion(object): # guessing the socket file name next if not found_tor: try: - if p == 'Linux': + if system == 'Linux': socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid()) - elif p == 'Darwin': + elif system == 'Darwin': # TODO: figure out the unix socket path in OS X socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid()) - elif p == 'Windows': + elif system == 'Windows': # Windows doesn't support unix sockets raise TorErrorAutomatic(strings._('settings_error_automatic')) @@ -276,12 +330,16 @@ class Onion(object): def cleanup(self): """ - Stop onion services that were created earlier. + Stop onion services that were created earlier. If there's a tor subprocess running, kill it. """ - # cleanup the ephemeral onion service + # Cleanup the ephemeral onion service if self.service_id: try: self.c.remove_ephemeral_hidden_service(self.service_id) except: pass self.service_id = None + + # Stop tor process + if self.tor_proc: + self.tor_proc.terminate() diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 13c62552..6cb6cdff 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -25,7 +25,6 @@ from onionshare.settings import Settings from onionshare.onion import * from .alert import Alert -from .tor_dialog import TorDialog class SettingsDialog(QtWidgets.QDialog): """ @@ -74,16 +73,10 @@ class SettingsDialog(QtWidgets.QDialog): self.connection_type_bundled_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_bundled_option', True)) self.connection_type_bundled_radio.toggled.connect(self.connection_type_bundled_toggled) - # Bundled Tor only works in Windows and Mac + # Bundled Tor doesn't work on dev mode in Windows or Mac p = platform.system() - if (p == 'Windows' or p == 'Darwin'): - # Bundled Tor doesn't work on dev mode - if getattr(sys, 'onionshare_dev_mode', False): - self.connection_type_bundled_radio.setEnabled(False) - else: - # If not using Windows or Mac, disable and hide bundled Tor + if (p == 'Windows' or p == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False): self.connection_type_bundled_radio.setEnabled(False) - self.connection_type_bundled_radio.hide() # Automatic self.connection_type_automatic_radio = QtWidgets.QRadioButton(strings._('gui_settings_connection_type_automatic_option', True)) @@ -285,20 +278,14 @@ class SettingsDialog(QtWidgets.QDialog): """ settings = self.settings_from_fields() - # If using bundled Tor, first connect to Tor - if settings.get('connection_type') == 'bundled': - tor_dialog = TorDialog() - tor_dialog.start() + try: + onion = Onion(settings=settings) - else: - try: - onion = Onion(settings=settings) + # If an exception hasn't been raised yet, the Tor settings work + Alert(strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth)) - # If an exception hasn't been raised yet, the Tor settings work - Alert(strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth)) - - except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported) as e: - Alert(e.args[0], QtWidgets.QMessageBox.Warning) + except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported) as e: + Alert(e.args[0], QtWidgets.QMessageBox.Warning) def save_clicked(self): """ diff --git a/share/locale/en.json b/share/locale/en.json index c0db438b..e50f93e6 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -90,5 +90,6 @@ "error_tor_protocol_error": "Error talking to the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.", "gui_tor_window_title": "Tor Connection", "gui_tor_button_close": "Close", - "gui_tor_button_restart": "Restart Tor" + "gui_tor_button_restart": "Restart Tor", + "connecting_to_tor": "Connecting to the Tor network" } diff --git a/share/torrc_template b/share/torrc_template new file mode 100644 index 00000000..464adf32 --- /dev/null +++ b/share/torrc_template @@ -0,0 +1,9 @@ +DataDirectory {{data_directory}} +SocksPort {{socks_port}} +ControlSocket {{control_socket}} +CookieAuthentication 1 +CookieAuthFile {{cookie_auth_file}} +AvoidDiskWrites 1 +Log notice stdout +GeoIPFile {{geo_ip_file}} +GeoIPv6File {{geo_ipv6_file}}