From 068b95f683f43420dacee9addd18d84fe6f3b11d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Nov 2020 12:25:09 -0800 Subject: [PATCH 1/2] All Tor-related that get thrown from the onionshare_cli.Onion object now get translated in the GUI --- cli/onionshare_cli/onion.py | 112 +++++++++--------- cli/onionshare_cli/onionshare.py | 1 - desktop/src/onionshare/gui_common.py | 51 +++++++- .../src/onionshare/resources/locale/en.json | 3 +- desktop/src/onionshare/settings_dialog.py | 36 +++++- desktop/src/onionshare/threads.py | 16 ++- .../src/onionshare/tor_connection_dialog.py | 41 ++++++- 7 files changed, 188 insertions(+), 72 deletions(-) diff --git a/cli/onionshare_cli/onion.py b/cli/onionshare_cli/onion.py index 1b025bf8..4cf360b5 100644 --- a/cli/onionshare_cli/onion.py +++ b/cli/onionshare_cli/onion.py @@ -35,8 +35,6 @@ from distutils.version import LooseVersion as Version from . import common from .settings import Settings -# TODO: Figure out how to localize this for the GUI - class TorErrorAutomatic(Exception): """ @@ -106,16 +104,20 @@ class TorErrorProtocolError(Exception): pass -class TorTooOld(Exception): +class TorTooOldEphemeral(Exception): """ - This exception is raised if onionshare needs to use a feature of Tor or stem - (like stealth ephemeral onion services) but the version you have installed - is too old. + This exception is raised if the version of tor doesn't support ephemeral onion services """ pass +class TorTooOldStealth(Exception): + """ + This exception is raised if the version of tor doesn't support stealth onion services + """ + + class BundledTorTimeout(Exception): """ This exception is raised if onionshare is set to use the bundled Tor binary, @@ -137,6 +139,12 @@ class BundledTorBroken(Exception): """ +class PortNotAvailable(Exception): + """ + There are no available ports for OnionShare to use, which really shouldn't ever happen + """ + + class Onion(object): """ Onion is an abstraction layer for connecting to the Tor control port and @@ -233,7 +241,8 @@ class Onion(object): try: self.tor_socks_port = self.common.get_available_port(1000, 65535) except: - raise OSError("OnionShare port not available") + print("OnionShare port not available") + raise PortNotAvailable() self.tor_torrc = os.path.join(self.tor_data_directory_name, "torrc") # If there is an existing OnionShare tor process, kill it @@ -265,7 +274,8 @@ class Onion(object): try: self.tor_control_port = self.common.get_available_port(1000, 65535) except: - raise OSError("OnionShare port not available") + print("OnionShare port not available") + raise PortNotAvailable() self.tor_control_socket = None else: # Linux and BSD can use unix sockets @@ -334,10 +344,6 @@ class Onion(object): f.write(self.settings.get("tor_bridges_use_custom_bridges")) f.write("\nUseBridges 1") - # Make sure the tor path is accurate - if not os.path.exists(self.tor_path): - raise BundledTorNotSupported(f"Cannot find tor binary: {self.tor_path}") - # Execute a tor subprocess start_ts = time.time() if self.common.platform == "Windows": @@ -372,10 +378,8 @@ class Onion(object): self.c = Controller.from_socket_file(path=self.tor_control_socket) self.c.authenticate() except Exception as e: - raise BundledTorBroken( - # strings._("settings_error_bundled_tor_broken").format(e.args[0]) - "OnionShare could not connect to Tor:\n{}".format(e.args[0]) - ) + print("OnionShare could not connect to Tor:\n{}".format(e.args[0])) + raise BundledTorBroken(e.args[0]) while True: try: @@ -422,15 +426,16 @@ class Onion(object): print("") try: self.tor_proc.terminate() - raise BundledTorTimeout( - # strings._("settings_error_bundled_tor_timeout") + print( "Taking too long to connect to Tor. Maybe you aren't connected to the Internet, or have an inaccurate system clock?" ) + raise BundledTorTimeout() except FileNotFoundError: pass elif self.settings.get("connection_type") == "automatic": # Automatically try to guess the right way to connect to Tor Browser + automatic_error = "Could not connect to the Tor controller. Is Tor Browser (available from torproject.org) running in the background?" # Try connecting to control port found_tor = False @@ -482,30 +487,25 @@ class Onion(object): ) elif self.common.platform == "Windows": # Windows doesn't support unix sockets - raise TorErrorAutomatic( - # strings._("settings_error_automatic") - "Could not connect to the Tor controller. Is Tor Browser (available from torproject.org) running in the background?" - ) + print(automatic_error) + raise TorErrorAutomatic() self.c = Controller.from_socket_file(path=socket_file_path) except: - raise TorErrorAutomatic( - # strings._("settings_error_automatic") - "Could not connect to the Tor controller. Is Tor Browser (available from torproject.org) running in the background?" - ) + print(automatic_error) + raise TorErrorAutomatic() # Try authenticating try: self.c.authenticate() except: - raise TorErrorAutomatic( - # strings._("settings_error_automatic") - "Could not connect to the Tor controller. Is Tor Browser (available from torproject.org) running in the background?" - ) + print(automatic_error) + raise TorErrorAutomatic() else: # Use specific settings to connect to tor + invalid_settings_error = "Can't connect to Tor controller because your settings don't make sense." # Try connecting try: @@ -519,27 +519,28 @@ class Onion(object): path=self.settings.get("socket_file_path") ) else: - raise TorErrorInvalidSetting( - # strings._("settings_error_unknown") - "Can't connect to Tor controller because your settings don't make sense." - ) + print(invalid_settings_error) + raise TorErrorInvalidSetting() except: if self.settings.get("connection_type") == "control_port": - raise TorErrorSocketPort( - # strings._("settings_error_socket_port") + print( "Can't connect to the Tor controller at {}:{}.".format( self.settings.get("control_port_address"), self.settings.get("control_port_port"), ) ) + raise TorErrorSocketPort( + self.settings.get("control_port_address"), + self.settings.get("control_port_port"), + ) else: - raise TorErrorSocketFile( - # strings._("settings_error_socket_file") + print( "Can't connect to the Tor controller using socket file {}.".format( self.settings.get("socket_file_path") ) ) + raise TorErrorSocketFile(self.settings.get("socket_file_path")) # Try authenticating try: @@ -548,29 +549,30 @@ class Onion(object): elif self.settings.get("auth_type") == "password": self.c.authenticate(self.settings.get("auth_password")) else: - raise TorErrorInvalidSetting( - # strings._("settings_error_unknown") - "Can't connect to Tor controller because your settings don't make sense." - ) + print(invalid_settings_error) + raise TorErrorInvalidSetting() except MissingPassword: - raise TorErrorMissingPassword( - # strings._("settings_error_missing_password") + print( "Connected to Tor controller, but it requires a password to authenticate." ) + raise TorErrorMissingPassword() except UnreadableCookieFile: - raise TorErrorUnreadableCookieFile( - # strings._("settings_error_unreadable_cookie_file") + print( "Connected to the Tor controller, but password may be wrong, or your user is not permitted to read the cookie file." ) + raise TorErrorUnreadableCookieFile() except AuthenticationFailure: - raise TorErrorAuthError( - # strings._("settings_error_auth") + print( "Connected to {}:{}, but can't authenticate. Maybe this isn't a Tor controller?".format( self.settings.get("control_port_address"), self.settings.get("control_port_port"), ) ) + raise TorErrorAuthError( + self.settings.get("control_port_address"), + self.settings.get("control_port_port"), + ) # If we made it this far, we should be connected to Tor self.connected_to_tor = True @@ -625,15 +627,15 @@ class Onion(object): self.common.log("Onion", "start_onion_service", f"port={port}") if not self.supports_ephemeral: - raise TorTooOld( - # strings._("error_ephemeral_not_supported") + print( "Your version of Tor is too old, ephemeral onion services are not supported" ) + raise TorTooOldEphemeral() if mode_settings.get("general", "client_auth") and not self.supports_stealth: - raise TorTooOld( - # strings._("error_stealth_not_supported") + print( "Your version of Tor is too old, stealth onion services are not supported" ) + raise TorTooOldStealth() auth_cookie = None if mode_settings.get("general", "client_auth"): @@ -690,10 +692,8 @@ class Onion(object): ) except ProtocolError as e: - raise TorErrorProtocolError( - # strings._("error_tor_protocol_error") - "Tor error: {}".format(e.args[0]) - ) + print("Tor error: {}".format(e.args[0])) + raise TorErrorProtocolError(e.args[0]) onion_host = res.service_id + ".onion" diff --git a/cli/onionshare_cli/onionshare.py b/cli/onionshare_cli/onionshare.py index f74672ce..513dbc34 100644 --- a/cli/onionshare_cli/onionshare.py +++ b/cli/onionshare_cli/onionshare.py @@ -21,7 +21,6 @@ along with this program. If not, see . import os, shutil from . import common -from .onion import TorTooOld, TorErrorProtocolError from .common import AutoStopTimer diff --git a/desktop/src/onionshare/gui_common.py b/desktop/src/onionshare/gui_common.py index f488a740..017fd6b7 100644 --- a/desktop/src/onionshare/gui_common.py +++ b/desktop/src/onionshare/gui_common.py @@ -24,7 +24,22 @@ import shutil from pkg_resources import resource_filename from . import strings -from onionshare_cli.onion import Onion +from onionshare_cli.onion import ( + Onion, + TorErrorInvalidSetting, + TorErrorAutomatic, + TorErrorSocketPort, + TorErrorSocketFile, + TorErrorMissingPassword, + TorErrorUnreadableCookieFile, + TorErrorAuthError, + TorErrorProtocolError, + BundledTorTimeout, + BundledTorBroken, + TorTooOldEphemeral, + TorTooOldStealth, + PortNotAvailable, +) class GuiCommon: @@ -377,3 +392,37 @@ class GuiCommon: Returns the absolute path of a resource """ return resource_filename("onionshare", os.path.join("resources", filename)) + + @staticmethod + def get_translated_tor_error(e): + """ + Takes an exception defined in onion.py and returns a translated error message + """ + if type(e) is TorErrorInvalidSetting: + return strings._("settings_error_unknown") + elif type(e) is TorErrorAutomatic: + return strings._("settings_error_automatic") + elif type(e) is TorErrorSocketPort: + return strings._("settings_error_socket_port").format(e.args[0], e.args[1]) + elif type(e) is TorErrorSocketFile: + return strings._("settings_error_socket_file").format(e.args[0]) + elif type(e) is TorErrorMissingPassword: + return strings._("settings_error_missing_password") + elif type(e) is TorErrorUnreadableCookieFile: + return strings._("settings_error_unreadable_cookie_file") + elif type(e) is TorErrorAuthError: + return strings._("settings_error_auth").format(e.args[0], e.args[1]) + elif type(e) is TorErrorProtocolError: + return strings._("error_tor_protocol_error").format(e.args[0]) + elif type(e) is BundledTorTimeout: + return strings._("settings_error_bundled_tor_timeout") + elif type(e) is BundledTorBroken: + return strings._("settings_error_bundled_tor_broken").format(e.args[0]) + elif type(e) is TorTooOldEphemeral: + return strings._("error_ephemeral_not_supported") + elif type(e) is TorTooOldStealth: + return strings._("error_stealth_not_supported") + elif type(e) is PortNotAvailable: + return strings._("error_port_not_available") + + return None diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json index 1f98d7a7..9617e9c4 100644 --- a/desktop/src/onionshare/resources/locale/en.json +++ b/desktop/src/onionshare/resources/locale/en.json @@ -187,5 +187,6 @@ "settings_error_unreadable_cookie_file": "Connected to the Tor controller, but password may be wrong, or your user is not permitted to read the cookie file.", "settings_error_bundled_tor_not_supported": "Using the Tor version that comes with OnionShare does not work in developer mode on Windows or macOS.", "settings_error_bundled_tor_timeout": "Taking too long to connect to Tor. Maybe you aren't connected to the Internet, or have an inaccurate system clock?", - "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor:\n{}" + "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor:\n{}", + "error_port_not_available": "OnionShare port not available" } \ No newline at end of file diff --git a/desktop/src/onionshare/settings_dialog.py b/desktop/src/onionshare/settings_dialog.py index 5f37bda1..1803b170 100644 --- a/desktop/src/onionshare/settings_dialog.py +++ b/desktop/src/onionshare/settings_dialog.py @@ -27,11 +27,31 @@ import os from onionshare_cli import common from onionshare_cli.settings import Settings -from onionshare_cli.onion import * +from onionshare_cli.onion import ( + Onion, + TorErrorInvalidSetting, + TorErrorAutomatic, + TorErrorSocketPort, + TorErrorSocketFile, + TorErrorMissingPassword, + TorErrorUnreadableCookieFile, + TorErrorAuthError, + TorErrorProtocolError, + BundledTorTimeout, + BundledTorBroken, + TorTooOldEphemeral, + TorTooOldStealth, + PortNotAvailable, +) from . import strings from .widgets import Alert -from .update_checker import * +from .update_checker import ( + UpdateCheckerCheckError, + UpdateCheckerInvalidLatestVersion, + UpdateChecker, + UpdateThread, +) from .tor_connection_dialog import TorConnectionDialog from .gui_common import GuiCommon @@ -698,10 +718,18 @@ class SettingsDialog(QtWidgets.QDialog): TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, - BundledTorNotSupported, BundledTorTimeout, + BundledTorBroken, + TorTooOldEphemeral, + TorTooOldStealth, + PortNotAvailable, ) as e: - Alert(self.common, e.args[0], QtWidgets.QMessageBox.Warning) + message = self.common.gui.get_translated_tor_error(e) + Alert( + self.common, + message, + QtWidgets.QMessageBox.Warning, + ) if settings.get("connection_type") == "bundled": self.tor_status.hide() self._enable_buttons() diff --git a/desktop/src/onionshare/threads.py b/desktop/src/onionshare/threads.py index 338bbf27..285b51b9 100644 --- a/desktop/src/onionshare/threads.py +++ b/desktop/src/onionshare/threads.py @@ -24,7 +24,6 @@ import os from PySide2 import QtCore from onionshare_cli.onion import ( - TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, @@ -34,6 +33,10 @@ from onionshare_cli.onion import ( TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, + BundledTorBroken, + TorTooOldEphemeral, + TorTooOldStealth, + PortNotAvailable, ) from . import strings @@ -93,7 +96,6 @@ class OnionThread(QtCore.QThread): self.success.emit() except ( - TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, @@ -103,9 +105,13 @@ class OnionThread(QtCore.QThread): TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, - OSError, + BundledTorBroken, + TorTooOldEphemeral, + TorTooOldStealth, + PortNotAvailable, ) as e: - self.error.emit(e.args[0]) + message = self.mode.common.gui.get_translated_tor_error(e) + self.error.emit(message) return @@ -174,7 +180,7 @@ class AutoStartTimer(QtCore.QThread): class EventHandlerThread(QtCore.QThread): """ - To trigger an event, write a JSON line to the events file. When that file changes, + To trigger an event, write a JSON line to the events file. When that file changes, each line will be handled as an event. Valid events are: {"type": "new_tab"} {"type": "new_share_tab", "filenames": ["file1", "file2"]} diff --git a/desktop/src/onionshare/tor_connection_dialog.py b/desktop/src/onionshare/tor_connection_dialog.py index d5fa72a0..a9201aed 100644 --- a/desktop/src/onionshare/tor_connection_dialog.py +++ b/desktop/src/onionshare/tor_connection_dialog.py @@ -18,9 +18,25 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +import time from PySide2 import QtCore, QtWidgets, QtGui -from onionshare_cli.onion import * +from onionshare_cli.onion import ( + BundledTorCanceled, + TorErrorInvalidSetting, + TorErrorAutomatic, + TorErrorSocketPort, + TorErrorSocketFile, + TorErrorMissingPassword, + TorErrorUnreadableCookieFile, + TorErrorAuthError, + TorErrorProtocolError, + BundledTorTimeout, + BundledTorBroken, + TorTooOldEphemeral, + TorTooOldStealth, + PortNotAvailable, +) from . import strings from .gui_common import GuiCommon @@ -156,9 +172,26 @@ class TorConnectionThread(QtCore.QThread): ) self.canceled_connecting_to_tor.emit() - except Exception as e: - self.common.log("TorConnectionThread", "run", f"caught exception: {e}") - self.error_connecting_to_tor.emit(str(e)) + except ( + TorErrorInvalidSetting, + TorErrorAutomatic, + TorErrorSocketPort, + TorErrorSocketFile, + TorErrorMissingPassword, + TorErrorUnreadableCookieFile, + TorErrorAuthError, + TorErrorProtocolError, + BundledTorTimeout, + BundledTorBroken, + TorTooOldEphemeral, + TorTooOldStealth, + PortNotAvailable, + ) as e: + message = self.common.gui.get_translated_tor_error(e) + self.common.log( + "TorConnectionThread", "run", f"caught exception: {message}" + ) + self.error_connecting_to_tor.emit(message) def _tor_status_update(self, progress, summary): self.tor_status_update.emit(progress, summary) From edaa8bd073382027bcb2042c4b14358c17c0a725 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Nov 2020 12:30:19 -0800 Subject: [PATCH 2/2] The CLI error message gets printed from the Onion object, so don't try to print it the exception --- cli/onionshare_cli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/onionshare_cli/__init__.py b/cli/onionshare_cli/__init__.py index 7361ac9e..bcc22f15 100644 --- a/cli/onionshare_cli/__init__.py +++ b/cli/onionshare_cli/__init__.py @@ -332,7 +332,7 @@ def main(cwd=None): print("") sys.exit() except Exception as e: - sys.exit(e.args[0]) + sys.exit() # Start the onionshare app try: