diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/leap/bitmask/backend.py | 12 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/eip_status.py | 161 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/mainwindow.py | 403 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/statemachines.py | 13 | ||||
| -rw-r--r-- | src/leap/bitmask/services/eip/conductor.py | 269 | ||||
| -rw-r--r-- | src/leap/bitmask/services/eip/vpnprocess.py | 15 | 
6 files changed, 512 insertions, 361 deletions
| diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 5e22a8c4..f0fe50f9 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -434,8 +434,12 @@ class EIP(object):          except Exception as e:              logger.error("Unexpected problem: {0!r}".format(e))          else: +            logger.debug('EIP: no errors')              # TODO: are we connected here? -            signaler.signal(signaler.EIP_CONNECTED) +            # kali -- no, we are not! CONNECTED should be passed only +            # by the vpn observer. Currently handled by the state updater +            # in eip_status +            #signaler.signal(signaler.EIP_CONNECTED)      def _do_stop(self, shutdown=False, restart=False):          """ @@ -470,9 +474,9 @@ class EIP(object):                  self._signaler.signal(self._signaler.EIP_STOPPED)                  return              else: -                msg = "Firewall is not down yet, waiting... {0} of {1}" -                msg = msg.format(retry, MAX_FW_WAIT_RETRIES) -                logger.debug(msg) +                #msg = "Firewall is not down yet, waiting... {0} of {1}" +                #msg = msg.format(retry, MAX_FW_WAIT_RETRIES) +                #logger.debug(msg)                  time.sleep(FW_WAIT_STEP)                  retry += 1          logger.warning("After waiting, firewall is not down... " diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index ca28b8bf..9da231f2 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -24,7 +24,6 @@ from functools import partial  from PySide import QtCore, QtGui -from leap.bitmask.services.eip.connection import EIPConnection  from leap.bitmask.services import get_service_display_name, EIP_SERVICE  from leap.bitmask.platform_init import IS_LINUX  from leap.bitmask.util.averages import RateMovingAverage @@ -43,9 +42,14 @@ class EIPStatusWidget(QtGui.QWidget):      RATE_STR = "%1.2f KB/s"      TOTAL_STR = "%1.2f Kb" -    eip_connection_connected = QtCore.Signal() +    def __init__(self, parent=None, eip_conductor=None): +        """ +        :param parent: the parent of the widget. +        :type parent: QObject -    def __init__(self, parent=None): +        :param eip_conductor: an EIPConductor object. +        :type eip_conductor: EIPConductor +        """          QtGui.QWidget.__init__(self, parent)          self._systray = None @@ -54,7 +58,8 @@ class EIPStatusWidget(QtGui.QWidget):          self.ui = Ui_EIPStatus()          self.ui.setupUi(self) -        self.eipconnection = EIPConnection() +        self.eip_conductor = eip_conductor +        self.eipconnection = eip_conductor.eip_connection          # set systray tooltip status          self._eip_status = "" @@ -75,11 +80,36 @@ class EIPStatusWidget(QtGui.QWidget):          self._make_status_clickable()          self._provider = "" +        self.is_restart = False          # Action for the systray          self._eip_disabled_action = QtGui.QAction(              "{0} is {1}".format(self._service_name, self.tr("disabled")), self) +    def connect_backend_signals(self): +        """ +        Connect backend signals. +        """ +        signaler = self.eip_conductor._backend.signaler + +        signaler.eip_openvpn_already_running.connect( +            self._on_eip_openvpn_already_running) +        signaler.eip_alien_openvpn_already_running.connect( +            self._on_eip_alien_openvpn_already_running) +        signaler.eip_openvpn_not_found_error.connect( +            self._on_eip_openvpn_not_found_error) +        signaler.eip_vpn_launcher_exception.connect( +            self._on_eip_vpn_launcher_exception) +        signaler.eip_no_polkit_agent_error.connect( +            self._on_eip_no_polkit_agent_error) +        signaler.eip_no_pkexec_error.connect(self._on_eip_no_pkexec_error) +        signaler.eip_no_tun_kext_error.connect(self._on_eip_no_tun_kext_error) + +        signaler.eip_state_changed.connect(self.update_vpn_state) +        signaler.eip_status_changed.connect(self.update_vpn_status) +        signaler.eip_network_unreachable.connect( +            self._on_eip_network_unreachable) +      def _make_status_clickable(self):          """          Makes upload and download figures clickable. @@ -238,7 +268,7 @@ class EIPStatusWidget(QtGui.QWidget):      def eip_pre_up(self):          """          Triggered when the app activates eip. -        Hides the status box and disables the start/stop button. +        Disables the start/stop button.          """          self.set_startstop_enabled(False) @@ -248,7 +278,7 @@ class EIPStatusWidget(QtGui.QWidget):          Triggered when a default provider_config has not been found.          Disables the start button and adds instructions to the user.          """ -        #logger.debug('Hiding EIP start button') +        logger.debug('Hiding EIP start button')          # you might be tempted to change this for a .setEnabled(False).          # it won't work. it's under the claws of the state machine.          # probably the best thing would be to make a conditional @@ -326,26 +356,26 @@ class EIPStatusWidget(QtGui.QWidget):          Sets the state of the widget to how it should look after EIP          has started          """ -        self.ui.btnEipStartStop.setText(self.tr("Turn OFF"))          self.ui.btnEipStartStop.disconnect(self)          self.ui.btnEipStartStop.clicked.connect(              self.eipconnection.qtsigs.do_connect_signal) -    # XXX disable ----------------------------- -    def eip_stopped(self): +    @QtCore.Slot(dict) +    def eip_stopped(self, restart=False):          """ +        TRIGGERS: +            EIPConductor.qtsigs.disconnected_signal +          Sets the state of the widget to how it should look after EIP          has stopped          """ -        # XXX should connect this to EIPConnection.disconnected_signal          self._reset_traffic_rates() -        # XXX disable ----------------------------- -        self.ui.btnEipStartStop.setText(self.tr("Turn ON")) -        self.ui.btnEipStartStop.disconnect(self) -        self.ui.btnEipStartStop.clicked.connect( -            self.eipconnection.qtsigs.do_disconnect_signal) -          self.ui.eip_bandwidth.hide() + +        # XXX FIXME ! ----------------------- this needs to +        # accomodate the messages about firewall up or not. +        # Or better call it from the conductor... +          self.ui.lblEIPMessage.setText(              self.tr("Traffic is being routed in the clear"))          self.ui.lblEIPStatus.show() @@ -407,7 +437,8 @@ class EIPStatusWidget(QtGui.QWidget):              self.ui.lblEIPStatus.hide()              # XXX should be handled by the state machine too. -            self.eip_connection_connected.emit() +            # --- is this currently being sent? +            self.eipconnection.qtsigs.connected_signal.emit()          # XXX should lookup vpn_state map in EIPConnection          elif vpn_state == "AUTH": @@ -423,8 +454,9 @@ class EIPStatusWidget(QtGui.QWidget):          elif vpn_state == "ALREADYRUNNING":              # Put the following calls in Qt's event queue, otherwise              # the UI won't update properly +            #self.send_disconnect_signal()              QtCore.QTimer.singleShot( -                0, self.eipconnection.qtsigs.do_disconnect_signal) +                0, self.eipconnection.qtsigns.do_disconnect_signal.emit)              msg = self.tr("Unable to start VPN, it's already running.")              QtCore.QTimer.singleShot(0, partial(self.set_eip_status, msg))          else: @@ -470,3 +502,96 @@ class EIPStatusWidget(QtGui.QWidget):          self._provider = provider          self.ui.lblEIPMessage.setText(              self.tr("Route traffic through: {0}").format(self._provider)) + +    # +    # Slots for signals +    # + +    @QtCore.Slot() +    def _on_eip_connection_aborted(self): +        """ +        TRIGGERS: +            Signaler.eip_connection_aborted +        """ +        logger.error("Tried to start EIP but cannot find any " +                     "available provider!") + +        eip_status_label = self.tr("Could not load {0} configuration.") +        eip_status_label = eip_status_label.format( +            self._eip_conductor.eip_name) +        self.set_eip_status(eip_status_label, error=True) + +        # signal connection_aborted to state machine: +        qtsigs = self._eip_connection.qtsigs +        qtsigs.connection_aborted_signal.emit() + +    def _on_eip_openvpn_already_running(self): +        self.set_eip_status( +            self.tr("Another openvpn instance is already running, and " +                    "could not be stopped."), +            error=True) +        self.set_eipstatus_off() + +    def _on_eip_alien_openvpn_already_running(self): +        self.set_eip_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() + +    def _on_eip_openvpn_not_found_error(self): +        self.set_eip_status( +            self.tr("We could not find openvpn binary."), +            error=True) +        self.set_eipstatus_off() + +    def _on_eip_vpn_launcher_exception(self): +        # XXX We should implement again translatable exceptions so +        # we can pass a translatable string to the panel (usermessage attr) +        self.set_eip_status("VPN Launcher error.", error=True) +        self.set_eipstatus_off() + +    def _on_eip_no_polkit_agent_error(self): +        self.set_eip_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() + +    def _on_eip_no_pkexec_error(self): +        self.set_eip_status( +            self.tr("We could not find <b>pkexec</b> in your system."), +            error=True) +        self.set_eipstatus_off() + +    def _on_eip_no_tun_kext_error(self): +        self.set_eip_status( +            self.tr("{0} cannot be started because the tuntap extension is " +                    "not installed properly in your " +                    "system.").format(self._eip_conductor.eip_name)) +        self.set_eipstatus_off() + +    @QtCore.Slot() +    def _on_eip_network_unreachable(self): +        """ +        TRIGGERS: +            self._eip_connection.qtsigs.network_unreachable + +        Displays a "network unreachable" error in the EIP status panel. +        """ +        self.set_eip_status(self.tr("Network is unreachable"), +                            error=True) +        self.set_eip_status_icon("error") + +    def set_eipstatus_off(self, error=True): +    # XXX this should be handled by the state machine. +        """ +        Sets eip status to off +        """ +        self.set_eip_status("", error=error) +        self.set_eip_status_icon("error") diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index fd9a70ac..27ef6109 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -30,7 +30,6 @@ from leap.bitmask import __version_hash__ as VERSION_HASH  from leap.bitmask.config import flags  from leap.bitmask.config.leapsettings import LeapSettings -from leap.bitmask.gui import statemachines  from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement  from leap.bitmask.gui.eip_preferenceswindow import EIPPreferencesWindow  from leap.bitmask.gui.eip_status import EIPStatusWidget @@ -46,12 +45,10 @@ from leap.bitmask.platform_init.initializers import init_platform  from leap.bitmask import backend -from leap.bitmask.services import get_service_display_name - +from leap.bitmask.services.eip import conductor as eip_conductor  from leap.bitmask.services.mail import conductor as mail_conductor  from leap.bitmask.services import EIP_SERVICE, MX_SERVICE -from leap.bitmask.services.eip.connection import EIPConnection  from leap.bitmask.util import make_address  from leap.bitmask.util.keyring_helpers import has_keyring @@ -68,6 +65,7 @@ from leap.mail.imap.service.imap import IMAP_PORT  from ui_mainwindow import Ui_MainWindow +QtDelayedCall = QtCore.QTimer.singleShot  logger = logging.getLogger(__name__) @@ -131,11 +129,16 @@ class MainWindow(QtGui.QMainWindow):          self._settings = LeapSettings() +        # Login Widget          self._login_widget = LoginWidget(              self._settings,              self)          self.ui.loginLayout.addWidget(self._login_widget) +        # Mail Widget +        self._mail_status = MailStatusWidget(self) +        self.ui.mailLayout.addWidget(self._mail_status) +          # Qt Signal Connections #####################################          # TODO separate logic from ui signals. @@ -144,34 +147,27 @@ class MainWindow(QtGui.QMainWindow):          self._login_widget.show_wizard.connect(self._launch_wizard)          self._login_widget.logout.connect(self._logout) -        self._eip_status = EIPStatusWidget(self) -        self.ui.eipLayout.addWidget(self._eip_status) -        self._login_widget.logged_in_signal.connect( -            self._eip_status.enable_eip_start) -        self._login_widget.logged_in_signal.connect( -            self._enable_eip_start_action) - -        self._mail_status = MailStatusWidget(self) -        self.ui.mailLayout.addWidget(self._mail_status) - -        self._eip_connection = EIPConnection() +        # EIP Control redux ######################################### +        self._eip_conductor = eip_conductor.EIPConductor( +            self._settings, self._backend) +        self._eip_status = EIPStatusWidget(self, self._eip_conductor) -        # XXX this should be handled by EIP Conductor -        self._eip_connection.qtsigs.connecting_signal.connect( -            self._start_EIP) -        self._eip_connection.qtsigs.disconnecting_signal.connect( -            self._stop_eip) +        self.ui.eipLayout.addWidget(self._eip_status) +        self._eip_conductor.add_eip_widget(self._eip_status) -        self._eip_status.eip_connection_connected.connect( +        self._eip_conductor.connect_signals() +        self._eip_conductor.qtsigs.connected_signal.connect(              self._on_eip_connection_connected) -        self._eip_status.eip_connection_connected.connect( +        self._eip_conductor.qtsigs.connected_signal.connect(              self._maybe_run_soledad_setup_checks) +          self.offline_mode_bypass_login.connect(              self._maybe_run_soledad_setup_checks)          self.eip_needs_login.connect(self._eip_status.disable_eip_start)          self.eip_needs_login.connect(self._disable_eip_start_action) +        self._already_started_eip = False          self._trying_to_start_eip = False          self._already_started_eip = False @@ -215,8 +211,7 @@ class MainWindow(QtGui.QMainWindow):          self._systray = None -        # XXX separate actions into a different -        # module. +        # XXX separate actions into a different module.          self._action_mail_status = QtGui.QAction(self.tr("Mail is OFF"), self)          self._mail_status.set_action_mail_status(self._action_mail_status) @@ -280,20 +275,19 @@ class MainWindow(QtGui.QMainWindow):          # the EIPConductor or some other clever component that we will          # instantiate from here. -        self.eip_machine = None          # start event machines -        self.start_eip_machine() +        # TODO should encapsulate all actions into one object +        self._eip_conductor.start_eip_machine( +            action=self._action_eip_startstop)          self._mail_conductor.start_mail_machine() -        self._eip_name = get_service_display_name(EIP_SERVICE) -          if self._first_run():              self._wizard_firstrun = True              self._disconnect_and_untrack()              self._wizard = Wizard(backend=self._backend,                                    bypass_checks=bypass_checks)              # Give this window time to finish init and then show the wizard -            QtCore.QTimer.singleShot(1, self._launch_wizard) +            QtDelayedCall(1, self._launch_wizard)              self._wizard.accepted.connect(self._finish_init)              self._wizard.rejected.connect(self._rejected_wizard)          else: @@ -351,67 +345,50 @@ class MainWindow(QtGui.QMainWindow):          :type only_tracked: bool          """          sig = self._backend.signaler +        conntrack = self._connect_and_track +        auth_err = self._authentication_error -        self._connect_and_track(sig.prov_name_resolution, -                                self._intermediate_stage) -        self._connect_and_track(sig.prov_https_connection, -                                self._intermediate_stage) -        self._connect_and_track(sig.prov_download_ca_cert, -                                self._intermediate_stage) +        conntrack(sig.prov_name_resolution, self._intermediate_stage) +        conntrack(sig.prov_https_connection, self._intermediate_stage) +        conntrack(sig.prov_download_ca_cert, self._intermediate_stage) +        conntrack(sig.prov_download_provider_info, self._load_provider_config) +        conntrack(sig.prov_check_api_certificate, self._provider_config_loaded) -        self._connect_and_track(sig.prov_download_provider_info, -                                self._load_provider_config) -        self._connect_and_track(sig.prov_check_api_certificate, -                                self._provider_config_loaded) - -        self._connect_and_track(sig.prov_problem_with_provider, -                                self._login_problem_provider) - -        self._connect_and_track(sig.prov_cancelled_setup, -                                self._set_login_cancelled) +        conntrack(sig.prov_problem_with_provider, self._login_problem_provider) +        conntrack(sig.prov_cancelled_setup, self._set_login_cancelled)          self._connect_and_track(sig.prov_get_details,                                  self._provider_get_details)          # Login signals -        self._connect_and_track(sig.srp_auth_ok, self._authentication_finished) +        conntrack(sig.srp_auth_ok, self._authentication_finished) -        auth_error = ( -            lambda: self._authentication_error(self.tr("Unknown error."))) -        self._connect_and_track(sig.srp_auth_error, auth_error) +        auth_error = lambda: auth_err(self.tr("Unknown error.")) +        conntrack(sig.srp_auth_error, auth_error) -        auth_server_error = ( -            lambda: self._authentication_error( -                self.tr("There was a server problem with authentication."))) -        self._connect_and_track(sig.srp_auth_server_error, auth_server_error) +        auth_server_error = lambda: auth_err(self.tr( +            "There was a server problem with authentication.")) +        conntrack(sig.srp_auth_server_error, auth_server_error) -        auth_connection_error = ( -            lambda: self._authentication_error( -                self.tr("Could not establish a connection."))) -        self._connect_and_track(sig.srp_auth_connection_error, -                                auth_connection_error) +        auth_connection_error = lambda: auth_err(self.tr( +            "Could not establish a connection.")) +        conntrack(sig.srp_auth_connection_error, auth_connection_error) -        auth_bad_user_or_password = ( -            lambda: self._authentication_error( -                self.tr("Invalid username or password."))) -        self._connect_and_track(sig.srp_auth_bad_user_or_password, -                                auth_bad_user_or_password) +        auth_bad_user_or_password = lambda: auth_err(self.tr( +            "Invalid username or password.")) +        conntrack(sig.srp_auth_bad_user_or_password, auth_bad_user_or_password)          # Logout signals -        self._connect_and_track(sig.srp_logout_ok, self._logout_ok) -        self._connect_and_track(sig.srp_logout_error, self._logout_error) - -        self._connect_and_track(sig.srp_not_logged_in_error, -                                self._not_logged_in_error) +        conntrack(sig.srp_logout_ok, self._logout_ok) +        conntrack(sig.srp_logout_error, self._logout_error) +        conntrack(sig.srp_not_logged_in_error, self._not_logged_in_error)          # EIP bootstrap signals -        self._connect_and_track(sig.eip_config_ready, -                                self._eip_intermediate_stage) -        self._connect_and_track(sig.eip_client_certificate_ready, -                                self._finish_eip_bootstrap) +        conntrack(sig.eip_config_ready, self._eip_intermediate_stage) +        conntrack(sig.eip_client_certificate_ready, self._finish_eip_bootstrap)          ################################################### -        # Add tracked signals above this, untracked bellow! +        # Add tracked signals above this, untracked below!          ###################################################          if only_tracked:              return @@ -421,38 +398,20 @@ class MainWindow(QtGui.QMainWindow):          sig.backend_bad_call.connect(self._backend_bad_call)          sig.prov_check_api_certificate.connect(self._get_provider_details) -          sig.prov_unsupported_client.connect(self._needs_update)          sig.prov_unsupported_api.connect(self._incompatible_api) +        sig.prov_get_all_services.connect(self._provider_get_all_services) -        sig.prov_get_all_services.connect( -            self._provider_get_all_services) - -        # EIP start signals -        sig.eip_openvpn_already_running.connect( -            self._on_eip_openvpn_already_running) -        sig.eip_alien_openvpn_already_running.connect( -            self._on_eip_alien_openvpn_already_running) -        sig.eip_openvpn_not_found_error.connect( -            self._on_eip_openvpn_not_found_error) -        sig.eip_vpn_launcher_exception.connect( -            self._on_eip_vpn_launcher_exception) -        sig.eip_no_polkit_agent_error.connect( -            self._on_eip_no_polkit_agent_error) -        sig.eip_no_pkexec_error.connect(self._on_eip_no_pkexec_error) -        sig.eip_no_tun_kext_error.connect(self._on_eip_no_tun_kext_error) - -        sig.eip_state_changed.connect(self._eip_status.update_vpn_state) -        sig.eip_status_changed.connect(self._eip_status.update_vpn_status) -        sig.eip_process_finished.connect(self._eip_finished) -        sig.eip_network_unreachable.connect(self._on_eip_network_unreachable) -        sig.eip_process_restart_tls.connect(self._do_eip_restart) -        sig.eip_process_restart_ping.connect(self._do_eip_restart) +        # EIP start signals ============================================== +        self._eip_conductor.connect_backend_signals()          sig.eip_can_start.connect(self._backend_can_start_eip)          sig.eip_cannot_start.connect(self._backend_cannot_start_eip) +        # ================================================================== +          # Soledad signals +        # TODO delegate connection to soledad bootstrapper          sig.soledad_bootstrap_failed.connect(              self._mail_status.set_soledad_failed)          sig.soledad_bootstrap_finished.connect(self._on_soledad_ready) @@ -919,7 +878,8 @@ class MainWindow(QtGui.QMainWindow):          systrayMenu.addAction(self._action_visible)          systrayMenu.addSeparator() -        eip_status_label = "{0}: {1}".format(self._eip_name, self.tr("OFF")) +        eip_status_label = "{0}: {1}".format( +            self._eip_conductor.eip_name, self.tr("OFF"))          self._eip_menu = eip_menu = systrayMenu.addMenu(eip_status_label)          eip_menu.addAction(self._action_eip_startstop)          self._eip_status.set_eip_status_menu(eip_menu) @@ -1001,7 +961,7 @@ class MainWindow(QtGui.QMainWindow):          # Wait a bit until the window visibility has changed so          # the menu is set with the correct value. -        QtCore.QTimer.singleShot(500, self._update_hideshow_menu) +        QtDelayedCall(500, self._update_hideshow_menu)      def _center_window(self):          """ @@ -1473,21 +1433,6 @@ class MainWindow(QtGui.QMainWindow):      ###################################################################      # Service control methods: eip -    def start_eip_machine(self): -        """ -        Initializes and starts the EIP state machine -        """ -        button = self._eip_status.eip_button -        action = self._action_eip_startstop -        label = self._eip_status.eip_label -        builder = statemachines.ConnectionMachineBuilder(self._eip_connection) -        eip_machine = builder.make_machine(button=button, -                                           action=action, -                                           label=label) -        self.eip_machine = eip_machine -        self.eip_machine.start() -        logger.debug('eip machine started') -      @QtCore.Slot()      def _disable_eip_start_action(self):          """ @@ -1506,17 +1451,14 @@ class MainWindow(QtGui.QMainWindow):      def _on_eip_connection_connected(self):          """          TRIGGERS: -            self._eip_status.eip_connection_connected - -        Emits the EIPConnection.qtsigs.connected_signal +            self._eip_conductor.qtsigs.connected_signal          This is a little workaround for connecting the vpn-connected          signal that currently is beeing processed under status_panel.          After the refactor to EIPConductor this should not be necessary.          """ -        self._eip_connection.qtsigs.connected_signal.emit() -          domain = self._login_widget.get_selected_provider() +          self._eip_status.set_provider(domain)          self._settings.set_defaultprovider(domain)          self._already_started_eip = True @@ -1525,6 +1467,9 @@ class MainWindow(QtGui.QMainWindow):          self._check_name_resolution(domain)      def _check_name_resolution(self, domain): +        # FIXME this has to be moved to backend !!! +        # Should move to netchecks module. +        # and separate qt from reactor...          """          Check if we can resolve the given domain name. @@ -1555,7 +1500,7 @@ class MainWindow(QtGui.QMainWindow):                  "missing some helper files that are needed to securely use "                  "DNS while {1} is active. To install these helper files, quit "                  "this application and start it again." -            ).format(domain, self._eip_name) +            ).format(domain, self._eip_conductor.eip_name)              show_err = lambda: QtGui.QMessageBox.critical(                  self, self.tr("Connection Error"), msg) @@ -1580,211 +1525,6 @@ class MainWindow(QtGui.QMainWindow):          if settings.get_autostart_eip():              self._maybe_start_eip(autostart=True) -    @QtCore.Slot() -    def _start_EIP(self): -        """ -        Starts EIP -        """ -        self._eip_status.eip_pre_up() -        self.user_stopped_eip = False - -        # Until we set an option in the preferences window, we'll assume that -        # by default we try to autostart. If we switch it off manually, it -        # won't try the next time. -        self._settings.set_autostart_eip(True) - -        self._backend.eip_start() - -    @QtCore.Slot() -    def _on_eip_connection_aborted(self): -        """ -        TRIGGERS: -            Signaler.eip_connection_aborted -        """ -        logger.error("Tried to start EIP but cannot find any " -                     "available provider!") - -        eip_status_label = self.tr("Could not load {0} configuration.") -        eip_status_label = eip_status_label.format(self._eip_name) -        self._eip_status.set_eip_status(eip_status_label, error=True) - -        # signal connection_aborted to state machine: -        qtsigs = self._eip_connection.qtsigs -        qtsigs.connection_aborted_signal.emit() - -    def _on_eip_openvpn_already_running(self): -        self._eip_status.set_eip_status( -            self.tr("Another openvpn instance is already running, and " -                    "could not be stopped."), -            error=True) -        self._set_eipstatus_off() - -    def _on_eip_alien_openvpn_already_running(self): -        self._eip_status.set_eip_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() - -    def _on_eip_openvpn_not_found_error(self): -        self._eip_status.set_eip_status( -            self.tr("We could not find openvpn binary."), -            error=True) -        self._set_eipstatus_off() - -    def _on_eip_vpn_launcher_exception(self): -        # XXX We should implement again translatable exceptions so -        # we can pass a translatable string to the panel (usermessage attr) -        self._eip_status.set_eip_status("VPN Launcher error.", error=True) -        self._set_eipstatus_off() - -    def _on_eip_no_polkit_agent_error(self): -        self._eip_status.set_eip_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() - -    def _on_eip_no_pkexec_error(self): -        self._eip_status.set_eip_status( -            self.tr("We could not find <b>pkexec</b> in your system."), -            error=True) -        self._set_eipstatus_off() - -    def _on_eip_no_tun_kext_error(self): -        self._eip_status.set_eip_status( -            self.tr("{0} cannot be started because the tuntap extension is " -                    "not installed properly in your " -                    "system.").format(self._eip_name)) -        self._set_eipstatus_off() - -    @QtCore.Slot() -    def _stop_eip(self, restart=False): -        """ -        TRIGGERS: -          self._eip_connection.qtsigs.do_disconnect_signal (via state machine) - -        Stops vpn process and makes gui adjustments to reflect -        the change of state. - -        :param abnormal: whether this was an abnormal termination. -        :type abnormal: bool -        """ -        self.user_stopped_eip = not restart -        self._backend.eip_stop(restart=restart) - -        self._set_eipstatus_off(False) -        self._already_started_eip = False - -        logger.debug('Setting autostart to: False') -        self._settings.set_autostart_eip(False) - -        user = self._logged_user -        if user: -            domain = self._login_widget.get_selected_provider() -            full_user_id = make_address(user, domain) -            self._eip_status.set_provider(full_user_id) - -        self._eip_status.eip_stopped() - -    @QtCore.Slot() -    def _on_eip_network_unreachable(self): -        # XXX Should move to EIP Conductor -        """ -        TRIGGERS: -            self._eip_connection.qtsigs.network_unreachable - -        Displays a "network unreachable" error in the EIP status panel. -        """ -        self._eip_status.set_eip_status(self.tr("Network is unreachable"), -                                        error=True) -        self._eip_status.set_eip_status_icon("error") - -    @QtCore.Slot() -    def _do_eip_restart(self): -        # XXX Should move to EIP Conductor -        """ -        TRIGGERS: -            self._eip_connection.qtsigs.process_restart - -        Restart the connection. -        """ -        # for some reason, emitting the do_disconnect/do_connect -        # signals hangs the UI. -        self._stop_eip(restart=True) -        QtCore.QTimer.singleShot(2000, self._start_EIP) - -    def _set_eipstatus_off(self, error=True): -        """ -        Sets eip status to off -        """ -        # XXX this should be handled by the state machine. -        self._eip_status.set_eip_status("", error=error) -        self._eip_status.set_eip_status_icon("error") - -    @QtCore.Slot(int) -    def _eip_finished(self, exitCode): -        """ -        TRIGGERS: -            Signaler.eip_process_finished - -        Triggered when the EIP/VPN process finishes to set the UI -        accordingly. - -        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. - -        :param exitCode: the exit code of the eip process. -        :type exitCode: int -        """ -        # TODO move to EIPConductor. -        # TODO Add error catching to the openvpn log observer -        # so we can have a more precise idea of which type -        # of error did we have (server side, local problem, etc) - -        logger.info("VPN process finished with exitCode %s..." -                    % (exitCode,)) - -        qtsigs = self._eip_connection.qtsigs -        signal = qtsigs.disconnected_signal - -        # XXX check if these exitCodes are pkexec/cocoasudo specific -        if exitCode in (126, 127): -            eip_status_label = self.tr( -                "{0} could not be launched " -                "because you did not authenticate properly.") -            eip_status_label = eip_status_label.format(self._eip_name) -            self._eip_status.set_eip_status(eip_status_label, error=True) -            signal = qtsigs.connection_aborted_signal -            self._backend.eip_terminate() - -        elif exitCode != 0 or not self.user_stopped_eip: -            eip_status_label = self.tr("{0} finished in an unexpected manner!") -            eip_status_label = eip_status_label.format(self._eip_name) -            self._eip_status.eip_stopped() -            self._eip_status.set_eip_status_icon("error") -            self._eip_status.set_eip_status(eip_status_label, error=True) -            signal = qtsigs.connection_died_signal - -        if exitCode == 0 and IS_MAC: -            # XXX remove this warning after I fix cocoasudo. -            logger.warning("The above exit code MIGHT BE WRONG.") - -        # We emit signals to trigger transitions in the state machine: -        signal.emit() -      # eip boostrapping, config etc...      def _maybe_start_eip(self, autostart=False): @@ -1812,9 +1552,8 @@ class MainWindow(QtGui.QMainWindow):              self._already_started_eip = True              # we want to start soledad anyway after a certain timeout if eip              # fails to come up -            QtCore.QTimer.singleShot( -                self.EIP_START_TIMEOUT, -                self._maybe_run_soledad_setup_checks) +            QtDelayedCall(self.EIP_START_TIMEOUT, +                          self._maybe_run_soledad_setup_checks)          else:              if not self._already_started_eip:                  if EIP_SERVICE in self._enabled_services: @@ -1833,8 +1572,8 @@ class MainWindow(QtGui.QMainWindow):          TRIGGERS:              self._backend.signaler.eip_client_certificate_ready -        Starts the VPN thread if the eip configuration is properly -        loaded +        Start the VPN thread if the eip configuration is properly +        loaded.          """          passed = data[self._backend.PASSED_KEY] @@ -1846,11 +1585,11 @@ class MainWindow(QtGui.QMainWindow):              return          # DO START EIP Connection! -        self._eip_connection.qtsigs.do_connect_signal.emit() +        self._eip_conductor.do_connect()      @QtCore.Slot(dict)      def _eip_intermediate_stage(self, data): -        # TODO missing param +        # TODO missing param documentation          """          TRIGGERS:              self._backend.signaler.eip_config_ready diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py index 31938a70..f8e5479d 100644 --- a/src/leap/bitmask/gui/statemachines.py +++ b/src/leap/bitmask/gui/statemachines.py @@ -504,6 +504,11 @@ class ConnectionMachineBuilder(object):              conn.qtsigs.connection_died_signal,              states[_OFF]) +        # XXX adding this--------------------- +        states[_ON].addTransition( +            conn.qtsigs.do_disconnect_signal, +            states[_DIS]) +          # * If we receive the connection_aborted, we transition          #   from connecting to the off state          states[_CON].addTransition( @@ -551,7 +556,8 @@ class ConnectionMachineBuilder(object):          # TODO add tooltip          # OFF State ---------------------- -        off = QState() +        off = SignallingState( +            None, name=conn.name)          off_label = _tr("Turn {0}").format(              conn.Connected.short_label)          if button: @@ -587,7 +593,10 @@ class ConnectionMachineBuilder(object):          states[_CON] = connecting          # ON State ------------------------ -        on = QState() +        on = SignallingState( +            None, name=conn.name) +        on_label = _tr("Turn {0}").format( +            conn.Disconnected.short_label)          if button:              on.assignProperty(                  button, 'text', on_label) diff --git a/src/leap/bitmask/services/eip/conductor.py b/src/leap/bitmask/services/eip/conductor.py new file mode 100644 index 00000000..253f76c0 --- /dev/null +++ b/src/leap/bitmask/services/eip/conductor.py @@ -0,0 +1,269 @@ +# -*- coding: utf-8 -*- +# conductor.py +# Copyright (C) 2014 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/>. +""" +EIP Conductor module. +""" +import logging + +from PySide import QtCore + +from leap.bitmask.gui import statemachines +from leap.bitmask.services import EIP_SERVICE +from leap.bitmask.services import get_service_display_name +from leap.bitmask.services.eip.connection import EIPConnection +from leap.bitmask.platform_init import IS_MAC +from leap.bitmask.util import make_address + +QtDelayedCall = QtCore.QTimer.singleShot +logger = logging.getLogger(__name__) + + +class EIPConductor(object): + +    def __init__(self, settings, backend, **kwargs): +        """ +        Initializes EIP Conductor. + +        :param settings: +        :type settings: + +        :param backend: +        :type backend: +        """ +        self.eip_connection = EIPConnection() +        self.eip_name = get_service_display_name(EIP_SERVICE) +        self._settings = settings +        self._backend = backend + +        self._eip_status = None + +    @property +    def qtsigs(self): +        return self.eip_connection.qtsigs + +    def add_eip_widget(self, widget): +        """ +        Keep a reference to the passed eip status widget. + +        :param widget: the EIP Status widget. +        :type widget: QWidget +        """ +        self._eip_status = widget + +    def connect_signals(self): +        """ +        Connect signals. +        """ +        self.qtsigs.connecting_signal.connect(self._start_eip) +        self.qtsigs.disconnecting_signal.connect(self._stop_eip) +        self.qtsigs.disconnected_signal.connect(self._eip_status.eip_stopped) + +    def connect_backend_signals(self): +        """ +        Connect to backend signals. +        """ +        signaler = self._backend.signaler + +        # for conductor +        signaler.eip_process_restart_tls.connect(self._do_eip_restart) +        signaler.eip_process_restart_ping.connect(self._do_eip_restart) +        signaler.eip_process_finished.connect(self._eip_finished) + +        # for widget +        self._eip_status.connect_backend_signals() + +    def start_eip_machine(self, action): +        """ +        Initializes and starts the EIP state machine. +        Needs the reference to the eip_status widget not to be empty. + +        :action: QtAction +        """ +        action = action +        button = self._eip_status.eip_button +        label = self._eip_status.eip_label + +        builder = statemachines.ConnectionMachineBuilder(self.eip_connection) +        eip_machine = builder.make_machine(button=button, +                                           action=action, +                                           label=label) +        self.eip_machine = eip_machine +        self.eip_machine.start() +        logger.debug('eip machine started') + +    def do_connect(self): +        """ +        Start the connection procedure. +        Emits a signal that triggers the OFF -> Connecting sequence. +        This will call _start_eip via the state machine. +        """ +        self.qtsigs.do_connect_signal.emit() + +    @QtCore.Slot() +    def _start_eip(self): +        """ +        Starts EIP. +        """ +        # FIXME --- pass is_restart parameter to here ??? +        is_restart = self._eip_status and self._eip_status.is_restart + +        def reconnect(): +            self.qtsigs.disconnecting_signal.connect(self._stop_eip) + +        if is_restart: +            QtDelayedCall(0, reconnect) +        else: +            self._eip_status.eip_pre_up() +        self.user_stopped_eip = False + +        # Until we set an option in the preferences window, we'll assume that +        # by default we try to autostart. If we switch it off manually, it +        # won't try the next time. +        self._settings.set_autostart_eip(True) +        self._eip_status.is_restart = False + +        # DO the backend call! +        self._backend.eip_start() + +    @QtCore.Slot() +    def _stop_eip(self, restart=False): +        """ +        TRIGGERS: +          self.qsigs.do_disconnect_signal (via state machine) + +        Stops vpn process and makes gui adjustments to reflect +        the change of state. + +        :param restart: whether this is part of a eip restart. +        :type restart: bool +        """ +        self._eip_status.is_restart = restart +        self.user_stopped_eip = not restart + +        def reconnect_stop_signal(): +            self.qtsigs.disconnecting_signal.connect(self._stop_eip) + +        def on_disconnected_do_restart(): +            # hard restarts +            eip_status_label = self._eip_status.tr("{0} is restarting") +            eip_status_label = eip_status_label.format(self.eip_name) +            self._eip_status.eip_stopped(restart=True) +            self._eip_status.set_eip_status(eip_status_label, error=False) + +            QtDelayedCall(1000, self.qtsigs.do_connect_signal.emit) + +        if restart: +            # we bypass the on_eip_disconnected here +            #qtsigs.disconnected_signal.disconnect() +            self.qtsigs.disconnected_signal.connect(on_disconnected_do_restart) +            QtDelayedCall(0, self.qtsigs.disconnected_signal.emit) + +        # Call to the backend. +        self._backend.eip_stop(restart=restart) + +        self._eip_status.set_eipstatus_off(False) +        self._already_started_eip = False + +        logger.debug('Setting autostart to: False') +        self._settings.set_autostart_eip(False) + +        if self._logged_user: +            self._eip_status.set_provider( +                make_address( +                    self._logged_user, +                    self._get_best_provider_config().get_domain())) + +        self._eip_status.eip_stopped(restart=restart) + +        if restart: +            QtDelayedCall(50000, reconnect_stop_signal) + +    @QtCore.Slot() +    def _do_eip_restart(self): +        """ +        TRIGGERS: +            self._eip_connection.qtsigs.process_restart + +        Restart the connection. +        """ +        if self._eip_status is not None: +            self._eip_status.is_restart = True +        try: +            self.qtsigs.disconnecting_signal.disconnect() +        except Exception: +            logger.error("cannot disconnect signals") + +        def do_stop(*args): +            self._stop_eip(restart=True) + +        self.qtsigs.disconnecting_signal.connect(do_stop) +        self.qtsigs.do_disconnect_signal.emit() + +    @QtCore.Slot(int) +    def _eip_finished(self, exitCode): +        """ +        TRIGGERS: +            Signaler.eip_process_finished + +        Triggered when the EIP/VPN process finishes to set the UI +        accordingly. + +        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. + +        :param exitCode: the exit code of the eip process. +        :type exitCode: int +        """ +        # TODO Add error catching to the openvpn log observer +        # so we can have a more precise idea of which type +        # of error did we have (server side, local problem, etc) + +        logger.info("VPN process finished with exitCode %s..." +                    % (exitCode,)) + +        signal = self.qtsigs.disconnected_signal + +        # XXX check if these exitCodes are pkexec/cocoasudo specific +        if exitCode in (126, 127): +            eip_status_label = self._eip_status.tr( +                "{0} could not be launched " +                "because you did not authenticate properly.") +            eip_status_label = eip_status_label.format(self.eip_name) +            self._eip_status.set_eip_status(eip_status_label, error=True) +            signal = self.qtsigs.connection_aborted_signal +            self._backend.eip_terminate() + +        # XXX FIXME --- check exitcode is != 0 really +        if exitCode != 0 and not self.user_stopped_eip: +            eip_status_label = self._eip_status.tr( +                "{0} finished in an unexpected manner!") +            eip_status_label = eip_status_label.format(self.eip_name) +            self._eip_status.eip_stopped() +            self._eip_status.set_eip_status_icon("error") +            self._eip_status.set_eip_status(eip_status_label, +                                            error=True) +            signal = self.qtsigs.connection_died_signal + +        if exitCode == 0 and IS_MAC: +            # XXX remove this warning after I fix cocoasudo. +            logger.warning("The above exit code MIGHT BE WRONG.") + +        # We emit signals to trigger transitions in the state machine: +        signal.emit() diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index b068066f..6eef05af 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -116,10 +116,12 @@ class VPNObserver(object):          :returns: a Signaler signal or None          :rtype: str or None          """ +        sig = self._signaler          signals = { -            "network_unreachable": self._signaler.EIP_NETWORK_UNREACHABLE, -            "process_restart_tls": self._signaler.EIP_PROCESS_RESTART_TLS, -            "process_restart_ping": self._signaler.EIP_PROCESS_RESTART_PING, +            "network_unreachable": sig.EIP_NETWORK_UNREACHABLE, +            "process_restart_tls": sig.EIP_PROCESS_RESTART_TLS, +            "process_restart_ping": sig.EIP_PROCESS_RESTART_PING, +            "initialization_completed": sig.EIP_CONNECTED          }          return signals.get(event.lower()) @@ -318,6 +320,7 @@ class VPN(object):          # We assume that the only valid stops are initiated          # by an user action, not hard restarts          self._user_stopped = not restart +        self._vpnproc.is_restart = restart          # First we try to be polite and send a SIGTERM...          if self._vpnproc: @@ -755,7 +758,7 @@ class VPNManager(object):                  # However, that should be a rare case right now.                  self._send_command("signal SIGTERM")                  self._close_management_socket(announce=True) -            except Exception as e: +            except (Exception, AssertionError) as e:                  logger.warning("Problem trying to terminate OpenVPN: %r"                                 % (e,))          else: @@ -824,6 +827,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):          self._openvpn_verb = openvpn_verb          self._vpn_observer = VPNObserver(signaler) +        self.is_restart = False      # processProtocol methods @@ -859,7 +863,8 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):          exit_code = reason.value.exitCode          if isinstance(exit_code, int):              logger.debug("processExited, status %d" % (exit_code,)) -        self._signaler.signal(self._signaler.EIP_PROCESS_FINISHED, exit_code) +        self._signaler.signal( +            self._signaler.EIP_PROCESS_FINISHED, exit_code)          self._alive = False      def processEnded(self, reason): | 
