diff --git a/onionshare/onion.py b/onionshare/onion.py index 1e8d45e3..ed685dc0 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -417,6 +417,11 @@ class Onion(object): self.tor_proc.kill() self.tor_proc = None + # Reset other Onion settings + self.connected_to_tor = False + self.stealth = False + self.service_id = None + def get_tor_socks_port(self): """ Returns a (address, port) tuple for the Tor SOCKS port diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 6345911d..a2f02145 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -59,12 +59,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.settings = Settings() self.settings.load() - # Start the "Connecting to Tor" dialog, which calls onion.connect() - tor_con = TorConnectionDialog(self.qtapp, self.settings, self.onion) - tor_con.canceled.connect(self._tor_connection_canceled) - tor_con.open_settings.connect(self._tor_connection_open_settings) - tor_con.start() - # Check for updates in a new thread, if enabled system = platform.system() if system == 'Windows' or system == 'Darwin': @@ -151,6 +145,12 @@ class OnionShareGui(QtWidgets.QMainWindow): # The server isn't active yet self.set_server_active(False) + # Start the "Connecting to Tor" dialog, which calls onion.connect() + tor_con = TorConnectionDialog(self.qtapp, self.settings, self.onion) + tor_con.canceled.connect(self._tor_connection_canceled) + tor_con.open_settings.connect(self._tor_connection_open_settings) + tor_con.start() + def _tor_connection_canceled(self): """ If the user cancels before Tor finishes connecting, ask if they want to @@ -158,22 +158,29 @@ class OnionShareGui(QtWidgets.QMainWindow): """ common.log('OnionShareGui', '_tor_connection_canceled') - def quit_settings_dialog(): - a = Alert("Would you like to open OnionShare settings to troubleshoot connecting to Tor?", QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) - settings_button = QtWidgets.QPushButton("Open Settings") - quit_button = QtWidgets.QPushButton("Quit") + def ask(): + a = Alert(strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) + settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings', True)) + quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit', True)) a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole) a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole) a.setDefaultButton(settings_button) a.exec_() if a.clickedButton() == settings_button: + # Open settings + common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked') self.open_settings() - else: - self.qtapp.quit() - # Wait 1ms for the event loop to finish closing the TorConnectionDialog - QtCore.QTimer.singleShot(1, quit_settings_dialog) + if a.clickedButton() == quit_button: + # Quit + common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked') + + # Wait 1ms for the event loop to finish, then quit + QtCore.QTimer.singleShot(1, self.qtapp.quit) + + # Wait 100ms before asking + QtCore.QTimer.singleShot(100, ask) def _tor_connection_open_settings(self): """ diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index fa2d6bf4..71b5bfad 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -26,6 +26,7 @@ from onionshare.onion import * from .alert import Alert from .update_checker import * +from .tor_connection_dialog import TorConnectionDialog class SettingsDialog(QtWidgets.QDialog): """ @@ -229,31 +230,31 @@ class SettingsDialog(QtWidgets.QDialog): self.cancel_button.setFocus() # Load settings, and fill them in - settings = Settings() - settings.load() + self.old_settings = Settings() + self.old_settings.load() - close_after_first_download = settings.get('close_after_first_download') + close_after_first_download = self.old_settings.get('close_after_first_download') if close_after_first_download: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Checked) else: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked) - use_stealth = settings.get('use_stealth') + use_stealth = self.old_settings.get('use_stealth') if use_stealth: self.stealth_checkbox.setCheckState(QtCore.Qt.Checked) else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) - use_autoupdate = settings.get('use_autoupdate') + use_autoupdate = self.old_settings.get('use_autoupdate') if use_autoupdate: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Checked) else: self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) - autoupdate_timestamp = settings.get('autoupdate_timestamp') + autoupdate_timestamp = self.old_settings.get('autoupdate_timestamp') self._update_autoupdate_timestamp(autoupdate_timestamp) - connection_type = settings.get('connection_type') + 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) @@ -266,17 +267,17 @@ class SettingsDialog(QtWidgets.QDialog): 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(settings.get('control_port_address')) - 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')) - self.connection_type_socks_address.setText(settings.get('socks_address')) - self.connection_type_socks_port.setText(str(settings.get('socks_port'))) - auth_type = settings.get('auth_type') + 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(settings.get('auth_password')) + self.authenticate_password_extras_password.setText(self.old_settings.get('auth_password')) # Show the dialog self.exec_() @@ -421,7 +422,48 @@ class SettingsDialog(QtWidgets.QDialog): settings = self.settings_from_fields() settings.save() - self.close() + + # If Tor isn't connected, or if Tor settings have changed, Reinitialize + # the Onion object + reboot_onion = False + if self.onion.connected_to_tor: + 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 + + 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']): + + reboot_onion = True + + else: + # Tor isn't connected, so try connecting + reboot_onion = True + + # Do we need to reinitialize Tor? + if reboot_onion: + # Reinitialize the Onion object + common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') + self.onion.cleanup() + + tor_con = TorConnectionDialog(self.qtapp, settings, self.onion) + tor_con.start() + + common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) + + if self.onion.connected_to_tor and not tor_con.wasCanceled(): + self.close() + + else: + self.close() def cancel_clicked(self): """ @@ -466,6 +508,16 @@ class SettingsDialog(QtWidgets.QDialog): return settings + def closeEvent(self, e): + common.log('SettingsDialog', 'closeEvent') + + # On close, if Tor isn't connected, then quit OnionShare altogether + if not self.onion.connected_to_tor: + 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.qtapp.quit) + def _update_autoupdate_timestamp(self, autoupdate_timestamp): common.log('SettingsDialog', '_update_autoupdate_timestamp') diff --git a/onionshare_gui/tor_connection_dialog.py b/onionshare_gui/tor_connection_dialog.py index c0efd172..462593b2 100644 --- a/onionshare_gui/tor_connection_dialog.py +++ b/onionshare_gui/tor_connection_dialog.py @@ -38,7 +38,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): self.settings = settings self.onion = onion - self.setWindowTitle("OnionShare") + self.setWindowTitle("OnionShare TOR") self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) self.setModal(True) self.setFixedSize(400, 150) @@ -88,20 +88,23 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): self.active = False # Cancel connecting to Tor - self.cancel() + QtCore.QTimer.singleShot(1, self.cancel) def _error_connecting_to_tor(self, msg): common.log('TorConnectionDialog', '_error_connecting_to_tor') self.active = False + def alert_and_open_settings(): + # Display the exception in an alert box + Alert("{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings', True)), QtWidgets.QMessageBox.Warning) + + # Open settings + self.open_settings.emit() + + QtCore.QTimer.singleShot(1, alert_and_open_settings) + # Cancel connecting to Tor - self.cancel() - - # Display the exception in an alert box - Alert("{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings', True)), QtWidgets.QMessageBox.Warning) - - # Open settings - self.open_settings.emit() + QtCore.QTimer.singleShot(1, self.cancel) class TorConnectionThread(QtCore.QThread): tor_status_update = QtCore.pyqtSignal(str, str) @@ -126,11 +129,11 @@ class TorConnectionThread(QtCore.QThread): if self.onion.connected_to_tor: self.connected_to_tor.emit() else: - self.error_connecting_to_tor.emit(strings._('settings_error_unknown_error', True)) + self.canceled_connecting_to_tor.emit() except BundledTorCanceled as e: common.log('TorConnectionThread', 'run', 'caught exception: BundledTorCanceled') - self.canceled_connecting_to_tor.emit(strings._('settings_error_bundled_tor_canceled')) + self.canceled_connecting_to_tor.emit() except Exception as e: common.log('TorConnectionThread', 'run', 'caught exception: {}'.format(e.args[0])) diff --git a/share/locale/en.json b/share/locale/en.json index 3cf17548..9a9a0e22 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -52,9 +52,6 @@ "zip_progress_bar_format": "Crunching files: %p%", "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requires at least at least Tor 0.2.7.1 and at least python3-stem 1.4.0.", - "gui_menu_file_menu": "&File", - "gui_menu_settings_action": "&Settings", - "gui_menu_quit_action": "&Quit", "gui_settings_window_title": "Settings", "gui_settings_stealth_label": "Stealth (advanced)", "gui_settings_stealth_option": "Create stealth onion services", @@ -94,7 +91,6 @@ "settings_error_bundled_tor_not_supported": "Bundled Tor is not supported when not using developer mode in Windows or macOS.", "settings_error_bundled_tor_timeout": "Connecting to Tor is taking too long. Maybe your computer is offline, or your clock isn't accurate.", "settings_error_bundled_tor_canceled": "The Tor process closed before it could finish connecting.", - "settings_error_unknown_error": "Failed connecting to Tor for an unknown reasons.", "settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}", "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.", "connecting_to_tor": "Connecting to the Tor network", @@ -102,5 +98,8 @@ "update_error_check_error": "Error checking for updates: Maybe you're not connected to Tor, or maybe the OnionShare website is down.", "update_error_invalid_latest_version": "Error checking for updates: The OnionShare website responded saying the latest version is '{}', but that doesn't appear to be a valid version string.", "update_not_available": "You are running the latest version of OnionShare.", + "gui_tor_connection_ask": "Would you like to open OnionShare settings to troubleshoot connecting to Tor?", + "gui_tor_connection_ask_open_settings": "Open Settings", + "gui_tor_connection_ask_quit": "Quit", "gui_tor_connection_error_settings": "Try adjusting how OnionShare connects to the Tor network in Settings." }