From 19d020f245d1f01c246f48bf1b97fcd78853dd95 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 29 Dec 2016 09:58:13 -0800 Subject: [PATCH] Onion now connects to the Tor controller using the settings in Settings (except automatic still needs some work), and the settings dialog handles error when testing settings --- onionshare/onion.py | 116 +++++++++++++++++++++++------- onionshare/settings.py | 5 +- onionshare_gui/settings_dialog.py | 50 ++++--------- resources/locale/en.json | 9 ++- 4 files changed, 113 insertions(+), 67 deletions(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index 22a95c19..df74ffde 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -27,6 +27,38 @@ from . import socks from . import helpers, strings from .settings import Settings +class TorErrorInvalidSetting(Exception): + """ + This exception is raised if the settings just don't make sense. + """ + pass + +class TorErrorSocketPort(Exception): + """ + OnionShare can't connect to the Tor controller using the supplied address and port. + """ + pass + +class TorErrorSocketFile(Exception): + """ + OnionShare can't connect to the Tor controller using the supplied socket file. + """ + pass + +class TorErrorMissingPassword(Exception): + """ + OnionShare connected to the Tor controller, but it requires a password. + """ + pass + +class TorErrorUnreadableCookieFile(Exception): + """ + OnionShare connected to the Tor controller, but your user does not have permission + to access the cookie file. + """ + pass + + class NoTor(Exception): """ This exception is raised if onionshare can't find a Tor control port @@ -72,34 +104,69 @@ class Onion(object): self.cleanup_filenames = [] self.service_id = None - # if the TOR_CONTROL_PORT environment variable is set, use that - # otherwise, default to Tor Browser, Tor Messenger, and system tor ports - env_port = os.environ.get('TOR_CONTROL_PORT') - if env_port: - ports = [int(env_port)] - else: - ports = [9151, 9153, 9051] - - # if the TOR_AUTHENTICATION_PASSWORD is set, use that to authenticate - password = os.environ.get('TOR_AUTHENTICATION_PASSWORD') - - # connect to the tor controlport - found_tor = False + # Try to connect to Tor self.c = None - for port in ports: + + if self.settings.get('connection_type') == 'automatic': + # Automatically try to guess the right way to connect to Tor Browser + + # if the TOR_CONTROL_PORT environment variable is set, use that + # otherwise, default to Tor Browser, Tor Messenger, and system tor ports + env_port = os.environ.get('TOR_CONTROL_PORT') + if env_port: + ports = [int(env_port)] + else: + ports = [9151, 9153, 9051] + + # connect to the tor controlport + found_tor = False + for port in ports: + try: + self.c = Controller.from_port(port=port) + self.c.authenticate() + found_tor = True + break + except SocketError: + pass + except MissingPassword: + raise NoTor(strings._("ctrlport_missing_password").format(str(ports))) + except UnreadableCookieFile: + raise NoTor(strings._("ctrlport_unreadable_cookie").format(str(ports))) + if not found_tor: + raise NoTor(strings._("cant_connect_ctrlport").format(str(ports))) + + else: + # Use specific settings to connect to tor + + # Try connecting try: - self.c = Controller.from_port(port=port) - self.c.authenticate(password) - found_tor = True - break + if self.settings.get('connection_type') == 'control_port': + self.c = Controller.from_port(address=self.settings.get('control_port_address'), port=self.settings.get('control_port_port')) + elif self.settings.get('connection_type') == 'socket_file': + self.c = Controller.from_socket_file(path=self.settings.get('socket_file_path')) + else: + raise TorErrorInvalidSetting(strings._("settings_error_unknown")) + except SocketError: - pass + if self.settings.get('connection_type') == 'control_port': + raise TorErrorSocketPort(strings._("settings_error_socket_port").format(self.settings.get('control_port_address'), self.settings.get('control_port_port'))) + else: + raise TorErrorSocketFile(strings._("settings_error_socket_file").format(self.settings.get('socket_file_path'))) + + # Try authenticating + try: + if self.settings.get('auth_type') == 'no_auth': + self.c.authenticate() + elif self.settings.get('auth_type') == 'password': + self.c.authenticate(self.settings.get('auth_password')) + else: + raise TorErrorInvalidSetting(strings._("settings_error_unknown")) + except MissingPassword: - raise NoTor(strings._("ctrlport_missing_password").format(str(ports))) + raise TorErrorMissingPassword(strings._('settings_error_missing_password')) except UnreadableCookieFile: - raise NoTor(strings._("ctrlport_unreadable_cookie").format(str(ports))) - if not found_tor: - raise NoTor(strings._("cant_connect_ctrlport").format(str(ports))) + raise TorErrorUnreadableCookieFile(strings._('settings_error_unreadable_cookie_file')) + # do the versions of stem and tor that I'm using support ephemeral onion services? tor_version = self.c.get_version().version_str @@ -107,8 +174,7 @@ class Onion(object): self.supports_ephemeral = callable(list_ephemeral_hidden_services) and tor_version >= '0.2.7.1' # do the versions of stem and tor that I'm using support stealth onion services? - if self.stealth: - self.check_for_stealth_support() + self.check_for_stealth_support() def check_for_stealth_support(self): try: diff --git a/onionshare/settings.py b/onionshare/settings.py index 1fa743b3..9414cab9 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -37,11 +37,10 @@ class Settings(object): 'version': helpers.get_version(), 'connection_type': 'automatic', 'control_port_address': '127.0.0.1', - 'control_port_port': '9051', + 'control_port_port': 9051, 'socket_file_path': '/var/run/tor/control', 'auth_type': 'no_auth', - 'auth_password': '', - 'auth_cookie_path': '/var/run/tor/control.authcookie' + 'auth_password': '' } def build_filename(self): diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 13db7270..6116d2a5 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -21,7 +21,9 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings from onionshare.settings import Settings -from onionshare.onion import Onion +from onionshare.onion import Onion, TorErrorInvalidSetting, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile + +from .alert import Alert class SettingsDialog(QtWidgets.QDialog): """ @@ -100,27 +102,11 @@ class SettingsDialog(QtWidgets.QDialog): self.authenticate_password_extras.setLayout(authenticate_password_extras_layout) self.authenticate_password_extras.hide() - # Cookie - self.authenticate_cookie_radio = QtWidgets.QRadioButton(strings._('gui_settings_authenticate_cookie_option', True)) - self.authenticate_cookie_radio.toggled.connect(self.authenticate_cookie_toggled) - - authenticate_cookie_extras_label = QtWidgets.QLabel(strings._('gui_settings_cookie_label', True)) - self.authenticate_cookie_extras_cookie_path = QtWidgets.QLineEdit() - authenticate_cookie_extras_layout = QtWidgets.QHBoxLayout() - authenticate_cookie_extras_layout.addWidget(authenticate_cookie_extras_label) - authenticate_cookie_extras_layout.addWidget(self.authenticate_cookie_extras_cookie_path) - - self.authenticate_cookie_extras = QtWidgets.QWidget() - self.authenticate_cookie_extras.setLayout(authenticate_cookie_extras_layout) - self.authenticate_cookie_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_cookie_radio) authenticate_group_layout.addWidget(self.authenticate_password_extras) - authenticate_group_layout.addWidget(self.authenticate_cookie_extras) self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label", True)) self.authenticate_group.setLayout(authenticate_group_layout) @@ -158,17 +144,14 @@ class SettingsDialog(QtWidgets.QDialog): elif connection_type == 'socket_file': self.connection_type_socket_file_radio.setChecked(True) self.connection_type_control_port_extras_address.setText(settings.get('control_port_address')) - self.connection_type_control_port_extras_port.setText(settings.get('control_port_port')) + self.connection_type_control_port_extras_port.setText(str(settings.get('control_port_port'))) self.connection_type_socket_file_extras_path.setText(settings.get('socket_file_path')) auth_type = 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) - elif auth_type == 'cookie': - self.authenticate_cookie_radio.setChecked(True) self.authenticate_password_extras_password.setText(settings.get('auth_password')) - self.authenticate_cookie_extras_cookie_path.setText(settings.get('auth_cookie_path')) # Show the dialog self.exec_() @@ -220,24 +203,20 @@ class SettingsDialog(QtWidgets.QDialog): else: self.authenticate_password_extras.hide() - def authenticate_cookie_toggled(self, checked): - """ - Authentication option cookie was toggled. If checked, show extra fields - for cookie auth. If unchecked, hide those extra fields. - """ - if checked: - self.authenticate_cookie_extras.show() - else: - self.authenticate_cookie_extras.hide() - def test_clicked(self): """ Test Settings button clicked. With the given settings, see if we can successfully connect and authenticate to Tor. """ - print("Testing settings") settings = self.settings_from_fields() - onion = Onion(settings=settings) + + try: + onion = Onion(settings=settings) + + # If an exception hasn't been raised yet, the Tor settings work + + except (TorErrorInvalidSetting, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile) as e: + Alert(e.args[0]) def save_clicked(self): """ @@ -267,17 +246,14 @@ class SettingsDialog(QtWidgets.QDialog): 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('control_port_port', int(self.connection_type_control_port_extras_port.text())) settings.set('socket_file_path', self.connection_type_socket_file_extras_path.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') - if self.authenticate_cookie_radio.isChecked(): - settings.set('auth_type', 'cookie') settings.set('auth_password', self.authenticate_password_extras_password.text()) - settings.set('auth_cookie_path', self.authenticate_cookie_extras_cookie_path.text()) return settings diff --git a/resources/locale/en.json b/resources/locale/en.json index 90e7d3ca..f48b90ed 100644 --- a/resources/locale/en.json +++ b/resources/locale/en.json @@ -70,7 +70,7 @@ "gui_settings_control_port_label": "Control port", "gui_settings_socket_file_label": "Socket file", "gui_settings_authenticate_label": "Tor authentication options", - "gui_settings_authenticate_no_auth_option": "No authentication", + "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", "gui_settings_authenticate_password_option": "Password", "gui_settings_authenticate_cookie_option": "Cookie", "gui_settings_password_label": "Password", @@ -78,5 +78,10 @@ "gui_settings_button_test": "Test Settings", "gui_settings_button_save": "Save", "gui_settings_button_cancel": "Cancel", - "settings_saved": "Settings saved to {}" + "settings_saved": "Settings saved to {}", + "settings_error_unknown": "Can't connect to Tor controller because the settings don't make sense.", + "settings_error_socket_port": "Can't connect to Tor controller on address {} with port {}.", + "settings_error_socket_file": "Can't connect to Tor controller using socket file {}.", + "settings_error_missing_password": "Connected to Tor controller, but it requires a password to authenticate.", + "settings_error_unreadable_cookie_file": "Connected to Tor controller, but can't authenticate because your password may be wrong, and your user doesn't have permission to read the cookie file." }