def get_color_mode(self, common): curr_settings = Settings(common) curr_settings.load() current_theme = curr_settings.get("theme") if current_theme == 1: return "light" elif current_theme == 2: return "dark" else: return "dark" if self.is_dark_mode() else "light"
class SettingsTab(QtWidgets.QWidget): """ Settings dialog. """ def __init__(self, common, tab_id, parent=None): super(SettingsTab, self).__init__() self.common = common self.common.log("SettingsTab", "__init__") self.system = platform.system() self.tab_id = tab_id self.parent = parent # Automatic updates options # Autoupdate self.autoupdate_checkbox = QtWidgets.QCheckBox() self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) self.autoupdate_checkbox.setText(strings._("gui_settings_autoupdate_option")) # Last update time self.autoupdate_timestamp = QtWidgets.QLabel() # Check for updates button self.check_for_updates_button = QtWidgets.QPushButton( strings._("gui_settings_autoupdate_check_button") ) self.check_for_updates_button.clicked.connect(self.check_for_updates) # We can't check for updates if not connected to Tor if not self.common.gui.onion.connected_to_tor: self.check_for_updates_button.setEnabled(False) # Autoupdate options layout autoupdate_group_layout = QtWidgets.QVBoxLayout() autoupdate_group_layout.addWidget(self.autoupdate_checkbox) autoupdate_group_layout.addWidget(self.autoupdate_timestamp) autoupdate_group_layout.addWidget(self.check_for_updates_button) autoupdate_group = QtWidgets.QGroupBox( strings._("gui_settings_autoupdate_label") ) autoupdate_group.setLayout(autoupdate_group_layout) autoupdate_layout = QtWidgets.QHBoxLayout() autoupdate_layout.addStretch() autoupdate_layout.addWidget(autoupdate_group) autoupdate_layout.addStretch() autoupdate_widget = QtWidgets.QWidget() autoupdate_widget.setLayout(autoupdate_layout) # Autoupdate is only available for Windows and Mac (Linux updates using package manager) if self.system != "Windows" and self.system != "Darwin": autoupdate_widget.hide() # Language settings language_label = QtWidgets.QLabel(strings._("gui_settings_language_label")) self.language_combobox = QtWidgets.QComboBox() # Populate the dropdown with all of OnionShare's available languages language_names_to_locales = { v: k for k, v in self.common.settings.available_locales.items() } language_names = list(language_names_to_locales) language_names.sort() for language_name in language_names: locale = language_names_to_locales[language_name] self.language_combobox.addItem(language_name, locale) language_layout = QtWidgets.QHBoxLayout() language_layout.addStretch() language_layout.addWidget(language_label) language_layout.addWidget(self.language_combobox) language_layout.addStretch() # Theme Settings theme_label = QtWidgets.QLabel(strings._("gui_settings_theme_label")) self.theme_combobox = QtWidgets.QComboBox() theme_choices = [ strings._("gui_settings_theme_auto"), strings._("gui_settings_theme_light"), strings._("gui_settings_theme_dark"), ] self.theme_combobox.addItems(theme_choices) theme_layout = QtWidgets.QHBoxLayout() theme_layout.addStretch() theme_layout.addWidget(theme_label) theme_layout.addWidget(self.theme_combobox) theme_layout.addStretch() # Version and help version_label = QtWidgets.QLabel( strings._("gui_settings_version_label").format(self.common.version) ) version_label.setAlignment(QtCore.Qt.AlignHCenter) help_label = QtWidgets.QLabel(strings._("gui_settings_help_label")) help_label.setAlignment(QtCore.Qt.AlignHCenter) help_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) help_label.setOpenExternalLinks(True) # Buttons self.save_button = QtWidgets.QPushButton(strings._("gui_settings_button_save")) self.save_button.clicked.connect(self.save_clicked) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addStretch() buttons_layout.addWidget(self.save_button) buttons_layout.addStretch() # Layout layout = QtWidgets.QVBoxLayout() layout.addStretch() layout.addWidget(autoupdate_widget) if autoupdate_widget.isVisible(): layout.addSpacing(20) layout.addLayout(language_layout) layout.addLayout(theme_layout) layout.addSpacing(20) layout.addWidget(version_label) layout.addWidget(help_label) layout.addSpacing(20) layout.addLayout(buttons_layout) layout.addStretch() self.setLayout(layout) self.reload_settings() if self.common.gui.onion.connected_to_tor: self.tor_is_connected() else: self.tor_is_disconnected() def reload_settings(self): # Load settings, and fill them in self.old_settings = Settings(self.common) self.old_settings.load() 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 = self.old_settings.get("autoupdate_timestamp") self._update_autoupdate_timestamp(autoupdate_timestamp) locale = self.old_settings.get("locale") locale_index = self.language_combobox.findData(locale) self.language_combobox.setCurrentIndex(locale_index) theme_choice = self.old_settings.get("theme") self.theme_combobox.setCurrentIndex(theme_choice) def check_for_updates(self): """ Check for Updates button clicked. Manually force an update check. """ self.common.log("SettingsTab", "check_for_updates") # Disable buttons self._disable_buttons() self.common.gui.qtapp.processEvents() def update_timestamp(): # Update the last checked label settings = Settings(self.common) settings.load() autoupdate_timestamp = settings.get("autoupdate_timestamp") self._update_autoupdate_timestamp(autoupdate_timestamp) def close_forced_update_thread(): forced_update_thread.quit() # Enable buttons self._enable_buttons() # Update timestamp update_timestamp() # Check for updates def update_available(update_url, installed_version, latest_version): Alert( self.common, strings._("update_available").format( update_url, installed_version, latest_version ), ) close_forced_update_thread() def update_not_available(): Alert(self.common, strings._("update_not_available")) close_forced_update_thread() def update_error(): Alert( self.common, strings._("update_error_check_error"), QtWidgets.QMessageBox.Warning, ) close_forced_update_thread() def update_invalid_version(latest_version): Alert( self.common, strings._("update_error_invalid_latest_version").format(latest_version), QtWidgets.QMessageBox.Warning, ) close_forced_update_thread() forced_update_thread = UpdateThread( self.common, self.common.gui.onion, force=True ) forced_update_thread.update_available.connect(update_available) forced_update_thread.update_not_available.connect(update_not_available) forced_update_thread.update_error.connect(update_error) forced_update_thread.update_invalid_version.connect(update_invalid_version) forced_update_thread.start() def save_clicked(self): """ Save button clicked. Save current settings to disk. """ self.common.log("SettingsTab", "save_clicked") 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 settings = self.settings_from_fields() if settings: # If language changed, inform user they need to restart OnionShare if changed(settings, self.old_settings, ["locale"]): # Look up error message in different locale new_locale = settings.get("locale") if ( new_locale in strings.translations and "gui_settings_language_changed_notice" in strings.translations[new_locale] ): notice = strings.translations[new_locale][ "gui_settings_language_changed_notice" ] else: notice = strings._("gui_settings_language_changed_notice") Alert(self.common, notice, QtWidgets.QMessageBox.Information) # If color mode changed, inform user they need to restart OnionShare if changed(settings, self.old_settings, ["theme"]): notice = strings._("gui_color_mode_changed_notice") Alert(self.common, notice, QtWidgets.QMessageBox.Information) # Save the new settings settings.save() self.parent.close_this_tab.emit() def help_clicked(self): """ Help button clicked. """ self.common.log("SettingsTab", "help_clicked") SettingsTab.open_help() @staticmethod def open_help(): help_url = "https://docs.onionshare.org/" QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_url)) def settings_from_fields(self): """ Return a Settings object that's full of values from the settings dialog. """ self.common.log("SettingsTab", "settings_from_fields") settings = Settings(self.common) settings.load() # To get the last update timestamp # Theme theme_index = self.theme_combobox.currentIndex() settings.set("theme", theme_index) # Language locale_index = self.language_combobox.currentIndex() locale = self.language_combobox.itemData(locale_index) settings.set("locale", locale) return settings def settings_have_changed(self): # Global settings have changed self.common.log("SettingsTab", "settings_have_changed") def _update_autoupdate_timestamp(self, autoupdate_timestamp): self.common.log("SettingsTab", "_update_autoupdate_timestamp") if autoupdate_timestamp: dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) last_checked = dt.strftime("%B %d, %Y %H:%M") else: last_checked = strings._("gui_settings_autoupdate_timestamp_never") self.autoupdate_timestamp.setText( strings._("gui_settings_autoupdate_timestamp").format(last_checked) ) def _disable_buttons(self): self.common.log("SettingsTab", "_disable_buttons") self.check_for_updates_button.setEnabled(False) self.save_button.setEnabled(False) def _enable_buttons(self): self.common.log("SettingsTab", "_enable_buttons") # We can't check for updates if we're still not connected to Tor if not self.common.gui.onion.connected_to_tor: self.check_for_updates_button.setEnabled(False) else: self.check_for_updates_button.setEnabled(True) self.save_button.setEnabled(True) def tor_is_connected(self): self.check_for_updates_button.show() def tor_is_disconnected(self): self.check_for_updates_button.hide()
def update_timestamp(): # Update the last checked label settings = Settings(self.common) settings.load() autoupdate_timestamp = settings.get("autoupdate_timestamp") self._update_autoupdate_timestamp(autoupdate_timestamp)
class AutoConnectTab(QtWidgets.QWidget): """ Initial Tab that appears in the very beginning to ask user if should auto connect. """ close_this_tab = QtCore.Signal() tor_is_connected = QtCore.Signal() tor_is_disconnected = QtCore.Signal() def __init__(self, common, tab_id, status_bar, window, parent=None): super(AutoConnectTab, self).__init__() self.common = common self.common.log("AutoConnectTab", "__init__") self.status_bar = status_bar self.tab_id = tab_id self.window = window self.parent = parent # Was auto connected? self.curr_settings = Settings(common) self.curr_settings.load() self.auto_connect_enabled = self.curr_settings.get("auto_connect") # Rocket ship animation images self.anim_stars = AnimStars(self, self.window) self.anim_ship = AnimShip(self, self.window) self.anim_smoke = AnimSmoke(self, self.window) # Onionshare logo self.image_label = QtWidgets.QLabel() self.image_label.setPixmap( QtGui.QPixmap.fromImage( QtGui.QImage( GuiCommon.get_resource_path( os.path.join( "images", f"{common.gui.color_mode}_logo_text_bg.png"))))) self.image_label.setFixedSize(322, 65) image_layout = QtWidgets.QVBoxLayout() image_layout.addWidget(self.image_label) self.image = QtWidgets.QWidget() self.image.setLayout(image_layout) # First launch widget self.first_launch_widget = AutoConnectFirstLaunchWidget( self.common, self.curr_settings) self.first_launch_widget.toggle_auto_connect.connect( self.toggle_auto_connect) self.first_launch_widget.connect_clicked.connect( self.first_launch_widget_connect_clicked) self.first_launch_widget.open_tor_settings.connect( self.open_tor_settings) self.first_launch_widget.show() # Use bridge widget self.use_bridge_widget = AutoConnectUseBridgeWidget(self.common) self.use_bridge_widget.connect_clicked.connect( self.use_bridge_connect_clicked) self.use_bridge_widget.try_again_clicked.connect( self.first_launch_widget_connect_clicked) self.use_bridge_widget.open_tor_settings.connect( self.open_tor_settings) self.use_bridge_widget.hide() # Tor connection widget self.tor_con = TorConnectionWidget(self.common, self.status_bar) self.tor_con.success.connect(self.tor_con_success) self.tor_con.fail.connect(self.tor_con_fail) self.tor_con.update_progress.connect(self.anim_stars.update) self.tor_con.update_progress.connect(self.anim_ship.update) self.tor_con.update_progress.connect(self.anim_smoke.update) self.tor_con.hide() # Layout content_layout = QtWidgets.QVBoxLayout() content_layout.addStretch() content_layout.addWidget(self.image) content_layout.addWidget(self.first_launch_widget) content_layout.addWidget(self.use_bridge_widget) content_layout.addWidget(self.tor_con) content_layout.addStretch() content_layout.setAlignment(QtCore.Qt.AlignCenter) content_widget = QtWidgets.QWidget() content_widget.setLayout(content_layout) self.layout = QtWidgets.QHBoxLayout() self.layout.addWidget(content_widget) self.layout.addStretch() self.setLayout(self.layout) def check_autoconnect(self): """ After rendering, check if autoconnect was clicked, then start connecting """ self.common.log("AutoConnectTab", "autoconnect_checking") if self.auto_connect_enabled: self.first_launch_widget.enable_autoconnect_checkbox.setChecked( True) self.first_launch_widget_connect_clicked() def toggle_auto_connect(self): """ Auto connect checkbox clicked """ self.common.log("AutoConnectTab", "autoconnect_checkbox_clicked") self.curr_settings.set( "auto_connect", self.first_launch_widget.enable_autoconnect_checkbox.isChecked(), ) self.curr_settings.save() def open_tor_settings(self): self.parent.open_settings_tab(from_autoconnect=True, active_tab="tor") def first_launch_widget_connect_clicked(self): """ Connect button in first launch widget clicked. Try to connect to tor. """ self.common.log("AutoConnectTab", "first_launch_widget_connect_clicked") self.first_launch_widget.hide_buttons() self.tor_con.show() self.tor_con.start(self.curr_settings) def _got_bridges(self): self.use_bridge_widget.progress.hide() self.use_bridge_widget.progress_label.hide() # Try and connect again self.common.log( "AutoConnectTab", "_got_bridges", "Got bridges. Trying to reconnect to Tor", ) self.tor_con.show() self.tor_con.start(self.curr_settings) def _got_no_bridges(self): # If we got no bridges, even after trying the default bridges # provided by the Censorship API, try connecting again using # our built-in obfs4 bridges self.curr_settings.set("bridges_type", "built-in") self.curr_settings.set("bridges_builtin_pt", "obfs4") self.curr_settings.set("bridges_enabled", True) self.curr_settings.save() self._got_bridges() def _censorship_progress_update(self, progress, summary): self.use_bridge_widget.progress.setValue(int(progress)) self.use_bridge_widget.progress_label.setText( f"<strong>{strings._('gui_autoconnect_circumventing_censorship')}</strong><br>{summary}" ) def network_connection_error(self): """ Display an error if there simply seems no network connection. """ self.use_bridge_widget.connection_status_label.setText( strings._("gui_autoconnect_failed_to_connect_to_tor")) self.use_bridge_widget.progress.hide() self.use_bridge_widget.progress_label.hide() self.use_bridge_widget.error_label.show() self.use_bridge_widget.country_combobox.setEnabled(True) self.use_bridge_widget.show_buttons() self.use_bridge_widget.show() def use_bridge_connect_clicked(self): """ Connect button in use bridge widget clicked. """ self.common.log( "AutoConnectTab", "use_bridge_connect_clicked", "Trying to automatically obtain bridges", ) self.use_bridge_widget.hide_buttons() self.use_bridge_widget.progress.show() self.use_bridge_widget.progress_label.show() if self.use_bridge_widget.detect_automatic_radio.isChecked(): country = False else: country = self.use_bridge_widget.country_combobox.currentData( ).lower() self._censorship_progress_update( 50, strings._( "gui_autoconnect_circumventing_censorship_starting_meek")) try: self.common.gui.meek.start() self.censorship_circumvention = CensorshipCircumvention( self.common, self.common.gui.meek) self._censorship_progress_update( 75, strings. _("gui_autoconnect_circumventing_censorship_requesting_bridges" ), ) bridge_settings = self.censorship_circumvention.request_settings( country=country) if not bridge_settings: # Fall back to trying the default bridges from the API self.common.log( "AutoConnectTab", "use_bridge_connect_clicked", "Falling back to trying default bridges provided by the Censorship Circumvention API", ) bridge_settings = ( self.censorship_circumvention.request_default_bridges()) self.common.gui.meek.cleanup() if bridge_settings and self.censorship_circumvention.save_settings( self.curr_settings, bridge_settings): self._censorship_progress_update( 100, strings. _("gui_autoconnect_circumventing_censorship_got_bridges"), ) self._got_bridges() else: self._got_no_bridges() except ( MeekNotRunning, MeekNotFound, ) as e: self._got_no_bridges() except CensorshipCircumventionError as e: self.common.log( "AutoConnectTab", "use_bridge_connect_clicked", "Request to the Tor Censorship Circumvention API failed. No network connection?", ) self.network_connection_error() def check_for_updates(self): """ Check for OnionShare updates in a new thread, if enabled. """ if self.common.platform == "Windows" or self.common.platform == "Darwin": if self.common.settings.get("use_autoupdate"): def update_available(update_url, installed_version, latest_version): Alert( self.common, strings._("update_available").format( update_url, installed_version, latest_version), ) self.update_thread = UpdateThread(self.common, self.common.gui.onion) self.update_thread.update_available.connect(update_available) self.update_thread.start() def tor_con_success(self): """ Finished testing tor connection. """ self.tor_con.hide() self.first_launch_widget.show_buttons() self.use_bridge_widget.show_buttons() self.use_bridge_widget.progress.hide() self.use_bridge_widget.progress_label.hide() if self.common.gui.onion.is_authenticated( ) and not self.tor_con.wasCanceled(): # Tell the tabs that Tor is connected self.tor_is_connected.emit() # After connecting to Tor, check for updates self.check_for_updates() # Close the tab self.close_this_tab.emit() def tor_con_fail(self, msg): """ Finished testing tor connection. """ self.tor_con.hide() # If there is a message, update the text of the bridge widget if msg: self.use_bridge_widget.connection_error_message.setText(msg) # If we're on first launch, check if wasCanceled # If cancelled, stay in first launch widget and show buttons # Else, switch to use bridge if self.first_launch_widget.isVisible(): if self.tor_con.wasCanceled(): self.first_launch_widget.show_buttons() else: self.first_launch_widget.show_buttons() self.first_launch_widget.hide() self.use_bridge_widget.show() else: self.use_bridge_widget.show_buttons() def reload_settings(self): """ Reload the latest Tor settings, and reset to show the first-launch widget if it had been hidden. """ self.curr_settings.load() self.auto_connect_enabled = self.curr_settings.get("auto_connect") self.first_launch_widget.enable_autoconnect_checkbox.setChecked( self.auto_connect_enabled) self.use_bridge_widget.hide() self.first_launch_widget.show_buttons() self.first_launch_widget.show()
class SettingsDialog(QtWidgets.QDialog): """ Settings dialog. """ settings_saved = QtCore.Signal() def __init__(self, common): super(SettingsDialog, self).__init__() self.common = common self.common.log("SettingsDialog", "__init__") self.setModal(True) self.setWindowTitle(strings._("gui_settings_window_title")) self.setWindowIcon( QtGui.QIcon(GuiCommon.get_resource_path("images/logo.png"))) self.system = platform.system() # If ONIONSHARE_HIDE_TOR_SETTINGS=1, hide Tor settings in the dialog self.hide_tor_settings = os.environ.get( "ONIONSHARE_HIDE_TOR_SETTINGS") == "1" # Automatic updates options # Autoupdate self.autoupdate_checkbox = QtWidgets.QCheckBox() self.autoupdate_checkbox.setCheckState(QtCore.Qt.Unchecked) self.autoupdate_checkbox.setText( strings._("gui_settings_autoupdate_option")) # Last update time self.autoupdate_timestamp = QtWidgets.QLabel() # Check for updates button self.check_for_updates_button = QtWidgets.QPushButton( strings._("gui_settings_autoupdate_check_button")) self.check_for_updates_button.clicked.connect(self.check_for_updates) # We can't check for updates if not connected to Tor if not self.common.gui.onion.connected_to_tor: self.check_for_updates_button.setEnabled(False) # Autoupdate options layout autoupdate_group_layout = QtWidgets.QVBoxLayout() autoupdate_group_layout.addWidget(self.autoupdate_checkbox) autoupdate_group_layout.addWidget(self.autoupdate_timestamp) autoupdate_group_layout.addWidget(self.check_for_updates_button) autoupdate_group = QtWidgets.QGroupBox( strings._("gui_settings_autoupdate_label")) autoupdate_group.setLayout(autoupdate_group_layout) # Autoupdate is only available for Windows and Mac (Linux updates using package manager) if self.system != "Windows" and self.system != "Darwin": autoupdate_group.hide() # Language settings language_label = QtWidgets.QLabel( strings._("gui_settings_language_label")) self.language_combobox = QtWidgets.QComboBox() # Populate the dropdown with all of OnionShare's available languages language_names_to_locales = { v: k for k, v in self.common.settings.available_locales.items() } language_names = list(language_names_to_locales) language_names.sort() for language_name in language_names: locale = language_names_to_locales[language_name] self.language_combobox.addItem(language_name, locale) language_layout = QtWidgets.QHBoxLayout() language_layout.addWidget(language_label) language_layout.addWidget(self.language_combobox) language_layout.addStretch() #Theme Settings theme_label = QtWidgets.QLabel(strings._("gui_settings_theme_label")) self.theme_combobox = QtWidgets.QComboBox() theme_choices = [ strings._("gui_settings_theme_auto"), strings._("gui_settings_theme_light"), strings._("gui_settings_theme_dark") ] self.theme_combobox.addItems(theme_choices) theme_layout = QtWidgets.QHBoxLayout() theme_layout.addWidget(theme_label) theme_layout.addWidget(self.theme_combobox) theme_layout.addStretch() # Connection type: either automatic, control port, or socket file # Bundled Tor self.connection_type_bundled_radio = QtWidgets.QRadioButton( strings._("gui_settings_connection_type_bundled_option")) self.connection_type_bundled_radio.toggled.connect( self.connection_type_bundled_toggled) # Bundled Tor doesn't work on dev mode in Windows or Mac if (self.system == "Windows" or self.system == "Darwin") and getattr( sys, "onionshare_dev_mode", False): self.connection_type_bundled_radio.setEnabled(False) # Bridge options for bundled tor # No bridges option radio self.tor_bridges_no_bridges_radio = QtWidgets.QRadioButton( strings._("gui_settings_tor_bridges_no_bridges_radio_option")) self.tor_bridges_no_bridges_radio.toggled.connect( self.tor_bridges_no_bridges_radio_toggled) # obfs4 option radio # if the obfs4proxy binary is missing, we can't use obfs4 transports ( self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path, ) = self.common.gui.get_tor_paths() if not self.obfs4proxy_file_path or not os.path.isfile( self.obfs4proxy_file_path): self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( strings. _("gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy")) self.tor_bridges_use_obfs4_radio.setEnabled(False) else: self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton( strings._("gui_settings_tor_bridges_obfs4_radio_option")) self.tor_bridges_use_obfs4_radio.toggled.connect( self.tor_bridges_use_obfs4_radio_toggled) # meek_lite-azure option radio # if the obfs4proxy binary is missing, we can't use meek_lite-azure transports ( self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path, ) = self.common.gui.get_tor_paths() if not self.obfs4proxy_file_path or not os.path.isfile( self.obfs4proxy_file_path): self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( strings. _("gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy" )) self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False) else: self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton( strings._( "gui_settings_tor_bridges_meek_lite_azure_radio_option")) self.tor_bridges_use_meek_lite_azure_radio.toggled.connect( self.tor_bridges_use_meek_lite_azure_radio_toggled) # Custom bridges radio and textbox self.tor_bridges_use_custom_radio = QtWidgets.QRadioButton( strings._("gui_settings_tor_bridges_custom_radio_option")) self.tor_bridges_use_custom_radio.toggled.connect( self.tor_bridges_use_custom_radio_toggled) self.tor_bridges_use_custom_label = QtWidgets.QLabel( strings._("gui_settings_tor_bridges_custom_label")) self.tor_bridges_use_custom_label.setTextInteractionFlags( QtCore.Qt.TextBrowserInteraction) self.tor_bridges_use_custom_label.setOpenExternalLinks(True) self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() self.tor_bridges_use_custom_textbox.setMaximumHeight(200) self.tor_bridges_use_custom_textbox.setPlaceholderText( "[address:port] [identifier]") tor_bridges_use_custom_textbox_options_layout = QtWidgets.QVBoxLayout() tor_bridges_use_custom_textbox_options_layout.addWidget( self.tor_bridges_use_custom_label) tor_bridges_use_custom_textbox_options_layout.addWidget( self.tor_bridges_use_custom_textbox) self.tor_bridges_use_custom_textbox_options = QtWidgets.QWidget() self.tor_bridges_use_custom_textbox_options.setLayout( tor_bridges_use_custom_textbox_options_layout) self.tor_bridges_use_custom_textbox_options.hide() # Bridges layout/widget bridges_layout = QtWidgets.QVBoxLayout() bridges_layout.addWidget(self.tor_bridges_no_bridges_radio) bridges_layout.addWidget(self.tor_bridges_use_obfs4_radio) bridges_layout.addWidget(self.tor_bridges_use_meek_lite_azure_radio) bridges_layout.addWidget(self.tor_bridges_use_custom_radio) bridges_layout.addWidget(self.tor_bridges_use_custom_textbox_options) self.bridges = QtWidgets.QWidget() self.bridges.setLayout(bridges_layout) # Automatic self.connection_type_automatic_radio = QtWidgets.QRadioButton( strings._("gui_settings_connection_type_automatic_option")) self.connection_type_automatic_radio.toggled.connect( self.connection_type_automatic_toggled) # Control port self.connection_type_control_port_radio = QtWidgets.QRadioButton( strings._("gui_settings_connection_type_control_port_option")) self.connection_type_control_port_radio.toggled.connect( self.connection_type_control_port_toggled) connection_type_control_port_extras_label = QtWidgets.QLabel( strings._("gui_settings_control_port_label")) self.connection_type_control_port_extras_address = QtWidgets.QLineEdit( ) self.connection_type_control_port_extras_port = QtWidgets.QLineEdit() connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() connection_type_control_port_extras_layout.addWidget( connection_type_control_port_extras_label) connection_type_control_port_extras_layout.addWidget( self.connection_type_control_port_extras_address) connection_type_control_port_extras_layout.addWidget( self.connection_type_control_port_extras_port) self.connection_type_control_port_extras = QtWidgets.QWidget() self.connection_type_control_port_extras.setLayout( connection_type_control_port_extras_layout) self.connection_type_control_port_extras.hide() # Socket file self.connection_type_socket_file_radio = QtWidgets.QRadioButton( strings._("gui_settings_connection_type_socket_file_option")) self.connection_type_socket_file_radio.toggled.connect( self.connection_type_socket_file_toggled) connection_type_socket_file_extras_label = QtWidgets.QLabel( strings._("gui_settings_socket_file_label")) self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit() connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() connection_type_socket_file_extras_layout.addWidget( connection_type_socket_file_extras_label) connection_type_socket_file_extras_layout.addWidget( self.connection_type_socket_file_extras_path) self.connection_type_socket_file_extras = QtWidgets.QWidget() self.connection_type_socket_file_extras.setLayout( connection_type_socket_file_extras_layout) self.connection_type_socket_file_extras.hide() # Tor SOCKS address and port gui_settings_socks_label = QtWidgets.QLabel( strings._("gui_settings_socks_label")) self.connection_type_socks_address = QtWidgets.QLineEdit() self.connection_type_socks_port = QtWidgets.QLineEdit() connection_type_socks_layout = QtWidgets.QHBoxLayout() connection_type_socks_layout.addWidget(gui_settings_socks_label) connection_type_socks_layout.addWidget( self.connection_type_socks_address) connection_type_socks_layout.addWidget(self.connection_type_socks_port) self.connection_type_socks = QtWidgets.QWidget() self.connection_type_socks.setLayout(connection_type_socks_layout) self.connection_type_socks.hide() # Authentication options # No authentication self.authenticate_no_auth_radio = QtWidgets.QRadioButton( strings._("gui_settings_authenticate_no_auth_option")) self.authenticate_no_auth_radio.toggled.connect( self.authenticate_no_auth_toggled) # Password self.authenticate_password_radio = QtWidgets.QRadioButton( strings._("gui_settings_authenticate_password_option")) self.authenticate_password_radio.toggled.connect( self.authenticate_password_toggled) authenticate_password_extras_label = QtWidgets.QLabel( strings._("gui_settings_password_label")) self.authenticate_password_extras_password = QtWidgets.QLineEdit("") authenticate_password_extras_layout = QtWidgets.QHBoxLayout() authenticate_password_extras_layout.addWidget( authenticate_password_extras_label) authenticate_password_extras_layout.addWidget( self.authenticate_password_extras_password) self.authenticate_password_extras = QtWidgets.QWidget() self.authenticate_password_extras.setLayout( authenticate_password_extras_layout) self.authenticate_password_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_password_extras) self.authenticate_group = QtWidgets.QGroupBox( strings._("gui_settings_authenticate_label")) self.authenticate_group.setLayout(authenticate_group_layout) # Put the radios into their own group so they are exclusive connection_type_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_radio_group_layout.addWidget( self.connection_type_bundled_radio) connection_type_radio_group_layout.addWidget( self.connection_type_automatic_radio) connection_type_radio_group_layout.addWidget( self.connection_type_control_port_radio) connection_type_radio_group_layout.addWidget( self.connection_type_socket_file_radio) connection_type_radio_group = QtWidgets.QGroupBox( strings._("gui_settings_connection_type_label")) connection_type_radio_group.setLayout( connection_type_radio_group_layout) # The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges) connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_bridges_radio_group_layout.addWidget(self.bridges) self.connection_type_bridges_radio_group = QtWidgets.QGroupBox( strings._("gui_settings_tor_bridges")) self.connection_type_bridges_radio_group.setLayout( connection_type_bridges_radio_group_layout) self.connection_type_bridges_radio_group.hide() # Test tor settings button self.connection_type_test_button = QtWidgets.QPushButton( strings._("gui_settings_connection_type_test_button")) self.connection_type_test_button.clicked.connect(self.test_tor_clicked) connection_type_test_button_layout = QtWidgets.QHBoxLayout() connection_type_test_button_layout.addWidget( self.connection_type_test_button) connection_type_test_button_layout.addStretch() # Connection type layout connection_type_layout = QtWidgets.QVBoxLayout() connection_type_layout.addWidget( self.connection_type_control_port_extras) connection_type_layout.addWidget( self.connection_type_socket_file_extras) connection_type_layout.addWidget(self.connection_type_socks) connection_type_layout.addWidget(self.authenticate_group) connection_type_layout.addWidget( self.connection_type_bridges_radio_group) connection_type_layout.addLayout(connection_type_test_button_layout) # Buttons self.save_button = QtWidgets.QPushButton( strings._("gui_settings_button_save")) self.save_button.clicked.connect(self.save_clicked) self.cancel_button = QtWidgets.QPushButton( strings._("gui_settings_button_cancel")) self.cancel_button.clicked.connect(self.cancel_clicked) version_label = QtWidgets.QLabel(f"OnionShare {self.common.version}") version_label.setStyleSheet(self.common.gui.css["settings_version"]) self.help_button = QtWidgets.QPushButton( strings._("gui_settings_button_help")) self.help_button.clicked.connect(self.help_clicked) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addWidget(version_label) buttons_layout.addWidget(self.help_button) buttons_layout.addStretch() buttons_layout.addWidget(self.save_button) buttons_layout.addWidget(self.cancel_button) # Tor network connection status self.tor_status = QtWidgets.QLabel() self.tor_status.setStyleSheet( self.common.gui.css["settings_tor_status"]) self.tor_status.hide() # Layout tor_layout = QtWidgets.QVBoxLayout() tor_layout.addWidget(connection_type_radio_group) tor_layout.addLayout(connection_type_layout) tor_layout.addWidget(self.tor_status) tor_layout.addStretch() layout = QtWidgets.QVBoxLayout() if not self.hide_tor_settings: layout.addLayout(tor_layout) layout.addSpacing(20) layout.addWidget(autoupdate_group) if autoupdate_group.isVisible(): layout.addSpacing(20) layout.addLayout(language_layout) layout.addSpacing(20) layout.addLayout(theme_layout) layout.addSpacing(20) layout.addStretch() layout.addLayout(buttons_layout) self.setLayout(layout) self.cancel_button.setFocus() self.reload_settings() def reload_settings(self): # Load settings, and fill them in self.old_settings = Settings(self.common) self.old_settings.load() 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 = self.old_settings.get("autoupdate_timestamp") self._update_autoupdate_timestamp(autoupdate_timestamp) locale = self.old_settings.get("locale") locale_index = self.language_combobox.findData(locale) self.language_combobox.setCurrentIndex(locale_index) theme_choice = self.old_settings.get("theme") self.theme_combobox.setCurrentIndex(theme_choice) 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) else: # If bundled tor is disabled, fallback to automatic self.connection_type_automatic_radio.setChecked(True) elif connection_type == "automatic": self.connection_type_automatic_radio.setChecked(True) elif connection_type == "control_port": 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( 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( self.old_settings.get("auth_password")) if self.old_settings.get("no_bridges"): self.tor_bridges_no_bridges_radio.setChecked(True) self.tor_bridges_use_obfs4_radio.setChecked(False) self.tor_bridges_use_meek_lite_azure_radio.setChecked(False) self.tor_bridges_use_custom_radio.setChecked(False) else: self.tor_bridges_no_bridges_radio.setChecked(False) self.tor_bridges_use_obfs4_radio.setChecked( self.old_settings.get("tor_bridges_use_obfs4")) self.tor_bridges_use_meek_lite_azure_radio.setChecked( self.old_settings.get("tor_bridges_use_meek_lite_azure")) if self.old_settings.get("tor_bridges_use_custom_bridges"): self.tor_bridges_use_custom_radio.setChecked(True) # Remove the 'Bridge' lines at the start of each bridge. # They are added automatically to provide compatibility with # copying/pasting bridges provided from https://bridges.torproject.org new_bridges = [] bridges = self.old_settings.get( "tor_bridges_use_custom_bridges").split("Bridge ") for bridge in bridges: new_bridges.append(bridge) new_bridges = "".join(new_bridges) self.tor_bridges_use_custom_textbox.setPlainText(new_bridges) def connection_type_bundled_toggled(self, checked): """ Connection type bundled was toggled. If checked, hide authentication fields. """ self.common.log("SettingsDialog", "connection_type_bundled_toggled") if self.hide_tor_settings: return if checked: self.authenticate_group.hide() self.connection_type_socks.hide() self.connection_type_bridges_radio_group.show() def tor_bridges_no_bridges_radio_toggled(self, checked): """ 'No bridges' option was toggled. If checked, enable other bridge options. """ if self.hide_tor_settings: return if checked: self.tor_bridges_use_custom_textbox_options.hide() def tor_bridges_use_obfs4_radio_toggled(self, checked): """ obfs4 bridges option was toggled. If checked, disable custom bridge options. """ if self.hide_tor_settings: return if checked: self.tor_bridges_use_custom_textbox_options.hide() def tor_bridges_use_meek_lite_azure_radio_toggled(self, checked): """ meek_lite_azure bridges option was toggled. If checked, disable custom bridge options. """ if self.hide_tor_settings: return if checked: self.tor_bridges_use_custom_textbox_options.hide() # Alert the user about meek's costliness if it looks like they're turning it on if not self.old_settings.get("tor_bridges_use_meek_lite_azure"): Alert( self.common, strings._("gui_settings_meek_lite_expensive_warning"), QtWidgets.QMessageBox.Warning, ) def tor_bridges_use_custom_radio_toggled(self, checked): """ Custom bridges option was toggled. If checked, show custom bridge options. """ if self.hide_tor_settings: return if checked: self.tor_bridges_use_custom_textbox_options.show() def connection_type_automatic_toggled(self, checked): """ Connection type automatic was toggled. If checked, hide authentication fields. """ self.common.log("SettingsDialog", "connection_type_automatic_toggled") if self.hide_tor_settings: return if checked: self.authenticate_group.hide() self.connection_type_socks.hide() self.connection_type_bridges_radio_group.hide() def connection_type_control_port_toggled(self, checked): """ Connection type control port was toggled. If checked, show extra fields for Tor control address and port. If unchecked, hide those extra fields. """ self.common.log("SettingsDialog", "connection_type_control_port_toggled") if self.hide_tor_settings: return if checked: self.authenticate_group.show() self.connection_type_control_port_extras.show() self.connection_type_socks.show() self.connection_type_bridges_radio_group.hide() else: self.connection_type_control_port_extras.hide() def connection_type_socket_file_toggled(self, checked): """ Connection type socket file was toggled. If checked, show extra fields for socket file. If unchecked, hide those extra fields. """ self.common.log("SettingsDialog", "connection_type_socket_file_toggled") if self.hide_tor_settings: return if checked: self.authenticate_group.show() self.connection_type_socket_file_extras.show() self.connection_type_socks.show() self.connection_type_bridges_radio_group.hide() else: self.connection_type_socket_file_extras.hide() def authenticate_no_auth_toggled(self, checked): """ Authentication option no authentication was toggled. """ self.common.log("SettingsDialog", "authenticate_no_auth_toggled") def authenticate_password_toggled(self, checked): """ Authentication option password was toggled. If checked, show extra fields for password auth. If unchecked, hide those extra fields. """ self.common.log("SettingsDialog", "authenticate_password_toggled") if checked: self.authenticate_password_extras.show() else: self.authenticate_password_extras.hide() def test_tor_clicked(self): """ Test Tor Settings button clicked. With the given settings, see if we can successfully connect and authenticate to Tor. """ self.common.log("SettingsDialog", "test_tor_clicked") settings = self.settings_from_fields() try: # Show Tor connection status if connection type is bundled tor if settings.get("connection_type") == "bundled": self.tor_status.show() self._disable_buttons() def tor_status_update_func(progress, summary): self._tor_status_update(progress, summary) return True else: tor_status_update_func = None onion = Onion( self.common, use_tmp_dir=True, get_tor_paths=self.common.gui.get_tor_paths, ) onion.connect( custom_settings=settings, tor_status_update_func=tor_status_update_func, ) # If an exception hasn't been raised yet, the Tor settings work Alert( self.common, strings._("settings_test_success").format( onion.tor_version, onion.supports_ephemeral, onion.supports_stealth, onion.supports_v3_onions, ), ) # Clean up onion.cleanup() 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) Alert( self.common, message, QtWidgets.QMessageBox.Warning, ) if settings.get("connection_type") == "bundled": self.tor_status.hide() self._enable_buttons() def check_for_updates(self): """ Check for Updates button clicked. Manually force an update check. """ self.common.log("SettingsDialog", "check_for_updates") # Disable buttons self._disable_buttons() self.common.gui.qtapp.processEvents() def update_timestamp(): # Update the last checked label settings = Settings(self.common) settings.load() autoupdate_timestamp = settings.get("autoupdate_timestamp") self._update_autoupdate_timestamp(autoupdate_timestamp) def close_forced_update_thread(): forced_update_thread.quit() # Enable buttons self._enable_buttons() # Update timestamp update_timestamp() # Check for updates def update_available(update_url, installed_version, latest_version): Alert( self.common, strings._("update_available").format(update_url, installed_version, latest_version), ) close_forced_update_thread() def update_not_available(): Alert(self.common, strings._("update_not_available")) close_forced_update_thread() def update_error(): Alert( self.common, strings._("update_error_check_error"), QtWidgets.QMessageBox.Warning, ) close_forced_update_thread() def update_invalid_version(latest_version): Alert( self.common, strings._("update_error_invalid_latest_version").format( latest_version), QtWidgets.QMessageBox.Warning, ) close_forced_update_thread() forced_update_thread = UpdateThread(self.common, self.common.gui.onion, force=True) forced_update_thread.update_available.connect(update_available) forced_update_thread.update_not_available.connect(update_not_available) forced_update_thread.update_error.connect(update_error) forced_update_thread.update_invalid_version.connect( update_invalid_version) forced_update_thread.start() def save_clicked(self): """ Save button clicked. Save current settings to disk. """ self.common.log("SettingsDialog", "save_clicked") 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 settings = self.settings_from_fields() if settings: # If language changed, inform user they need to restart OnionShare if changed(settings, self.old_settings, ["locale"]): # Look up error message in different locale new_locale = settings.get("locale") if (new_locale in strings.translations and "gui_settings_language_changed_notice" in strings.translations[new_locale]): notice = strings.translations[new_locale][ "gui_settings_language_changed_notice"] else: notice = strings._("gui_settings_language_changed_notice") Alert(self.common, notice, QtWidgets.QMessageBox.Information) # If color mode changed, inform user they need to restart OnionShare if changed(settings, self.old_settings, ["theme"]): notice = strings._("gui_color_mode_changed_notice") Alert(self.common, notice, QtWidgets.QMessageBox.Information) # Save the new settings settings.save() # If Tor isn't connected, or if Tor settings have changed, Reinitialize # the Onion object reboot_onion = False if not self.common.gui.local_only: if self.common.gui.onion.is_authenticated(): self.common.log("SettingsDialog", "save_clicked", "Connected to Tor") 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", "no_bridges", "tor_bridges_use_obfs4", "tor_bridges_use_meek_lite_azure", "tor_bridges_use_custom_bridges", ], ): reboot_onion = True else: self.common.log("SettingsDialog", "save_clicked", "Not connected to Tor") # Tor isn't connected, so try connecting reboot_onion = True # Do we need to reinitialize Tor? if reboot_onion: # Reinitialize the Onion object self.common.log("SettingsDialog", "save_clicked", "rebooting the Onion") self.common.gui.onion.cleanup() tor_con = TorConnectionDialog(self.common, settings) tor_con.start() self.common.log( "SettingsDialog", "save_clicked", f"Onion done rebooting, connected to Tor: {self.common.gui.onion.connected_to_tor}", ) if (self.common.gui.onion.is_authenticated() and not tor_con.wasCanceled()): self.settings_saved.emit() self.close() else: self.settings_saved.emit() self.close() else: self.settings_saved.emit() self.close() def cancel_clicked(self): """ Cancel button clicked. """ self.common.log("SettingsDialog", "cancel_clicked") if (not self.common.gui.local_only and not self.common.gui.onion.is_authenticated()): Alert( self.common, strings._("gui_tor_connection_canceled"), QtWidgets.QMessageBox.Warning, ) sys.exit() else: self.close() def help_clicked(self): """ Help button clicked. """ self.common.log("SettingsDialog", "help_clicked") SettingsDialog.open_help() @staticmethod def open_help(): help_url = "https://docs.onionshare.org/" QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_url)) def settings_from_fields(self): """ Return a Settings object that's full of values from the settings dialog. """ self.common.log("SettingsDialog", "settings_from_fields") settings = Settings(self.common) settings.load() # To get the last update timestamp # Theme theme_index = self.theme_combobox.currentIndex() settings.set("theme", theme_index) # Language locale_index = self.language_combobox.currentIndex() locale = self.language_combobox.itemData(locale_index) settings.set("locale", locale) # Tor connection if self.connection_type_bundled_radio.isChecked(): settings.set("connection_type", "bundled") if self.connection_type_automatic_radio.isChecked(): settings.set("connection_type", "automatic") if self.connection_type_control_port_radio.isChecked(): settings.set("connection_type", "control_port") if self.connection_type_socket_file_radio.isChecked(): settings.set("connection_type", "socket_file") if self.autoupdate_checkbox.isChecked(): settings.set("use_autoupdate", True) else: settings.set("use_autoupdate", False) 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("socket_file_path", self.connection_type_socket_file_extras_path.text()) settings.set("socks_address", self.connection_type_socks_address.text()) settings.set("socks_port", self.connection_type_socks_port.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") settings.set("auth_password", self.authenticate_password_extras_password.text()) # Whether we use bridges if self.tor_bridges_no_bridges_radio.isChecked(): settings.set("no_bridges", True) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", False) settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_obfs4_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", True) settings.set("tor_bridges_use_meek_lite_azure", False) settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_meek_lite_azure_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", True) settings.set("tor_bridges_use_custom_bridges", "") if self.tor_bridges_use_custom_radio.isChecked(): settings.set("no_bridges", False) settings.set("tor_bridges_use_obfs4", False) settings.set("tor_bridges_use_meek_lite_azure", False) # Insert a 'Bridge' line at the start of each bridge. # This makes it easier to copy/paste a set of bridges # provided from https://bridges.torproject.org new_bridges = [] bridges = self.tor_bridges_use_custom_textbox.toPlainText().split( "\n") bridges_valid = False for bridge in bridges: if bridge != "": # Check the syntax of the custom bridge to make sure it looks legitimate ipv4_pattern = re.compile( "(obfs4\s+)?(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):([0-9]+)(\s+)([A-Z0-9]+)(.+)$" ) ipv6_pattern = re.compile( "(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$" ) meek_lite_pattern = re.compile( "(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)" ) if (ipv4_pattern.match(bridge) or ipv6_pattern.match(bridge) or meek_lite_pattern.match(bridge)): new_bridges.append("".join(["Bridge ", bridge, "\n"])) bridges_valid = True if bridges_valid: new_bridges = "".join(new_bridges) settings.set("tor_bridges_use_custom_bridges", new_bridges) else: Alert(self.common, strings._("gui_settings_tor_bridges_invalid")) settings.set("no_bridges", True) return False return settings def closeEvent(self, e): self.common.log("SettingsDialog", "closeEvent") # On close, if Tor isn't connected, then quit OnionShare altogether if not self.common.gui.local_only: if not self.common.gui.onion.is_authenticated(): self.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.common.gui.qtapp.quit) def _update_autoupdate_timestamp(self, autoupdate_timestamp): self.common.log("SettingsDialog", "_update_autoupdate_timestamp") if autoupdate_timestamp: dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) last_checked = dt.strftime("%B %d, %Y %H:%M") else: last_checked = strings._("gui_settings_autoupdate_timestamp_never") self.autoupdate_timestamp.setText( strings._("gui_settings_autoupdate_timestamp").format( last_checked)) def _tor_status_update(self, progress, summary): self.tor_status.setText( f"<strong>{strings._('connecting_to_tor')}</strong><br>{progress}% {summary}" ) self.common.gui.qtapp.processEvents() if "Done" in summary: self.tor_status.hide() self._enable_buttons() def _disable_buttons(self): self.common.log("SettingsDialog", "_disable_buttons") self.check_for_updates_button.setEnabled(False) self.connection_type_test_button.setEnabled(False) self.save_button.setEnabled(False) self.cancel_button.setEnabled(False) def _enable_buttons(self): self.common.log("SettingsDialog", "_enable_buttons") # We can't check for updates if we're still not connected to Tor if not self.common.gui.onion.connected_to_tor: self.check_for_updates_button.setEnabled(False) else: self.check_for_updates_button.setEnabled(True) self.connection_type_test_button.setEnabled(True) self.save_button.setEnabled(True) self.cancel_button.setEnabled(True)
def check(self, force=False): self.common.log("UpdateChecker", "check", f"force={force}") # Load the settings settings = Settings(self.common) settings.load() # If force=True, then definitely check if force: check_for_updates = True else: check_for_updates = False # See if it's been 1 day since the last check autoupdate_timestamp = settings.get("autoupdate_timestamp") if autoupdate_timestamp: last_checked = datetime.datetime.fromtimestamp( autoupdate_timestamp) now = datetime.datetime.now() one_day = datetime.timedelta(days=1) if now - last_checked > one_day: check_for_updates = True else: check_for_updates = True # Check for updates if check_for_updates: self.common.log("UpdateChecker", "check", "checking for updates") # Download the latest-version file over Tor try: # User agent string includes OnionShare version and platform user_agent = f"OnionShare {self.common.version}, {self.common.platform}" # If the update is forced, add '?force=1' to the URL, to more # accurately measure daily users path = "/latest-version.txt" if force: path += "?force=1" if Version(self.onion.tor_version) >= Version("0.3.2.9"): onion_domain = ( "lldan5gahapx5k7iafb3s4ikijc4ni7gx5iywdflkba5y2ezyg6sjgyd.onion" ) else: onion_domain = "elx57ue5uyfplgva.onion" self.common.log("UpdateChecker", "check", f"loading http://{onion_domain}{path}") (socks_address, socks_port) = self.onion.get_tor_socks_port() socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port) s = socks.socksocket() s.settimeout(15) # 15 second timeout s.connect((onion_domain, 80)) http_request = f"GET {path} HTTP/1.0\r\n" http_request += f"Host: {onion_domain}\r\n" http_request += f"User-Agent: {user_agent}\r\n" http_request += "\r\n" s.sendall(http_request.encode("utf-8")) http_response = s.recv(1024) latest_version = ( http_response[http_response.find(b"\r\n\r\n"):].strip( ).decode("utf-8")) self.common.log( "UpdateChecker", "check", f"latest OnionShare version: {latest_version}", ) except Exception as e: self.common.log("UpdateChecker", "check", str(e)) self.update_error.emit() raise UpdateCheckerCheckError # Validate that latest_version looks like a version string # This regex is: 1-3 dot-separated numeric components version_re = r"^(\d+\.)?(\d+\.)?(\d+)$" if not re.match(version_re, latest_version): self.update_invalid_version.emit(latest_version) raise UpdateCheckerInvalidLatestVersion(latest_version) # Update the last checked timestamp (dropping the seconds and milliseconds) timestamp = (datetime.datetime.now().replace( microsecond=0).replace(second=0).timestamp()) # Re-load the settings first before saving, just in case they've changed since we started our thread settings.load() settings.set("autoupdate_timestamp", timestamp) settings.save() # Do we need to update? update_url = "https://onionshare.org" installed_version = self.common.version if installed_version < latest_version: self.update_available.emit(update_url, installed_version, latest_version) return # No updates are available self.update_not_available.emit()
class TorSettingsTab(QtWidgets.QWidget): """ Settings dialog. """ close_this_tab = QtCore.Signal() tor_is_connected = QtCore.Signal() tor_is_disconnected = QtCore.Signal() def __init__( self, common, tab_id, are_tabs_active, status_bar, from_autoconnect=False, parent=None, ): super(TorSettingsTab, self).__init__() self.common = common self.common.log("TorSettingsTab", "__init__") self.status_bar = status_bar self.meek = Meek(common, get_tor_paths=self.common.gui.get_tor_paths) self.system = platform.system() self.tab_id = tab_id self.parent = parent self.from_autoconnect = from_autoconnect # Connection type: either automatic, control port, or socket file # Bundled Tor self.connection_type_bundled_radio = QtWidgets.QRadioButton( strings._("gui_settings_connection_type_bundled_option")) self.connection_type_bundled_radio.toggled.connect( self.connection_type_bundled_toggled) # Bundled Tor doesn't work on dev mode in Windows or Mac if (self.system == "Windows" or self.system == "Darwin") and getattr( sys, "onionshare_dev_mode", False): self.connection_type_bundled_radio.setEnabled(False) # Bridge options for bundled tor ( 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, ) = self.common.gui.get_tor_paths() bridges_label = QtWidgets.QLabel( strings._("gui_settings_tor_bridges_label")) bridges_label.setWordWrap(True) self.bridge_use_checkbox = QtWidgets.QCheckBox( strings._("gui_settings_bridge_use_checkbox")) self.bridge_use_checkbox.stateChanged.connect( self.bridge_use_checkbox_state_changed) # Built-in bridge self.bridge_builtin_radio = QtWidgets.QRadioButton( strings._("gui_settings_bridge_radio_builtin")) self.bridge_builtin_radio.toggled.connect( self.bridge_builtin_radio_toggled) self.bridge_builtin_dropdown = QtWidgets.QComboBox() self.bridge_builtin_dropdown.currentTextChanged.connect( self.bridge_builtin_dropdown_changed) if self.obfs4proxy_file_path and os.path.isfile( self.obfs4proxy_file_path): self.bridge_builtin_dropdown.addItem("obfs4") self.bridge_builtin_dropdown.addItem("meek-azure") if self.snowflake_file_path and os.path.isfile( self.snowflake_file_path): self.bridge_builtin_dropdown.addItem("snowflake") # Request a bridge from torproject.org (moat) self.bridge_moat_radio = QtWidgets.QRadioButton( strings._("gui_settings_bridge_moat_radio_option")) self.bridge_moat_radio.toggled.connect(self.bridge_moat_radio_toggled) self.bridge_moat_button = QtWidgets.QPushButton( strings._("gui_settings_bridge_moat_button")) self.bridge_moat_button.clicked.connect( self.bridge_moat_button_clicked) self.bridge_moat_textbox = QtWidgets.QPlainTextEdit() self.bridge_moat_textbox.setMinimumHeight(100) self.bridge_moat_textbox.setMaximumHeight(100) self.bridge_moat_textbox.setReadOnly(True) self.bridge_moat_textbox.setWordWrapMode(QtGui.QTextOption.NoWrap) bridge_moat_textbox_options_layout = QtWidgets.QVBoxLayout() bridge_moat_textbox_options_layout.addWidget(self.bridge_moat_button) bridge_moat_textbox_options_layout.addWidget(self.bridge_moat_textbox) self.bridge_moat_textbox_options = QtWidgets.QWidget() self.bridge_moat_textbox_options.setLayout( bridge_moat_textbox_options_layout) self.bridge_moat_textbox_options.hide() # Custom bridges radio and textbox self.bridge_custom_radio = QtWidgets.QRadioButton( strings._("gui_settings_bridge_custom_radio_option")) self.bridge_custom_radio.toggled.connect( self.bridge_custom_radio_toggled) self.bridge_custom_textbox = QtWidgets.QPlainTextEdit() self.bridge_custom_textbox.setMinimumHeight(100) self.bridge_custom_textbox.setMaximumHeight(100) self.bridge_custom_textbox.setPlaceholderText( strings._("gui_settings_bridge_custom_placeholder")) bridge_custom_textbox_options_layout = QtWidgets.QVBoxLayout() bridge_custom_textbox_options_layout.addWidget( self.bridge_custom_textbox) self.bridge_custom_textbox_options = QtWidgets.QWidget() self.bridge_custom_textbox_options.setLayout( bridge_custom_textbox_options_layout) self.bridge_custom_textbox_options.hide() # Bridge settings layout bridge_settings_layout = QtWidgets.QVBoxLayout() bridge_settings_layout.addWidget(self.bridge_builtin_radio) bridge_settings_layout.addWidget(self.bridge_builtin_dropdown) bridge_settings_layout.addWidget(self.bridge_moat_radio) bridge_settings_layout.addWidget(self.bridge_moat_textbox_options) bridge_settings_layout.addWidget(self.bridge_custom_radio) bridge_settings_layout.addWidget(self.bridge_custom_textbox_options) self.bridge_settings = QtWidgets.QWidget() self.bridge_settings.setLayout(bridge_settings_layout) # Bridges layout/widget bridges_layout = QtWidgets.QVBoxLayout() bridges_layout.addWidget(bridges_label) bridges_layout.addWidget(self.bridge_use_checkbox) bridges_layout.addWidget(self.bridge_settings) self.bridges = QtWidgets.QWidget() self.bridges.setLayout(bridges_layout) # Automatic self.connection_type_automatic_radio = QtWidgets.QRadioButton( strings._("gui_settings_connection_type_automatic_option")) self.connection_type_automatic_radio.toggled.connect( self.connection_type_automatic_toggled) # Control port self.connection_type_control_port_radio = QtWidgets.QRadioButton( strings._("gui_settings_connection_type_control_port_option")) self.connection_type_control_port_radio.toggled.connect( self.connection_type_control_port_toggled) connection_type_control_port_extras_label = QtWidgets.QLabel( strings._("gui_settings_control_port_label")) self.connection_type_control_port_extras_address = QtWidgets.QLineEdit( ) self.connection_type_control_port_extras_port = QtWidgets.QLineEdit() connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() connection_type_control_port_extras_layout.addWidget( connection_type_control_port_extras_label) connection_type_control_port_extras_layout.addWidget( self.connection_type_control_port_extras_address) connection_type_control_port_extras_layout.addWidget( self.connection_type_control_port_extras_port) self.connection_type_control_port_extras = QtWidgets.QWidget() self.connection_type_control_port_extras.setLayout( connection_type_control_port_extras_layout) self.connection_type_control_port_extras.hide() # Socket file self.connection_type_socket_file_radio = QtWidgets.QRadioButton( strings._("gui_settings_connection_type_socket_file_option")) self.connection_type_socket_file_radio.toggled.connect( self.connection_type_socket_file_toggled) connection_type_socket_file_extras_label = QtWidgets.QLabel( strings._("gui_settings_socket_file_label")) self.connection_type_socket_file_extras_path = QtWidgets.QLineEdit() connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() connection_type_socket_file_extras_layout.addWidget( connection_type_socket_file_extras_label) connection_type_socket_file_extras_layout.addWidget( self.connection_type_socket_file_extras_path) self.connection_type_socket_file_extras = QtWidgets.QWidget() self.connection_type_socket_file_extras.setLayout( connection_type_socket_file_extras_layout) self.connection_type_socket_file_extras.hide() # Tor SOCKS address and port gui_settings_socks_label = QtWidgets.QLabel( strings._("gui_settings_socks_label")) self.connection_type_socks_address = QtWidgets.QLineEdit() self.connection_type_socks_port = QtWidgets.QLineEdit() connection_type_socks_layout = QtWidgets.QHBoxLayout() connection_type_socks_layout.addWidget(gui_settings_socks_label) connection_type_socks_layout.addWidget( self.connection_type_socks_address) connection_type_socks_layout.addWidget(self.connection_type_socks_port) self.connection_type_socks = QtWidgets.QWidget() self.connection_type_socks.setLayout(connection_type_socks_layout) self.connection_type_socks.hide() # Authentication options self.authenticate_no_auth_checkbox = QtWidgets.QCheckBox( strings._("gui_settings_authenticate_no_auth_option")) self.authenticate_no_auth_checkbox.toggled.connect( self.authenticate_no_auth_toggled) authenticate_password_extras_label = QtWidgets.QLabel( strings._("gui_settings_password_label")) self.authenticate_password_extras_password = QtWidgets.QLineEdit("") authenticate_password_extras_layout = QtWidgets.QHBoxLayout() authenticate_password_extras_layout.addWidget( authenticate_password_extras_label) authenticate_password_extras_layout.addWidget( self.authenticate_password_extras_password) self.authenticate_password_extras = QtWidgets.QWidget() self.authenticate_password_extras.setLayout( authenticate_password_extras_layout) self.authenticate_password_extras.hide() # Group for Tor settings tor_settings_layout = QtWidgets.QVBoxLayout() tor_settings_layout.addWidget(self.connection_type_control_port_extras) tor_settings_layout.addWidget(self.connection_type_socket_file_extras) tor_settings_layout.addWidget(self.connection_type_socks) tor_settings_layout.addWidget(self.authenticate_no_auth_checkbox) tor_settings_layout.addWidget(self.authenticate_password_extras) self.tor_settings_group = QtWidgets.QGroupBox( strings._("gui_settings_controller_extras_label")) self.tor_settings_group.setLayout(tor_settings_layout) self.tor_settings_group.hide() # Put the radios into their own group so they are exclusive connection_type_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_radio_group_layout.addWidget( self.connection_type_bundled_radio) connection_type_radio_group_layout.addWidget( self.connection_type_automatic_radio) connection_type_radio_group_layout.addWidget( self.connection_type_control_port_radio) connection_type_radio_group_layout.addWidget( self.connection_type_socket_file_radio) connection_type_radio_group_layout.addStretch() connection_type_radio_group = QtWidgets.QGroupBox( strings._("gui_settings_connection_type_label")) connection_type_radio_group.setLayout( connection_type_radio_group_layout) # Quickstart settings self.autoconnect_checkbox = QtWidgets.QCheckBox( strings._("gui_enable_autoconnect_checkbox")) self.autoconnect_checkbox.toggled.connect(self.autoconnect_toggled) left_column_settings = QtWidgets.QVBoxLayout() connection_type_radio_group.setFixedHeight(300) left_column_settings.addWidget(connection_type_radio_group) left_column_settings.addSpacing(20) left_column_settings.addWidget(self.autoconnect_checkbox) left_column_settings.addStretch() left_column_settings.setContentsMargins(0, 0, 0, 0) left_column_setting_widget = QtWidgets.QWidget() left_column_setting_widget.setLayout(left_column_settings) # The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges) connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_bridges_radio_group_layout.addWidget(self.bridges) self.connection_type_bridges_radio_group = QtWidgets.QGroupBox( strings._("gui_settings_tor_bridges")) self.connection_type_bridges_radio_group.setLayout( connection_type_bridges_radio_group_layout) self.connection_type_bridges_radio_group.hide() # Connection type layout connection_type_layout = QtWidgets.QVBoxLayout() connection_type_layout.addWidget(self.tor_settings_group) connection_type_layout.addWidget( self.connection_type_bridges_radio_group) connection_type_layout.addStretch() # Settings are in columns columns_layout = QtWidgets.QHBoxLayout() columns_layout.addWidget(left_column_setting_widget) columns_layout.addSpacing(20) columns_layout.addLayout(connection_type_layout, stretch=1) columns_wrapper = QtWidgets.QWidget() columns_wrapper.setFixedHeight(400) columns_wrapper.setLayout(columns_layout) # Tor connection widget self.tor_con = TorConnectionWidget(self.common, self.status_bar) self.tor_con.success.connect(self.tor_con_success) self.tor_con.fail.connect(self.tor_con_fail) self.tor_con.hide() self.tor_con_type = None # Error label self.error_label = QtWidgets.QLabel() self.error_label.setStyleSheet( self.common.gui.css["tor_settings_error"]) self.error_label.setWordWrap(True) # Buttons self.test_tor_button = QtWidgets.QPushButton( strings._("gui_settings_connection_type_test_button")) self.test_tor_button.clicked.connect(self.test_tor_clicked) self.save_button = QtWidgets.QPushButton( strings._("gui_settings_button_save")) self.save_button.clicked.connect(self.save_clicked) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addWidget(self.error_label, stretch=1) buttons_layout.addSpacing(20) buttons_layout.addWidget(self.test_tor_button) buttons_layout.addWidget(self.save_button) # Main layout main_layout = QtWidgets.QVBoxLayout() main_layout.addWidget(columns_wrapper) main_layout.addStretch() main_layout.addWidget(self.tor_con) main_layout.addStretch() main_layout.addLayout(buttons_layout) self.main_widget = QtWidgets.QWidget() self.main_widget.setLayout(main_layout) # Tabs are active label active_tabs_label = QtWidgets.QLabel( strings._("gui_settings_stop_active_tabs_label")) active_tabs_label.setAlignment(QtCore.Qt.AlignHCenter) # Active tabs layout active_tabs_layout = QtWidgets.QVBoxLayout() active_tabs_layout.addStretch() active_tabs_layout.addWidget(active_tabs_label) active_tabs_layout.addStretch() self.active_tabs_widget = QtWidgets.QWidget() self.active_tabs_widget.setLayout(active_tabs_layout) layout = QtWidgets.QVBoxLayout() layout.addWidget(self.main_widget) layout.addWidget(self.active_tabs_widget) self.setLayout(layout) self.active_tabs_changed(are_tabs_active) self.reload_settings() def reload_settings(self): # Load settings, and fill them in self.old_settings = Settings(self.common) self.old_settings.load() # Check if autoconnect was enabled if self.old_settings.get("auto_connect"): self.autoconnect_checkbox.setCheckState(QtCore.Qt.Checked) 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) else: # If bundled tor is disabled, fallback to automatic self.connection_type_automatic_radio.setChecked(True) elif connection_type == "automatic": self.connection_type_automatic_radio.setChecked(True) elif connection_type == "control_port": 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( 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_checkbox.setCheckState(QtCore.Qt.Checked) else: self.authenticate_no_auth_checkbox.setChecked(QtCore.Qt.Unchecked) self.authenticate_password_extras_password.setText( self.old_settings.get("auth_password")) if self.old_settings.get("bridges_enabled"): self.bridge_use_checkbox.setCheckState(QtCore.Qt.Checked) self.bridge_settings.show() bridges_type = self.old_settings.get("bridges_type") if bridges_type == "built-in": self.bridge_builtin_radio.setChecked(True) self.bridge_builtin_dropdown.show() self.bridge_moat_radio.setChecked(False) self.bridge_moat_textbox_options.hide() self.bridge_custom_radio.setChecked(False) self.bridge_custom_textbox_options.hide() bridges_builtin_pt = self.old_settings.get( "bridges_builtin_pt") if bridges_builtin_pt == "obfs4": self.bridge_builtin_dropdown.setCurrentText("obfs4") elif bridges_builtin_pt == "meek-azure": self.bridge_builtin_dropdown.setCurrentText("meek-azure") else: self.bridge_builtin_dropdown.setCurrentText("snowflake") self.bridge_moat_textbox_options.hide() self.bridge_custom_textbox_options.hide() elif bridges_type == "moat": self.bridge_builtin_radio.setChecked(False) self.bridge_builtin_dropdown.hide() self.bridge_moat_radio.setChecked(True) self.bridge_moat_textbox_options.show() self.bridge_custom_radio.setChecked(False) self.bridge_custom_textbox_options.hide() else: self.bridge_builtin_radio.setChecked(False) self.bridge_builtin_dropdown.hide() self.bridge_moat_radio.setChecked(False) self.bridge_moat_textbox_options.hide() self.bridge_custom_radio.setChecked(True) self.bridge_custom_textbox_options.show() bridges_moat = self.old_settings.get("bridges_moat") self.bridge_moat_textbox.document().setPlainText(bridges_moat) bridges_custom = self.old_settings.get("bridges_custom") self.bridge_custom_textbox.document().setPlainText(bridges_custom) else: self.bridge_use_checkbox.setCheckState(QtCore.Qt.Unchecked) self.bridge_settings.hide() def autoconnect_toggled(self): """ Auto connect checkbox clicked """ self.common.log("TorSettingsTab", "autoconnect_checkbox_clicked") def active_tabs_changed(self, are_tabs_active): if are_tabs_active: self.main_widget.hide() self.active_tabs_widget.show() else: self.main_widget.show() self.active_tabs_widget.hide() def connection_type_bundled_toggled(self, checked): """ Connection type bundled was toggled """ self.common.log("TorSettingsTab", "connection_type_bundled_toggled") if checked: self.tor_settings_group.hide() self.connection_type_socks.hide() self.connection_type_bridges_radio_group.show() def bridge_use_checkbox_state_changed(self, state): """ 'Use a bridge' checkbox changed """ if state == QtCore.Qt.Checked: self.bridge_settings.show() self.bridge_builtin_radio.click() self.bridge_builtin_dropdown.setCurrentText("obfs4") else: self.bridge_settings.hide() def bridge_builtin_radio_toggled(self, checked): """ 'Select a built-in bridge' radio button toggled """ if checked: self.bridge_builtin_dropdown.show() self.bridge_custom_textbox_options.hide() self.bridge_moat_textbox_options.hide() def bridge_builtin_dropdown_changed(self, selection): """ Build-in bridge selection changed """ if selection == "meek-azure": # Alert the user about meek's costliness if it looks like they're turning it on if not self.old_settings.get("bridges_builtin_pt") == "meek-azure": Alert( self.common, strings._("gui_settings_meek_lite_expensive_warning"), QtWidgets.QMessageBox.Warning, ) def bridge_moat_radio_toggled(self, checked): """ Moat (request bridge) bridges option was toggled. If checked, show moat bridge options. """ if checked: self.bridge_builtin_dropdown.hide() self.bridge_custom_textbox_options.hide() self.bridge_moat_textbox_options.show() def bridge_moat_button_clicked(self): """ Request new bridge button clicked """ self.common.log("TorSettingsTab", "bridge_moat_button_clicked") moat_dialog = MoatDialog(self.common, self.meek) moat_dialog.got_bridges.connect(self.bridge_moat_got_bridges) moat_dialog.exec_() def bridge_moat_got_bridges(self, bridges): """ Got new bridges from moat """ self.common.log("TorSettingsTab", "bridge_moat_got_bridges") self.bridge_moat_textbox.document().setPlainText(bridges) self.bridge_moat_textbox.show() def bridge_custom_radio_toggled(self, checked): """ Custom bridges option was toggled. If checked, show custom bridge options. """ if checked: self.bridge_builtin_dropdown.hide() self.bridge_moat_textbox_options.hide() self.bridge_custom_textbox_options.show() def connection_type_automatic_toggled(self, checked): """ Connection type automatic was toggled. If checked, hide authentication fields. """ self.common.log("TorSettingsTab", "connection_type_automatic_toggled") if checked: self.tor_settings_group.hide() self.connection_type_socks.hide() self.connection_type_bridges_radio_group.hide() def connection_type_control_port_toggled(self, checked): """ Connection type control port was toggled. If checked, show extra fields for Tor control address and port. If unchecked, hide those extra fields. """ self.common.log("TorSettingsTab", "connection_type_control_port_toggled") if checked: self.tor_settings_group.show() self.connection_type_control_port_extras.show() self.connection_type_socks.show() self.connection_type_bridges_radio_group.hide() else: self.connection_type_control_port_extras.hide() def connection_type_socket_file_toggled(self, checked): """ Connection type socket file was toggled. If checked, show extra fields for socket file. If unchecked, hide those extra fields. """ self.common.log("TorSettingsTab", "connection_type_socket_file_toggled") if checked: self.tor_settings_group.show() self.connection_type_socket_file_extras.show() self.connection_type_socks.show() self.connection_type_bridges_radio_group.hide() else: self.connection_type_socket_file_extras.hide() def authenticate_no_auth_toggled(self, checked): """ Authentication option no authentication was toggled. """ self.common.log("TorSettingsTab", "authenticate_no_auth_toggled") if checked: self.authenticate_password_extras.hide() else: self.authenticate_password_extras.show() def test_tor_clicked(self): """ Test Tor Settings button clicked. With the given settings, see if we can successfully connect and authenticate to Tor. """ self.common.log("TorSettingsTab", "test_tor_clicked") self.error_label.setText("") settings = self.settings_from_fields() if not settings: return self.test_tor_button.hide() self.save_button.hide() self.test_onion = Onion( self.common, use_tmp_dir=True, get_tor_paths=self.common.gui.get_tor_paths, ) self.tor_con_type = "test" self.tor_con.show() self.tor_con.start(settings, True, self.test_onion) def save_clicked(self): """ Save button clicked. Save current settings to disk. """ self.common.log("TorSettingsTab", "save_clicked") self.error_label.setText("") 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 settings = self.settings_from_fields() if settings: # Save the new settings settings.save() # If Tor isn't connected, or if Tor settings have changed, Reinitialize # the Onion object reboot_onion = False if not self.common.gui.local_only and not ( self.from_autoconnect and not settings.get("auto_connect")): if self.common.gui.onion.is_authenticated(): self.common.log("TorSettingsTab", "save_clicked", "Connected to Tor") 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", "bridges_enabled", "bridges_type", "bridges_builtin_pt", "bridges_moat", "bridges_custom", ], ): reboot_onion = True else: self.common.log("TorSettingsTab", "save_clicked", "Not connected to Tor") # Tor isn't connected, so try connecting reboot_onion = True # Do we need to reinitialize Tor? if reboot_onion: # Tell the tabs that Tor is disconnected self.tor_is_disconnected.emit() # Reinitialize the Onion object self.common.log("TorSettingsTab", "save_clicked", "rebooting the Onion") self.common.gui.onion.cleanup() self.test_tor_button.hide() self.save_button.hide() self.tor_con_type = "save" self.tor_con.show() self.tor_con.start(settings) else: self.parent.close_this_tab.emit() else: self.parent.close_this_tab.emit() def tor_con_success(self): """ Finished testing tor connection. """ self.tor_con.hide() self.test_tor_button.show() self.save_button.show() if self.tor_con_type == "test": Alert( self.common, strings._("settings_test_success").format( self.test_onion.tor_version, self.test_onion.supports_ephemeral, self.test_onion.supports_stealth, self.test_onion.supports_v3_onions, ), title=strings._("gui_settings_connection_type_test_button"), ) self.test_onion.cleanup() elif self.tor_con_type == "save": if (self.common.gui.onion.is_authenticated() and not self.tor_con.wasCanceled()): # Tell the tabs that Tor is connected self.tor_is_connected.emit() # Close the tab self.parent.close_this_tab.emit() self.tor_con_type = None def tor_con_fail(self, msg): """ Finished testing tor connection. """ self.tor_con.hide() self.test_tor_button.show() self.save_button.show() self.error_label.setText(msg) if self.tor_con_type == "test": self.test_onion.cleanup() self.tor_con_type = None def settings_from_fields(self): """ Return a Settings object that's full of values from the settings dialog. """ self.common.log("TorSettingsTab", "settings_from_fields") settings = Settings(self.common) settings.load() # To get the last update timestamp # autoconnect settings.set("auto_connect", self.autoconnect_checkbox.isChecked()) # Tor connection if self.connection_type_bundled_radio.isChecked(): settings.set("connection_type", "bundled") if self.connection_type_automatic_radio.isChecked(): settings.set("connection_type", "automatic") if self.connection_type_control_port_radio.isChecked(): settings.set("connection_type", "control_port") if self.connection_type_socket_file_radio.isChecked(): 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("socket_file_path", self.connection_type_socket_file_extras_path.text()) settings.set("socks_address", self.connection_type_socks_address.text()) settings.set("socks_port", self.connection_type_socks_port.text()) if self.authenticate_no_auth_checkbox.checkState( ) == QtCore.Qt.Checked: settings.set("auth_type", "no_auth") else: settings.set("auth_type", "password") settings.set("auth_password", self.authenticate_password_extras_password.text()) # Whether we use bridges if self.bridge_use_checkbox.checkState() == QtCore.Qt.Checked: settings.set("bridges_enabled", True) if self.bridge_builtin_radio.isChecked(): settings.set("bridges_type", "built-in") selection = self.bridge_builtin_dropdown.currentText() settings.set("bridges_builtin_pt", selection) if self.bridge_moat_radio.isChecked(): settings.set("bridges_type", "moat") moat_bridges = self.bridge_moat_textbox.toPlainText() if (self.connection_type_bundled_radio.isChecked() and moat_bridges.strip() == ""): self.error_label.setText( strings._("gui_settings_moat_bridges_invalid")) return False settings.set("bridges_moat", moat_bridges) if self.bridge_custom_radio.isChecked(): settings.set("bridges_type", "custom") bridges = self.bridge_custom_textbox.toPlainText().split("\n") bridges_valid = self.common.check_bridges_valid(bridges) if bridges_valid: new_bridges = "\n".join(bridges_valid) + "\n" settings.set("bridges_custom", new_bridges) else: self.error_label.setText( strings._("gui_settings_tor_bridges_invalid")) return False else: settings.set("bridges_enabled", False) return settings def closeEvent(self, e): self.common.log("TorSettingsTab", "closeEvent") # On close, if Tor isn't connected, then quit OnionShare altogether if not self.common.gui.local_only: if not self.common.gui.onion.is_authenticated(): self.common.log( "TorSettingsTab", "closeEvent", "Closing while not connected to Tor", ) # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.common.gui.qtapp.quit) def settings_have_changed(self): # Global settings have changed self.common.log("TorSettingsTab", "settings_have_changed")