summaryrefslogtreecommitdiff
path: root/src/leap/gui/mainwindow.py
diff options
context:
space:
mode:
authorKali Kaneko <kali@leap.se>2013-08-12 13:25:44 +0200
committerKali Kaneko <kali@leap.se>2013-08-12 13:25:44 +0200
commit6da8d09846db4d2eed01e488bc6a6f5ba48b959f (patch)
tree3b82e8c4e14b1730ff292b6eb632c145dafb332a /src/leap/gui/mainwindow.py
parent00d98a47c60764475d97df1c2eb847e20a77cae5 (diff)
move everything into bitmask namespace
Diffstat (limited to 'src/leap/gui/mainwindow.py')
-rw-r--r--src/leap/gui/mainwindow.py1537
1 files changed, 0 insertions, 1537 deletions
diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py
deleted file mode 100644
index 5ace1043..00000000
--- a/src/leap/gui/mainwindow.py
+++ /dev/null
@@ -1,1537 +0,0 @@
-# -*- coding: utf-8 -*-
-# mainwindow.py
-# Copyright (C) 2013 LEAP
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""
-Main window for the leap client
-"""
-import logging
-import os
-import platform
-import tempfile
-from functools import partial
-
-import keyring
-
-from PySide import QtCore, QtGui
-from twisted.internet import threads
-
-from leap.common.check import leap_assert
-from leap.common.events import register
-from leap.common.events import events_pb2 as proto
-from leap.config.leapsettings import LeapSettings
-from leap.config.providerconfig import ProviderConfig
-from leap.crypto.srpauth import SRPAuth
-from leap.gui.loggerwindow import LoggerWindow
-from leap.gui.wizard import Wizard
-from leap.gui.login import LoginWidget
-from leap.gui.statuspanel import StatusPanelWidget
-from leap.services.eip.eipbootstrapper import EIPBootstrapper
-from leap.services.eip.eipconfig import EIPConfig
-from leap.services.eip.providerbootstrapper import ProviderBootstrapper
-# XXX: Soledad might not work out of the box in Windows, issue #2932
-from leap.services.soledad.soledadbootstrapper import SoledadBootstrapper
-from leap.services.mail.smtpbootstrapper import SMTPBootstrapper
-from leap.services.mail import imap
-from leap.platform_init import IS_WIN, IS_MAC
-from leap.platform_init.initializers import init_platform
-
-from leap.services.eip.vpnprocess import VPN
-from leap.services.eip.vpnprocess import OpenVPNAlreadyRunning
-from leap.services.eip.vpnprocess import AlienOpenVPNAlreadyRunning
-
-from leap.services.eip.vpnlaunchers import VPNLauncherException
-from leap.services.eip.vpnlaunchers import OpenVPNNotFoundException
-from leap.services.eip.vpnlaunchers import EIPNoPkexecAvailable
-from leap.services.eip.vpnlaunchers import EIPNoPolkitAuthAgentAvailable
-from leap.services.eip.vpnlaunchers import EIPNoTunKextLoaded
-
-from leap.util import __version__ as VERSION
-from leap.util.keyring_helpers import has_keyring
-
-from leap.services.mail.smtpconfig import SMTPConfig
-
-if IS_WIN:
- from leap.platform_init.locks import WindowsLock
- from leap.platform_init.locks import raise_window_ack
-
-from ui_mainwindow import Ui_MainWindow
-
-logger = logging.getLogger(__name__)
-
-
-class MainWindow(QtGui.QMainWindow):
- """
- Main window for login and presenting status updates to the user
- """
-
- # StackedWidget indexes
- LOGIN_INDEX = 0
- EIP_STATUS_INDEX = 1
-
- # Keyring
- KEYRING_KEY = "bitmask"
-
- # SMTP
- PORT_KEY = "port"
- IP_KEY = "ip_address"
-
- OPENVPN_SERVICE = "openvpn"
- MX_SERVICE = "mx"
-
- # Signals
- new_updates = QtCore.Signal(object)
- raise_window = QtCore.Signal([])
- soledad_ready = QtCore.Signal([])
-
- # We use this flag to detect abnormal terminations
- user_stopped_eip = False
-
- def __init__(self, quit_callback,
- standalone=False,
- openvpn_verb=1,
- bypass_checks=False):
- """
- Constructor for the client main window
-
- :param quit_callback: Function to be called when closing
- the application.
- :type quit_callback: callable
-
- :param standalone: Set to true if the app should use configs
- inside its pwd
- :type standalone: bool
-
- :param bypass_checks: Set to true if the app should bypass
- first round of checks for CA
- certificates at bootstrap
- :type bypass_checks: bool
- """
- QtGui.QMainWindow.__init__(self)
-
- # register leap events
- register(signal=proto.UPDATER_NEW_UPDATES,
- callback=self._new_updates_available,
- reqcbk=lambda req, resp: None) # make rpc call async
- register(signal=proto.RAISE_WINDOW,
- callback=self._on_raise_window_event,
- reqcbk=lambda req, resp: None) # make rpc call async
-
- self._quit_callback = quit_callback
-
- self._updates_content = ""
-
- self.ui = Ui_MainWindow()
- self.ui.setupUi(self)
-
- self._settings = LeapSettings(standalone)
-
- self._login_widget = LoginWidget(
- self._settings,
- self.ui.stackedWidget.widget(self.LOGIN_INDEX))
- self.ui.loginLayout.addWidget(self._login_widget)
-
- # Signals
- # TODO separate logic from ui signals.
-
- self._login_widget.login.connect(self._login)
- self._login_widget.cancel_login.connect(self._cancel_login)
- self._login_widget.show_wizard.connect(
- self._launch_wizard)
-
- self.ui.btnShowLog.clicked.connect(self._show_logger_window)
-
- self._status_panel = StatusPanelWidget(
- self.ui.stackedWidget.widget(self.EIP_STATUS_INDEX))
- self.ui.statusLayout.addWidget(self._status_panel)
-
- self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX)
-
- self._status_panel.start_eip.connect(self._start_eip)
- self._status_panel.stop_eip.connect(self._stop_eip)
-
- # This is loaded only once, there's a bug when doing that more
- # than once
- ProviderConfig.standalone = standalone
- EIPConfig.standalone = standalone
- self._standalone = standalone
- self._provider_config = ProviderConfig()
- # Used for automatic start of EIP
- self._provisional_provider_config = ProviderConfig()
- self._eip_config = EIPConfig()
-
- self._already_started_eip = False
-
- # This is created once we have a valid provider config
- self._srp_auth = None
- self._logged_user = None
-
- # This thread is always running, although it's quite
- # lightweight when it's done setting up provider
- # configuration and certificate.
- self._provider_bootstrapper = ProviderBootstrapper(bypass_checks)
-
- # Intermediate stages, only do something if there was an error
- self._provider_bootstrapper.name_resolution.connect(
- self._intermediate_stage)
- self._provider_bootstrapper.https_connection.connect(
- self._intermediate_stage)
- self._provider_bootstrapper.download_ca_cert.connect(
- self._intermediate_stage)
-
- # Important stages, loads the provider config and checks
- # certificates
- self._provider_bootstrapper.download_provider_info.connect(
- self._load_provider_config)
- self._provider_bootstrapper.check_api_certificate.connect(
- self._provider_config_loaded)
-
- # This thread is similar to the provider bootstrapper
- self._eip_bootstrapper = EIPBootstrapper()
-
- self._eip_bootstrapper.download_config.connect(
- self._eip_intermediate_stage)
- self._eip_bootstrapper.download_client_certificate.connect(
- self._finish_eip_bootstrap)
-
- self._soledad_bootstrapper = SoledadBootstrapper()
- self._soledad_bootstrapper.download_config.connect(
- self._soledad_intermediate_stage)
- self._soledad_bootstrapper.gen_key.connect(
- self._soledad_bootstrapped_stage)
-
- self._smtp_bootstrapper = SMTPBootstrapper()
- self._smtp_bootstrapper.download_config.connect(
- self._smtp_bootstrapped_stage)
-
- self._vpn = VPN(openvpn_verb=openvpn_verb)
- self._vpn.qtsigs.state_changed.connect(
- self._status_panel.update_vpn_state)
- self._vpn.qtsigs.status_changed.connect(
- self._status_panel.update_vpn_status)
- self._vpn.qtsigs.process_finished.connect(
- self._eip_finished)
-
- self.ui.action_log_out.setEnabled(False)
- self.ui.action_log_out.triggered.connect(self._logout)
- self.ui.action_about_leap.triggered.connect(self._about)
- self.ui.action_quit.triggered.connect(self.quit)
- self.ui.action_wizard.triggered.connect(self._launch_wizard)
- self.ui.action_show_logs.triggered.connect(self._show_logger_window)
- self.raise_window.connect(self._do_raise_mainwindow)
-
- # Used to differentiate between real quits and close to tray
- self._really_quit = False
-
- self._systray = None
-
- self._action_eip_provider = QtGui.QAction(
- self.tr("No default provider"), self)
- self._action_eip_provider.setEnabled(False)
- self._action_eip_status = QtGui.QAction(
- self.tr("Encrypted internet is OFF"),
- self)
- self._action_eip_status.setEnabled(False)
-
- self._status_panel.set_action_eip_status(
- self._action_eip_status)
-
- self._action_eip_startstop = QtGui.QAction(
- self.tr("Turn OFF"), self)
- self._action_eip_startstop.triggered.connect(
- self._stop_eip)
- self._action_eip_startstop.setEnabled(False)
- self._status_panel.set_action_eip_startstop(
- self._action_eip_startstop)
-
- self._action_visible = QtGui.QAction(self.tr("Hide Main Window"), self)
- self._action_visible.triggered.connect(self._toggle_visible)
-
- self._enabled_services = []
-
- self._center_window()
-
- self.ui.lblNewUpdates.setVisible(False)
- self.ui.btnMore.setVisible(False)
- self.ui.btnMore.clicked.connect(self._updates_details)
-
- self.new_updates.connect(self._react_to_new_updates)
- self.soledad_ready.connect(self._start_imap_service)
-
- init_platform()
-
- self._wizard = None
- self._wizard_firstrun = False
-
- self._logger_window = None
-
- self._bypass_checks = bypass_checks
-
- self._soledad = None
- self._keymanager = None
- self._imap_service = None
-
- self._login_defer = None
- self._download_provider_defer = None
-
- self._smtp_config = SMTPConfig()
-
- if self._first_run():
- self._wizard_firstrun = True
- self._wizard = Wizard(standalone=standalone,
- bypass_checks=bypass_checks)
- # Give this window time to finish init and then show the wizard
- QtCore.QTimer.singleShot(1, self._launch_wizard)
- self._wizard.accepted.connect(self._finish_init)
- self._wizard.rejected.connect(self._rejected_wizard)
- else:
- self._finish_init()
-
- def _rejected_wizard(self):
- """
- SLOT
- TRIGGERS: self._wizard.rejected
-
- Called if the wizard has been cancelled or closed before
- finishing.
- """
- if self._wizard_firstrun:
- self._settings.set_properprovider(False)
- self.quit()
- else:
- self._finish_init()
-
- def _launch_wizard(self):
- """
- SLOT
- TRIGGERS:
- self._login_widget.show_wizard
- self.ui.action_wizard.triggered
-
- Also called in first run.
-
- Launches the wizard, creating the object itself if not already
- there.
- """
- if self._wizard is None:
- self._wizard = Wizard(bypass_checks=self._bypass_checks)
- self._wizard.accepted.connect(self._finish_init)
- self._wizard.rejected.connect(self._wizard.close)
-
- self.setVisible(False)
- # Do NOT use exec_, it will use a child event loop!
- # Refer to http://www.themacaque.com/?p=1067 for funny details.
- self._wizard.show()
- if IS_MAC:
- self._wizard.raise_()
- self._wizard.finished.connect(self._wizard_finished)
-
- def _wizard_finished(self):
- """
- SLOT
- TRIGGERS
- self._wizard.finished
-
- Called when the wizard has finished.
- """
- self.setVisible(True)
-
- def _get_leap_logging_handler(self):
- """
- Gets the leap handler from the top level logger
-
- :return: a logging handler or None
- :rtype: LeapLogHandler or None
- """
- from leap.util.leap_log_handler import LeapLogHandler
- leap_logger = logging.getLogger('leap')
- for h in leap_logger.handlers:
- if isinstance(h, LeapLogHandler):
- return h
- return None
-
- def _show_logger_window(self):
- """
- SLOT
- TRIGGERS:
- self.ui.action_show_logs.triggered
- self.ui.btnShowLog.clicked
-
- Displays the window with the history of messages logged until now
- and displays the new ones on arrival.
- """
- if self._logger_window is None:
- leap_log_handler = self._get_leap_logging_handler()
- if leap_log_handler is None:
- logger.error('Leap logger handler not found')
- else:
- self._logger_window = LoggerWindow(handler=leap_log_handler)
- self._logger_window.setVisible(
- not self._logger_window.isVisible())
- self.ui.btnShowLog.setChecked(self._logger_window.isVisible())
- else:
- self._logger_window.setVisible(not self._logger_window.isVisible())
- self.ui.btnShowLog.setChecked(self._logger_window.isVisible())
-
- self._logger_window.finished.connect(self._uncheck_logger_button)
-
- def _uncheck_logger_button(self):
- """
- SLOT
- Sets the checked state of the loggerwindow button to false.
- """
- self.ui.btnShowLog.setChecked(False)
-
- def _new_updates_available(self, req):
- """
- Callback for the new updates event
-
- :param req: Request type
- :type req: leap.common.events.events_pb2.SignalRequest
- """
- self.new_updates.emit(req)
-
- def _react_to_new_updates(self, req):
- """
- SLOT
- TRIGGER: self._new_updates_available
-
- Displays the new updates label and sets the updates_content
- """
- self.moveToThread(QtCore.QCoreApplication.instance().thread())
- self.ui.lblNewUpdates.setVisible(True)
- self.ui.btnMore.setVisible(True)
- self._updates_content = req.content
-
- def _updates_details(self):
- """
- SLOT
- TRIGGER: self.ui.btnMore.clicked
-
- Parses and displays the updates details
- """
- msg = self.tr("The Bitmask app is ready to update, please"
- " restart the application.")
-
- # We assume that if there is nothing in the contents, then
- # the Bitmask bundle is what needs updating.
- if len(self._updates_content) > 0:
- files = self._updates_content.split(", ")
- files_str = ""
- for f in files:
- final_name = f.replace("/data/", "")
- final_name = final_name.replace(".thp", "")
- files_str += final_name
- files_str += "\n"
- msg += self.tr(" The following components will be updated:\n%s") \
- % (files_str,)
-
- QtGui.QMessageBox.information(self,
- self.tr("Updates available"),
- msg)
-
- def _finish_init(self):
- """
- SLOT
- TRIGGERS:
- self._wizard.accepted
-
- Also called at the end of the constructor if not first run,
- and after _rejected_wizard if not first run.
-
- Implements the behavior after either constructing the
- mainwindow object, loading the saved user/password, or after
- the wizard has been executed.
- """
- # XXX: May be this can be divided into two methods?
-
- self._login_widget.set_providers(self._configured_providers())
- self._show_systray()
- self.show()
- if IS_MAC:
- self.raise_()
-
- if self._wizard:
- possible_username = self._wizard.get_username()
- possible_password = self._wizard.get_password()
-
- # select the configured provider in the combo box
- domain = self._wizard.get_domain()
- self._login_widget.select_provider_by_name(domain)
-
- self._login_widget.set_remember(self._wizard.get_remember())
- self._enabled_services = list(self._wizard.get_services())
- self._settings.set_enabled_services(
- self._login_widget.get_selected_provider(),
- self._enabled_services)
- if possible_username is not None:
- self._login_widget.set_user(possible_username)
- if possible_password is not None:
- self._login_widget.set_password(possible_password)
- self._login()
- self._wizard = None
- self._settings.set_properprovider(True)
- else:
- self._try_autostart_eip()
- if not self._settings.get_remember():
- # nothing to do here
- return
-
- saved_user = self._settings.get_user()
-
- try:
- username, domain = saved_user.split('@')
- except (ValueError, AttributeError) as e:
- # if the saved_user does not contain an '@' or its None
- logger.error('Username@provider malformed. %r' % (e, ))
- saved_user = None
-
- if saved_user is not None and has_keyring():
- # fill the username
- self._login_widget.set_user(username)
-
- # select the configured provider in the combo box
- self._login_widget.select_provider_by_name(domain)
-
- self._login_widget.set_remember(True)
-
- saved_password = None
- try:
- saved_password = keyring.get_password(self.KEYRING_KEY,
- saved_user
- .encode("utf8"))
- except ValueError, e:
- logger.debug("Incorrect Password. %r." % (e,))
-
- if saved_password is not None:
- self._login_widget.set_password(
- saved_password.decode("utf8"))
- self._login()
-
- def _try_autostart_eip(self):
- """
- Tries to autostart EIP
- """
- default_provider = self._settings.get_defaultprovider()
-
- if default_provider is None:
- logger.info("Cannot autostart Encrypted Internet because there is "
- "no default provider configured")
- return
-
- self._action_eip_provider.setText(default_provider)
-
- self._enabled_services = self._settings.get_enabled_services(
- default_provider)
-
- if self._provisional_provider_config.load(
- os.path.join("leap",
- "providers",
- default_provider,
- "provider.json")):
- self._download_eip_config()
- else:
- # XXX: Display a proper message to the user
- logger.error("Unable to load %s config, cannot autostart." %
- (default_provider,))
-
- def _show_systray(self):
- """
- Sets up the systray icon
- """
- if self._systray is not None:
- self._systray.setVisible(True)
- return
-
- # Placeholder actions
- # They are temporary to display the tray as designed
- preferences_action = QtGui.QAction(self.tr("Preferences"), self)
- preferences_action.setEnabled(False)
- help_action = QtGui.QAction(self.tr("Help"), self)
- help_action.setEnabled(False)
-
- systrayMenu = QtGui.QMenu(self)
- systrayMenu.addAction(self._action_visible)
- systrayMenu.addSeparator()
- systrayMenu.addAction(self._action_eip_provider)
- systrayMenu.addAction(self._action_eip_status)
- systrayMenu.addAction(self._action_eip_startstop)
- systrayMenu.addSeparator()
- systrayMenu.addAction(preferences_action)
- systrayMenu.addAction(help_action)
- systrayMenu.addSeparator()
- systrayMenu.addAction(self.ui.action_log_out)
- systrayMenu.addAction(self.ui.action_quit)
- self._systray = QtGui.QSystemTrayIcon(self)
- self._systray.setContextMenu(systrayMenu)
- self._systray.setIcon(self._status_panel.ERROR_ICON_TRAY)
- self._systray.setVisible(True)
- self._systray.activated.connect(self._tray_activated)
-
- self._status_panel.set_systray(self._systray)
-
- def _tray_activated(self, reason=None):
- """
- SLOT
- TRIGGER: self._systray.activated
-
- Displays the context menu from the tray icon
- """
- self._update_hideshow_menu()
-
- context_menu = self._systray.contextMenu()
- if not IS_MAC:
- # for some reason, context_menu.show()
- # is failing in a way beyond my understanding.
- # (not working the first time it's clicked).
- # this works however.
- context_menu.exec_(self._systray.geometry().center())
-
- def _update_hideshow_menu(self):
- """
- Updates the Hide/Show main window menu text based on the
- visibility of the window.
- """
- get_action = lambda visible: (
- self.tr("Show Main Window"),
- self.tr("Hide Main Window"))[int(visible)]
-
- # set labels
- visible = self.isVisible()
- self._action_visible.setText(get_action(visible))
-
- def _toggle_visible(self):
- """
- SLOT
- TRIGGER: self._action_visible.triggered
-
- Toggles the window visibility
- """
- if not self.isVisible():
- self.show()
- self.raise_()
- else:
- self.hide()
-
- self._update_hideshow_menu()
-
- def _center_window(self):
- """
- Centers the mainwindow based on the desktop geometry
- """
- geometry = self._settings.get_geometry()
- state = self._settings.get_windowstate()
-
- if geometry is None:
- app = QtGui.QApplication.instance()
- width = app.desktop().width()
- height = app.desktop().height()
- window_width = self.size().width()
- window_height = self.size().height()
- x = (width / 2.0) - (window_width / 2.0)
- y = (height / 2.0) - (window_height / 2.0)
- self.move(x, y)
- else:
- self.restoreGeometry(geometry)
-
- if state is not None:
- self.restoreState(state)
-
- def _about(self):
- """
- SLOT
- TRIGGERS: self.ui.action_about_leap.triggered
-
- Display the About Bitmask dialog
- """
- QtGui.QMessageBox.about(
- self, self.tr("About Bitmask - %s") % (VERSION,),
- self.tr("Version: <b>%s</b><br>"
- "<br>"
- "Bitmask is the Desktop client application for "
- "the LEAP platform, supporting encrypted internet "
- "proxy, secure email, and secure chat (coming soon).<br>"
- "<br>"
- "LEAP is a non-profit dedicated to giving "
- "all internet users access to secure "
- "communication. Our focus is on adapting "
- "encryption technology to make it easy to use "
- "and widely available. <br>"
- "<br>"
- "<a href='https://leap.se'>More about LEAP"
- "</a>") % (VERSION,))
-
- def changeEvent(self, e):
- """
- Reimplements the changeEvent method to minimize to tray
- """
- if QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \
- e.type() == QtCore.QEvent.WindowStateChange and \
- self.isMinimized():
- self._toggle_visible()
- e.accept()
- return
- QtGui.QMainWindow.changeEvent(self, e)
-
- def closeEvent(self, e):
- """
- Reimplementation of closeEvent to close to tray
- """
- if QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \
- not self._really_quit:
- self._toggle_visible()
- e.ignore()
- return
-
- self._settings.set_geometry(self.saveGeometry())
- self._settings.set_windowstate(self.saveState())
-
- QtGui.QMainWindow.closeEvent(self, e)
-
- def _configured_providers(self):
- """
- Returns the available providers based on the file structure
-
- :rtype: list
- """
-
- # TODO: check which providers have a valid certificate among
- # other things, not just the directories
- providers = []
- try:
- providers = os.listdir(
- os.path.join(self._provider_config.get_path_prefix(),
- "leap",
- "providers"))
- except Exception as e:
- logger.debug("Error listing providers, assume there are none. %r"
- % (e,))
-
- return providers
-
- def _first_run(self):
- """
- Returns True if there are no configured providers. False otherwise
-
- :rtype: bool
- """
- has_provider_on_disk = len(self._configured_providers()) != 0
- is_proper_provider = self._settings.get_properprovider()
- return not (has_provider_on_disk and is_proper_provider)
-
- def _download_provider_config(self):
- """
- Starts the bootstrapping sequence. It will download the
- provider configuration if it's not present, otherwise will
- emit the corresponding signals inmediately
- """
- provider = self._login_widget.get_selected_provider()
-
- pb = self._provider_bootstrapper
- d = pb.run_provider_select_checks(provider, download_if_needed=True)
- self._download_provider_defer = d
-
- def _load_provider_config(self, data):
- """
- SLOT
- TRIGGER: self._provider_bootstrapper.download_provider_info
-
- Once the provider config has been downloaded, this loads the
- self._provider_config instance with it and starts the second
- part of the bootstrapping sequence
-
- :param data: result from the last stage of the
- run_provider_select_checks
- :type data: dict
- """
- if data[self._provider_bootstrapper.PASSED_KEY]:
- provider = self._login_widget.get_selected_provider()
-
- # If there's no loaded provider or
- # we want to connect to other provider...
- if (not self._provider_config.loaded() or
- self._provider_config.get_domain() != provider):
- self._provider_config.load(
- os.path.join("leap", "providers",
- provider, "provider.json"))
-
- if self._provider_config.loaded():
- self._provider_bootstrapper.run_provider_setup_checks(
- self._provider_config,
- download_if_needed=True)
- else:
- self._login_widget.set_status(
- self.tr("Unable to login: Problem with provider"))
- logger.error("Could not load provider configuration.")
- self._login_widget.set_enabled(True)
- else:
- self._login_widget.set_status(
- self.tr("Unable to login: Problem with provider"))
- logger.error(data[self._provider_bootstrapper.ERROR_KEY])
- self._login_widget.set_enabled(True)
-
- def _login(self):
- """
- SLOT
- TRIGGERS:
- self._login_widget.login
-
- Starts the login sequence. Which involves bootstrapping the
- selected provider if the selection is valid (not empty), then
- start the SRP authentication, and as the last step
- bootstrapping the EIP service
- """
- leap_assert(self._provider_config, "We need a provider config")
-
- username = self._login_widget.get_user()
- password = self._login_widget.get_password()
- provider = self._login_widget.get_selected_provider()
-
- self._enabled_services = self._settings.get_enabled_services(
- self._login_widget.get_selected_provider())
-
- if len(provider) == 0:
- self._login_widget.set_status(
- self.tr("Please select a valid provider"))
- return
-
- if len(username) == 0:
- self._login_widget.set_status(
- self.tr("Please provide a valid username"))
- return
-
- if len(password) == 0:
- self._login_widget.set_status(
- self.tr("Please provide a valid Password"))
- return
-
- self._login_widget.set_status(self.tr("Logging in..."), error=False)
- self._login_widget.set_enabled(False)
-
- if self._login_widget.get_remember() and has_keyring():
- # in the keyring and in the settings
- # we store the value 'usename@provider'
- username_domain = (username + '@' + provider).encode("utf8")
- try:
- keyring.set_password(self.KEYRING_KEY,
- username_domain,
- password.encode("utf8"))
- # Only save the username if it was saved correctly in
- # the keyring
- self._settings.set_user(username_domain)
- except Exception as e:
- logger.error("Problem saving data to keyring. %r"
- % (e,))
-
- self._download_provider_config()
-
- def _cancel_login(self):
- """
- SLOT
- TRIGGERS:
- self._login_widget.cancel_login
-
- Stops the login sequence.
- """
- logger.debug("Cancelling log in.")
-
- if self._download_provider_defer:
- logger.debug("Cancelling download provider defer.")
- self._download_provider_defer.cancel()
-
- if self._login_defer:
- logger.debug("Cancelling login defer.")
- self._login_defer.cancel()
-
- def _provider_config_loaded(self, data):
- """
- SLOT
- TRIGGER: self._provider_bootstrapper.check_api_certificate
-
- Once the provider configuration is loaded, this starts the SRP
- authentication
- """
- leap_assert(self._provider_config, "We need a provider config!")
-
- if data[self._provider_bootstrapper.PASSED_KEY]:
- username = self._login_widget.get_user().encode("utf8")
- password = self._login_widget.get_password().encode("utf8")
-
- if self._srp_auth is None:
- self._srp_auth = SRPAuth(self._provider_config)
- self._srp_auth.authentication_finished.connect(
- self._authentication_finished)
- self._srp_auth.logout_finished.connect(
- self._done_logging_out)
-
- # TODO: Add errback!
- self._login_defer = self._srp_auth.authenticate(username, password)
- else:
- self._login_widget.set_status(
- "Unable to login: Problem with provider")
- logger.error(data[self._provider_bootstrapper.ERROR_KEY])
- self._login_widget.set_enabled(True)
-
- def _authentication_finished(self, ok, message):
- """
- SLOT
- TRIGGER: self._srp_auth.authentication_finished
-
- Once the user is properly authenticated, try starting the EIP
- service
- """
-
- # In general we want to "filter" likely complicated error
- # messages, but in this case, the messages make more sense as
- # they come. Since they are "Unknown user" or "Unknown
- # password"
- self._login_widget.set_status(message, error=not ok)
-
- if ok:
- self._logged_user = self._login_widget.get_user()
- self.ui.action_log_out.setEnabled(True)
- # We leave a bit of room for the user to see the
- # "Succeeded" message and then we switch to the EIP status
- # panel
- QtCore.QTimer.singleShot(1000, self._switch_to_status)
- self._login_defer = None
- else:
- self._login_widget.set_enabled(True)
-
- def _switch_to_status(self):
- """
- Changes the stackedWidget index to the EIP status one and
- triggers the eip bootstrapping
- """
- if not self._already_started_eip:
- self._status_panel.set_provider(
- "%s@%s" % (self._login_widget.get_user(),
- self._get_best_provider_config().get_domain()))
-
- self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX)
-
- self._soledad_bootstrapper.run_soledad_setup_checks(
- self._provider_config,
- self._login_widget.get_user(),
- self._login_widget.get_password(),
- download_if_needed=True,
- standalone=self._standalone)
-
- self._download_eip_config()
-
- def _soledad_intermediate_stage(self, data):
- """
- SLOT
- TRIGGERS:
- self._soledad_bootstrapper.download_config
-
- If there was a problem, displays it, otherwise it does nothing.
- This is used for intermediate bootstrapping stages, in case
- they fail.
- """
- passed = data[self._soledad_bootstrapper.PASSED_KEY]
- if not passed:
- # TODO: display in the GUI:
- # should pass signal to a slot in status_panel
- # that sets the global status
- logger.warning("Soledad failed to start: %s" %
- (data[self._soledad_bootstrapper.ERROR_KEY],))
-
- def _soledad_bootstrapped_stage(self, data):
- """
- SLOT
- TRIGGERS:
- self._soledad_bootstrapper.gen_key
-
- If there was a problem, displays it, otherwise it does nothing.
- This is used for intermediate bootstrapping stages, in case
- they fail.
-
- :param data: result from the bootstrapping stage for Soledad
- :type data: dict
- """
- passed = data[self._soledad_bootstrapper.PASSED_KEY]
- if not passed:
- logger.error(data[self._soledad_bootstrapper.ERROR_KEY])
- return
-
- logger.debug("Done bootstrapping Soledad")
-
- self._soledad = self._soledad_bootstrapper.soledad
- self._keymanager = self._soledad_bootstrapper.keymanager
-
- # Ok, now soledad is ready, so we can allow other things that
- # depend on soledad to start.
-
- # this will trigger start_imap_service
- self.soledad_ready.emit()
-
- # TODO connect all these activations to the soledad_ready
- # signal so the logic is clearer to follow.
-
- if self._provider_config.provides_mx() and \
- self._enabled_services.count(self.MX_SERVICE) > 0:
- self._smtp_bootstrapper.run_smtp_setup_checks(
- self._provider_config,
- self._smtp_config,
- True)
- else:
- if self._enabled_services.count(self.MX_SERVICE) > 0:
- pass # TODO: show MX status
- #self._status_panel.set_eip_status(
- # self.tr("%s does not support MX") %
- # (self._provider_config.get_domain(),),
- # error=True)
- else:
- pass # TODO: show MX status
- #self._status_panel.set_eip_status(
- # self.tr("MX is disabled"))
-
- # Service control methods: smtp
-
- def _smtp_bootstrapped_stage(self, data):
- """
- SLOT
- TRIGGERS:
- self._smtp_bootstrapper.download_config
-
- If there was a problem, displays it, otherwise it does nothing.
- This is used for intermediate bootstrapping stages, in case
- they fail.
-
- :param data: result from the bootstrapping stage for Soledad
- :type data: dict
- """
- passed = data[self._smtp_bootstrapper.PASSED_KEY]
- if not passed:
- logger.error(data[self._smtp_bootstrapper.ERROR_KEY])
- return
- logger.debug("Done bootstrapping SMTP")
-
- hosts = self._smtp_config.get_hosts()
- # TODO: handle more than one host and define how to choose
- if len(hosts) > 0:
- hostname = hosts.keys()[0]
- logger.debug("Using hostname %s for SMTP" % (hostname,))
- host = hosts[hostname][self.IP_KEY].encode("utf-8")
- port = hosts[hostname][self.PORT_KEY]
- # TODO: pick local smtp port in a better way
- # TODO: Make the encrypted_only configurable
-
- from leap.mail.smtp import setup_smtp_relay
- client_cert = self._eip_config.get_client_cert_path(
- self._provider_config)
- setup_smtp_relay(port=2013,
- keymanager=self._keymanager,
- smtp_host=host,
- smtp_port=port,
- smtp_cert=client_cert,
- smtp_key=client_cert,
- encrypted_only=False)
-
- def _start_imap_service(self):
- """
- SLOT
- TRIGGERS:
- soledad_ready
- """
- logger.debug('Starting imap service')
-
- self._imap_service = imap.start_imap_service(
- self._soledad,
- self._keymanager)
-
- def _get_socket_host(self):
- """
- Returns the socket and port to be used for VPN
-
- :rtype: tuple (str, str) (host, port)
- """
-
- # TODO: make this properly multiplatform
-
- if platform.system() == "Windows":
- host = "localhost"
- port = "9876"
- else:
- host = os.path.join(tempfile.mkdtemp(prefix="leap-tmp"),
- 'openvpn.socket')
- port = "unix"
-
- return host, port
-
- def _start_eip(self):
- """
- SLOT
- TRIGGERS:
- self._status_panel.start_eip
- self._action_eip_startstop.triggered
- or called from _finish_eip_bootstrap
-
- Starts EIP
- """
- self._status_panel.eip_pre_up()
- self.user_stopped_eip = False
- provider_config = self._get_best_provider_config()
-
- try:
- host, port = self._get_socket_host()
- self._vpn.start(eipconfig=self._eip_config,
- providerconfig=provider_config,
- socket_host=host,
- socket_port=port)
-
- self._settings.set_defaultprovider(
- provider_config.get_domain())
-
- provider = provider_config.get_domain()
- if self._logged_user is not None:
- provider = "%s@%s" % (self._logged_user, provider)
-
- self._status_panel.set_provider(provider)
-
- self._action_eip_provider.setText(provider_config.get_domain())
-
- self._status_panel.eip_started()
-
- # XXX refactor into status_panel method?
- self._action_eip_startstop.setText(self.tr("Turn OFF"))
- self._action_eip_startstop.disconnect(self)
- self._action_eip_startstop.triggered.connect(
- self._stop_eip)
- except EIPNoPolkitAuthAgentAvailable:
- self._status_panel.set_global_status(
- # XXX this should change to polkit-kde where
- # applicable.
- self.tr("We could not find any "
- "authentication "
- "agent in your system.<br/>"
- "Make sure you have "
- "<b>polkit-gnome-authentication-"
- "agent-1</b> "
- "running and try again."),
- error=True)
- self._set_eipstatus_off()
- except EIPNoTunKextLoaded:
- self._status_panel.set_global_status(
- self.tr("Encrypted Internet cannot be started because "
- "the tuntap extension is not installed properly "
- "in your system."))
- self._set_eipstatus_off()
- except EIPNoPkexecAvailable:
- self._status_panel.set_global_status(
- self.tr("We could not find <b>pkexec</b> "
- "in your system."),
- error=True)
- self._set_eipstatus_off()
- except OpenVPNNotFoundException:
- self._status_panel.set_global_status(
- self.tr("We could not find openvpn binary."),
- error=True)
- self._set_eipstatus_off()
- except OpenVPNAlreadyRunning as e:
- self._status_panel.set_global_status(
- self.tr("Another openvpn instance is already running, and "
- "could not be stopped."),
- error=True)
- self._set_eipstatus_off()
- except AlienOpenVPNAlreadyRunning as e:
- self._status_panel.set_global_status(
- self.tr("Another openvpn instance is already running, and "
- "could not be stopped because it was not launched by "
- "Bitmask. Please stop it and try again."),
- error=True)
- self._set_eipstatus_off()
- except VPNLauncherException as e:
- # XXX We should implement again translatable exceptions so
- # we can pass a translatable string to the panel (usermessage attr)
- self._status_panel.set_global_status("%s" % (e,), error=True)
- self._set_eipstatus_off()
- else:
- self._already_started_eip = True
-
- def _set_eipstatus_off(self):
- """
- Sets eip status to off
- """
- self._status_panel.set_eip_status(self.tr("OFF"), error=True)
- self._status_panel.set_eip_status_icon("error")
- self._status_panel.set_startstop_enabled(True)
- self._status_panel.eip_stopped()
-
- self._set_action_eipstart_off()
-
- def _set_action_eipstart_off(self):
- """
- Sets eip startstop action to OFF status.
- """
- self._action_eip_startstop.setText(self.tr("Turn ON"))
- self._action_eip_startstop.disconnect(self)
- self._action_eip_startstop.triggered.connect(
- self._start_eip)
-
- def _stop_eip(self, abnormal=False):
- """
- SLOT
- TRIGGERS:
- self._status_panel.stop_eip
- self._action_eip_startstop.triggered
- or called from _eip_finished
-
- Stops vpn process and makes gui adjustments to reflect
- the change of state.
-
- :param abnormal: whether this was an abnormal termination.
- :type abnormal: bool
- """
- if abnormal:
- logger.warning("Abnormal EIP termination.")
-
- self.user_stopped_eip = True
- self._vpn.terminate()
-
- self._set_eipstatus_off()
-
- self._already_started_eip = False
- self._settings.set_defaultprovider(None)
- if self._logged_user:
- self._status_panel.set_provider(
- "%s@%s" % (self._logged_user,
- self._get_best_provider_config().get_domain()))
-
- def _get_best_provider_config(self):
- """
- Returns the best ProviderConfig to use at a moment. We may
- have to use self._provider_config or
- self._provisional_provider_config depending on the start
- status.
-
- :rtype: ProviderConfig
- """
- leap_assert(self._provider_config is not None or
- self._provisional_provider_config is not None,
- "We need a provider config")
-
- provider_config = None
- if self._provider_config.loaded():
- provider_config = self._provider_config
- elif self._provisional_provider_config.loaded():
- provider_config = self._provisional_provider_config
- else:
- leap_assert(False, "We could not find any usable ProviderConfig.")
-
- return provider_config
-
- def _download_eip_config(self):
- """
- Starts the EIP bootstrapping sequence
- """
- leap_assert(self._eip_bootstrapper, "We need an eip bootstrapper!")
-
- provider_config = self._get_best_provider_config()
-
- if provider_config.provides_eip() and \
- self._enabled_services.count(self.OPENVPN_SERVICE) > 0 and \
- not self._already_started_eip:
-
- self._status_panel.set_eip_status(
- self.tr("Starting..."))
- self._eip_bootstrapper.run_eip_setup_checks(
- provider_config,
- download_if_needed=True)
- self._already_started_eip = True
- elif not self._already_started_eip:
- if self._enabled_services.count(self.OPENVPN_SERVICE) > 0:
- self._status_panel.set_eip_status(
- self.tr("Not supported"),
- error=True)
- else:
- self._status_panel.set_eip_status(self.tr("Disabled"))
- self._status_panel.set_startstop_enabled(False)
-
- def _finish_eip_bootstrap(self, data):
- """
- SLOT
- TRIGGER: self._eip_bootstrapper.download_client_certificate
-
- Starts the VPN thread if the eip configuration is properly
- loaded
- """
- leap_assert(self._eip_config, "We need an eip config!")
- passed = data[self._eip_bootstrapper.PASSED_KEY]
-
- if not passed:
- error_msg = self.tr("There was a problem with the provider")
- self._status_panel.set_eip_status(error_msg, error=True)
- logger.error(data[self._eip_bootstrapper.ERROR_KEY])
- self._already_started_eip = False
- return
-
- provider_config = self._get_best_provider_config()
-
- domain = provider_config.get_domain()
-
- loaded = self._eip_config.loaded()
- if not loaded:
- eip_config_path = os.path.join("leap", "providers",
- domain, "eip-service.json")
- api_version = provider_config.get_api_version()
- self._eip_config.set_api_version(api_version)
- loaded = self._eip_config.load(eip_config_path)
-
- if loaded:
- self._start_eip()
- else:
- self._status_panel.set_eip_status(
- self.tr("Could not load Encrypted Internet "
- "Configuration."),
- error=True)
-
- def _logout(self):
- """
- SLOT
- TRIGGER: self.ui.action_log_out.triggered
-
- Starts the logout sequence
- """
- # XXX: If other defers are doing authenticated stuff, this
- # might conflict with those. CHECK!
- threads.deferToThread(self._srp_auth.logout)
-
- def _done_logging_out(self, ok, message):
- """
- SLOT
- TRIGGER: self._srp_auth.logout_finished
-
- Switches the stackedWidget back to the login stage after
- logging out
- """
- self._logged_user = None
- self.ui.action_log_out.setEnabled(False)
- self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX)
- self._login_widget.set_password("")
- self._login_widget.set_enabled(True)
- self._login_widget.set_status("")
-
- def _intermediate_stage(self, data):
- """
- SLOT
- TRIGGERS:
- self._provider_bootstrapper.name_resolution
- self._provider_bootstrapper.https_connection
- self._provider_bootstrapper.download_ca_cert
- self._eip_bootstrapper.download_config
-
- If there was a problem, displays it, otherwise it does nothing.
- This is used for intermediate bootstrapping stages, in case
- they fail.
- """
- passed = data[self._provider_bootstrapper.PASSED_KEY]
- if not passed:
- self._login_widget.set_enabled(True)
- self._login_widget.set_status(
- self.tr("Unable to connect: Problem with provider"))
- logger.error(data[self._provider_bootstrapper.ERROR_KEY])
-
- def _eip_intermediate_stage(self, data):
- """
- SLOT
- TRIGGERS:
- self._eip_bootstrapper.download_config
-
- If there was a problem, displays it, otherwise it does nothing.
- This is used for intermediate bootstrapping stages, in case
- they fail.
- """
- passed = data[self._provider_bootstrapper.PASSED_KEY]
- if not passed:
- self._login_widget.set_status(
- self.tr("Unable to connect: Problem with provider"))
- logger.error(data[self._provider_bootstrapper.ERROR_KEY])
- self._already_started_eip = False
-
- def _eip_finished(self, exitCode):
- """
- SLOT
- TRIGGERS:
- self._vpn.process_finished
-
- Triggered when the EIP/VPN process finishes to set the UI
- accordingly.
- """
- logger.info("VPN process finished with exitCode %s..."
- % (exitCode,))
-
- # Ideally we would have the right exit code here,
- # but the use of different wrappers (pkexec, cocoasudo) swallows
- # the openvpn exit code so we get zero exit in some cases where we
- # shouldn't. As a workaround we just use a flag to indicate
- # a purposeful switch off, and mark everything else as unexpected.
-
- # In the near future we should trigger a native notification from here,
- # since the user really really wants to know she is unprotected asap.
- # And the right thing to do will be to fail-close.
-
- # TODO we should have a way of parsing the latest lines in the vpn
- # log buffer so we can have a more precise idea of which type
- # of error did we have (server side, local problem, etc)
- abnormal = True
-
- # XXX check if these exitCodes are pkexec/cocoasudo specific
- if exitCode in (126, 127):
- self._status_panel.set_global_status(
- self.tr("Encrypted Internet could not be launched "
- "because you did not authenticate properly."),
- error=True)
- self._vpn.killit()
- elif exitCode != 0 or not self.user_stopped_eip:
- self._status_panel.set_global_status(
- self.tr("Encrypted Internet finished in an "
- "unexpected manner!"), error=True)
- else:
- abnormal = False
- if exitCode == 0 and IS_MAC:
- # XXX remove this warning after I fix cocoasudo.
- logger.warning("The above exit code MIGHT BE WRONG.")
- self._stop_eip(abnormal)
-
- def _on_raise_window_event(self, req):
- """
- Callback for the raise window event
- """
- if IS_WIN:
- raise_window_ack()
- self.raise_window.emit()
-
- def _do_raise_mainwindow(self):
- """
- SLOT
- TRIGGERS:
- self._on_raise_window_event
-
- Triggered when we receive a RAISE_WINDOW event.
- """
- TOPFLAG = QtCore.Qt.WindowStaysOnTopHint
- self.setWindowFlags(self.windowFlags() | TOPFLAG)
- self.show()
- self.setWindowFlags(self.windowFlags() & ~TOPFLAG)
- self.show()
- if IS_MAC:
- self.raise_()
-
- def _cleanup_pidfiles(self):
- """
- Removes lockfiles on a clean shutdown.
-
- Triggered after aboutToQuit signal.
- """
- if IS_WIN:
- WindowsLock.release_all_locks()
-
- def _cleanup_and_quit(self):
- """
- Call all the cleanup actions in a serialized way.
- Should be called from the quit function.
- """
- logger.debug('About to quit, doing cleanup...')
-
- if self._imap_service is not None:
- self._imap_service.stop()
-
- if self._srp_auth is not None:
- if self._srp_auth.get_session_id() is not None or \
- self._srp_auth.get_token() is not None:
- # XXX this can timeout after loong time: See #3368
- self._srp_auth.logout()
-
- if self._soledad:
- logger.debug("Closing soledad...")
- self._soledad.close()
- else:
- logger.error("No instance of soledad was found.")
-
- logger.debug('Terminating vpn')
- self._vpn.terminate(shutdown=True)
-
- if self._login_defer:
- logger.debug("Cancelling login defer.")
- self._login_defer.cancel()
-
- if self._download_provider_defer:
- logger.debug("Cancelling download provider defer.")
- self._download_provider_defer.cancel()
-
- # TODO missing any more cancels?
-
- logger.debug('Cleaning pidfiles')
- self._cleanup_pidfiles()
-
- def quit(self):
- """
- Cleanup and tidely close the main window before quitting.
- """
- # TODO: separate the shutting down of services from the
- # UI stuff.
- self._cleanup_and_quit()
-
- self._really_quit = True
-
- if self._wizard:
- self._wizard.close()
-
- if self._logger_window:
- self._logger_window.close()
-
- self.close()
-
- if self._quit_callback:
- self._quit_callback()
-
- logger.debug('Bye.')
-
-
-if __name__ == "__main__":
- import signal
-
- def sigint_handler(*args, **kwargs):
- logger.debug('SIGINT catched. shutting down...')
- mainwindow = args[0]
- mainwindow.quit()
-
- import sys
-
- logger = logging.getLogger(name='leap')
- logger.setLevel(logging.DEBUG)
- console = logging.StreamHandler()
- console.setLevel(logging.DEBUG)
- formatter = logging.Formatter(
- '%(asctime)s '
- '- %(name)s - %(levelname)s - %(message)s')
- console.setFormatter(formatter)
- logger.addHandler(console)
-
- app = QtGui.QApplication(sys.argv)
- mainwindow = MainWindow()
- mainwindow.show()
-
- timer = QtCore.QTimer()
- timer.start(500)
- timer.timeout.connect(lambda: None)
-
- sigint = partial(sigint_handler, mainwindow)
- signal.signal(signal.SIGINT, sigint)
-
- sys.exit(app.exec_())