diff options
| -rw-r--r-- | src/leap/bitmask/backend.py | 278 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/eip_status.py | 55 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/mainwindow.py | 297 | ||||
| -rw-r--r-- | src/leap/bitmask/services/eip/vpnprocess.py | 167 | 
4 files changed, 433 insertions, 364 deletions
| diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 00a399ee..591b5da5 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -33,8 +33,13 @@ from leap.bitmask.crypto.srpauth import SRPAuth  from leap.bitmask.crypto.srpregister import SRPRegister  from leap.bitmask.provider import get_provider_path  from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper +from leap.bitmask.services.eip import eipconfig +from leap.bitmask.services.eip import get_openvpn_management  from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper +from leap.bitmask.services.eip import vpnlauncher, vpnprocess +from leap.bitmask.services.eip import linuxvpnlauncher, darwinvpnlauncher +  # Frontend side  from PySide import QtCore @@ -248,6 +253,140 @@ class Register(object):              logger.error("Could not load provider configuration.") +class EIP(object): +    """ +    Interfaces with setup and launch of EIP +    """ + +    zope.interface.implements(ILEAPService) + +    def __init__(self, signaler=None): +        """ +        Constructor for the EIP component + +        :param signaler: Object in charge of handling communication +                         back to the frontend +        :type signaler: Signaler +        """ +        object.__init__(self) +        self.key = "eip" +        self._signaler = signaler +        self._eip_bootstrapper = EIPBootstrapper(signaler) +        self._eip_setup_defer = None +        self._provider_config = ProviderConfig() + +        self._vpn = vpnprocess.VPN(signaler=signaler) + +    def setup_eip(self, domain): +        """ +        Initiates the setup for a provider + +        :param domain: URL for the provider +        :type domain: unicode + +        :returns: the defer for the operation running in a thread. +        :rtype: twisted.internet.defer.Deferred +        """ +        config = self._provider_config +        if get_provider_config(config, domain): +            eb = self._eip_bootstrapper +            d = eb.run_eip_setup_checks(self._provider_config, +                                        download_if_needed=True) +            self._eip_setup_defer = d +            return d +        else: +            raise Exception("No provider setup loaded") + +    def cancel_setup_eip(self): +        """ +        Cancel the ongoing setup eip defer (if any). +        """ +        d = self._eip_setup_defer +        if d is not None: +            d.cancel() + +    def _start_eip(self): +        """ +        Starts EIP +        """ +        provider_config = self._provider_config +        eip_config = eipconfig.EIPConfig() +        domain = provider_config.get_domain() + +        loaded = eipconfig.load_eipconfig_if_needed( +            provider_config, eip_config, domain) + +        if not loaded: +            if self._signaler is not None: +                self._signaler.signal(self._signaler.EIP_CONNECTION_ABORTED) +            logger.error("Tried to start EIP but cannot find any " +                         "available provider!") +            return + +        host, port = get_openvpn_management() +        self._vpn.start(eipconfig=eip_config, +                        providerconfig=provider_config, +                        socket_host=host, socket_port=port) + +    def start(self): +        """ +        Starts the service. +        """ +        signaler = self._signaler + +        if not self._provider_config.loaded(): +            # This means that the user didn't call setup_eip first. +            self._signaler.signal(signaler.BACKEND_BAD_CALL) +            return + +        try: +            self._start_eip() +        except vpnprocess.OpenVPNAlreadyRunning: +            signaler.signal(signaler.EIP_OPEN_VPN_ALREADY_RUNNING) +        except vpnprocess.AlienOpenVPNAlreadyRunning: +            signaler.signal(signaler.EIP_ALIEN_OPEN_VPN_ALREADY_RUNNING) +        except vpnlauncher.OpenVPNNotFoundException: +            signaler.signal(signaler.EIP_OPEN_VPN_NOT_FOUND_ERROR) +        except vpnlauncher.VPNLauncherException: +            # TODO: this seems to be used for 'gateway not found' only. +            #       see vpnlauncher.py +            signaler.signal(signaler.EIP_VPN_LAUNCHER_EXCEPTION) +        except linuxvpnlauncher.EIPNoPolkitAuthAgentAvailable: +            signaler.signal(signaler.EIP_NO_POLKIT_AGENT_ERROR) +        except linuxvpnlauncher.EIPNoPkexecAvailable: +            signaler.signal(signaler.EIP_NO_PKEXEC_ERROR) +        except darwinvpnlauncher.EIPNoTunKextLoaded: +            signaler.signal(signaler.EIP_NO_TUN_KEXT_ERROR) +        except Exception as e: +            logger.error("Unexpected problem: {0!r}".format(e)) +        else: +            # TODO: are we connected here? +            signaler.signal(signaler.EIP_CONNECTED) + +    def stop(self, shutdown=False): +        """ +        Stops the service. +        """ +        self._vpn.terminate(shutdown) + +    def terminate(self): +        """ +        Terminates the service, not necessarily in a nice way. +        """ +        self._vpn.killit() + +    def status(self): +        """ +        Returns a json object with the current status for the service. + +        :rtype: object (list, str, dict) +        """ +        # XXX: Use a namedtuple or a specific object instead of a json +        # object, since parsing it will be problematic otherwise. +        # It has to be something easily serializable though. +        pass + +  class Authenticate(object):      """      Interfaces with setup and bootstrapping operations for a provider @@ -355,57 +494,6 @@ class Authenticate(object):          self._signaler.signal(signal) -class EIP(object): -    """ -    Interfaces with setup and launch of EIP -    """ - -    zope.interface.implements(ILEAPComponent) - -    def __init__(self, signaler=None): -        """ -        Constructor for the EIP component - -        :param signaler: Object in charge of handling communication -                         back to the frontend -        :type signaler: Signaler -        """ -        object.__init__(self) -        self.key = "eip" -        self._eip_bootstrapper = EIPBootstrapper(signaler) -        self._eip_setup_defer = None -        self._provider_config = ProviderConfig() - -    def setup_eip(self, domain): -        """ -        Initiates the setup for a provider - -        :param domain: URL for the provider -        :type domain: unicode - -        :returns: the defer for the operation running in a thread. -        :rtype: twisted.internet.defer.Deferred -        """ -        config = self._provider_config -        if get_provider_config(config, domain): -            log.msg("") -            eb = self._eip_bootstrapper -            d = eb.run_eip_setup_checks(self._provider_config, -                                        download_if_needed=True) -            self._eip_setup_defer = d -            return d -        else: -            raise Exception("No provider setup loaded") - -    def cancel_setup_eip(self): -        """ -        Cancel the ongoing setup eip defer (if any). -        """ -        d = self._eip_setup_defer -        if d is not None: -            d.cancel() - -  class Signaler(QtCore.QObject):      """      Signaler object, handles converting string commands to Qt signals. @@ -437,7 +525,7 @@ class Signaler(QtCore.QObject):      srp_registration_failed = QtCore.Signal(object)      srp_registration_taken = QtCore.Signal(object) -    # Signals for EIP +    # Signals for EIP bootstrapping      eip_download_config = QtCore.Signal(object)      eip_download_client_certificate = QtCore.Signal(object) @@ -458,6 +546,35 @@ class Signaler(QtCore.QObject):      srp_status_logged_in = QtCore.Signal(object)      srp_status_not_logged_in = QtCore.Signal(object) +    # Signals for EIP +    eip_connected = QtCore.Signal(object) +    eip_disconnected = QtCore.Signal(object) +    eip_connection_died = QtCore.Signal(object) +    eip_connection_aborted = QtCore.Signal(object) + +    # EIP problems +    eip_no_polkit_agent_error = QtCore.Signal(object) +    eip_no_tun_kext_error = QtCore.Signal(object) +    eip_no_pkexec_error = QtCore.Signal(object) +    eip_openvpn_not_found_error = QtCore.Signal(object) +    eip_openvpn_already_running = QtCore.Signal(object) +    eip_alien_openvpn_already_running = QtCore.Signal(object) +    eip_vpn_launcher_exception = QtCore.Signal(object) + +    # signals from parsing openvpn output +    eip_network_unreachable = QtCore.Signal(object) +    eip_process_restart_tls = QtCore.Signal(object) +    eip_process_restart_ping = QtCore.Signal(object) + +    # signals from vpnprocess.py +    eip_state_changed = QtCore.Signal(dict) +    eip_status_changed = QtCore.Signal(dict) +    eip_process_finished = QtCore.Signal(int) + +    # This signal is used to warn the backend user that is doing something +    # wrong +    backend_bad_call = QtCore.Signal(object) +      ####################      # These will exist both in the backend AND the front end.      # The frontend might choose to not "interpret" all the signals @@ -497,11 +614,27 @@ class Signaler(QtCore.QObject):      EIP_DOWNLOAD_CLIENT_CERTIFICATE = "eip_download_client_certificate"      EIP_CANCELLED_SETUP = "eip_cancelled_setup" -    # TODO change the name of "download_config" signal to -    # something less confusing (config_ready maybe) -    EIP_DOWNLOAD_CONFIG = "eip_download_config" -    EIP_DOWNLOAD_CLIENT_CERTIFICATE = "eip_download_client_certificate" -    EIP_CANCELLED_SETUP = "eip_cancelled_setup" +    EIP_CONNECTED = "eip_connected" +    EIP_DISCONNECTED = "eip_disconnected" +    EIP_CONNECTION_DIED = "eip_connection_died" +    EIP_CONNECTION_ABORTED = "eip_connection_aborted" +    EIP_NO_POLKIT_AGENT_ERROR = "eip_no_polkit_agent_error" +    EIP_NO_TUN_KEXT_ERROR = "eip_no_tun_kext_error" +    EIP_NO_PKEXEC_ERROR = "eip_no_pkexec_error" +    EIP_OPENVPN_NOT_FOUND_ERROR = "eip_openvpn_not_found_error" +    EIP_OPENVPN_ALREADY_RUNNING = "eip_openvpn_already_running" +    EIP_ALIEN_OPENVPN_ALREADY_RUNNING = "eip_alien_openvpn_already_running" +    EIP_VPN_LAUNCHER_EXCEPTION = "eip_vpn_launcher_exception" + +    EIP_NETWORK_UNREACHABLE = "eip_network_unreachable" +    EIP_PROCESS_RESTART_TLS = "eip_process_restart_tls" +    EIP_PROCESS_RESTART_PING = "eip_process_restart_ping" + +    EIP_STATE_CHANGED = "eip_state_changed" +    EIP_STATUS_CHANGED = "eip_status_changed" +    EIP_PROCESS_FINISHED = "eip_process_finished" + +    BACKEND_BAD_CALL = "backend_bad_call"      def __init__(self):          """ @@ -530,6 +663,26 @@ class Signaler(QtCore.QObject):              self.EIP_DOWNLOAD_CLIENT_CERTIFICATE,              self.EIP_CANCELLED_SETUP, +            self.EIP_CONNECTED, +            self.EIP_DISCONNECTED, +            self.EIP_CONNECTION_DIED, +            self.EIP_CONNECTION_ABORTED, +            self.EIP_NO_POLKIT_AGENT_ERROR, +            self.EIP_NO_TUN_KEXT_ERROR, +            self.EIP_NO_PKEXEC_ERROR, +            self.EIP_OPENVPN_NOT_FOUND_ERROR, +            self.EIP_OPENVPN_ALREADY_RUNNING, +            self.EIP_ALIEN_OPENVPN_ALREADY_RUNNING, +            self.EIP_VPN_LAUNCHER_EXCEPTION, + +            self.EIP_NETWORK_UNREACHABLE, +            self.EIP_PROCESS_RESTART_TLS, +            self.EIP_PROCESS_RESTART_PING, + +            self.EIP_STATE_CHANGED, +            self.EIP_STATUS_CHANGED, +            self.EIP_PROCESS_FINISHED, +              self.SRP_AUTH_OK,              self.SRP_AUTH_ERROR,              self.SRP_AUTH_SERVER_ERROR, @@ -543,6 +696,8 @@ class Signaler(QtCore.QObject):              self.SRP_NOT_LOGGED_IN_ERROR,              self.SRP_STATUS_LOGGED_IN,              self.SRP_STATUS_NOT_LOGGED_IN, + +            self.BACKEND_BAD_CALL,          ]          for sig in signals: @@ -728,6 +883,15 @@ class Backend(object):      def cancel_setup_eip(self):          self._call_queue.put(("eip", "cancel_setup_eip", None)) +    def start_eip(self): +        self._call_queue.put(("eip", "start", None)) + +    def stop_eip(self, shutdown=False): +        self._call_queue.put(("eip", "stop", None, shutdown)) + +    def terminate_eip(self): +        self._call_queue.put(("eip", "terminate", None)) +      def login(self, provider, username, password):          self._call_queue.put(("authenticate", "login", None, provider,                                username, password)) diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 19942d9d..f24d87c7 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -25,7 +25,6 @@ from functools import partial  from PySide import QtCore, QtGui  from leap.bitmask.services.eip.connection import EIPConnection -from leap.bitmask.services.eip.vpnprocess import VPNManager  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 @@ -99,7 +98,7 @@ class EIPStatusWidget(QtGui.QWidget):          status figures.          """          self.DISPLAY_TRAFFIC_RATES = not self.DISPLAY_TRAFFIC_RATES -        self.update_vpn_status(None)  # refresh +        self.update_vpn_status()  # refresh      def _set_traffic_rates(self):          """ @@ -117,7 +116,7 @@ class EIPStatusWidget(QtGui.QWidget):          """          self._up_rate.reset()          self._down_rate.reset() -        self.update_vpn_status(None) +        self.update_vpn_status()      def _update_traffic_rates(self, up, down):          """ @@ -348,23 +347,26 @@ class EIPStatusWidget(QtGui.QWidget):              self.tr("Traffic is being routed in the clear"))          self.ui.lblEIPStatus.show() -    def update_vpn_status(self, data): +    def update_vpn_status(self, data=None):          """          SLOT -        TRIGGER: VPN.status_changed +            TRIGGER: Signaler.eip_status_changed -        Updates the download/upload labels based on the data provided -        by the VPN thread. +        Updates the download/upload labels based on the data provided by the +        VPN thread. +        If data is None, we just will refresh the display based on the previous +        data.          :param data: a dictionary with the tcp/udp write and read totals. -                     If data is None, we just will refresh the display based -                     on the previous data.          :type data: dict          """ -        if data: -            upload = float(data[VPNManager.TCPUDP_WRITE_KEY] or "0") -            download = float(data[VPNManager.TCPUDP_READ_KEY] or "0") -            self._update_traffic_rates(upload, download) +        if data is not None: +            try: +                upload, download = map(float, data) +                self._update_traffic_rates(upload, download) +            except Exception: +                # discard invalid data +                return          if self.DISPLAY_TRAFFIC_RATES:              uprate, downrate = self._get_traffic_rates() @@ -379,39 +381,38 @@ class EIPStatusWidget(QtGui.QWidget):          self.ui.btnUpload.setText(upload_str)          self.ui.btnDownload.setText(download_str) -    def update_vpn_state(self, data): +    def update_vpn_state(self, vpn_state):          """          SLOT -        TRIGGER: VPN.state_changed +            TRIGGER: Signaler.eip_state_changed          Updates the displayed VPN state based on the data provided by          the VPN thread.          Emits: -            If the status is connected, we emit EIPConnection.qtsigs. +            If the vpn_state is connected, we emit EIPConnection.qtsigs.              connected_signal          """ -        status = data[VPNManager.STATUS_STEP_KEY] -        self.set_eip_status_icon(status) -        if status == "CONNECTED": +        self.set_eip_status_icon(vpn_state) +        if vpn_state == "CONNECTED":              self.ui.eip_bandwidth.show()              self.ui.lblEIPStatus.hide()              # XXX should be handled by the state machine too.              self.eip_connection_connected.emit() -        # XXX should lookup status map in EIPConnection -        elif status == "AUTH": +        # XXX should lookup vpn_state map in EIPConnection +        elif vpn_state == "AUTH":              self.set_eip_status(self.tr("Authenticating...")) -        elif status == "GET_CONFIG": +        elif vpn_state == "GET_CONFIG":              self.set_eip_status(self.tr("Retrieving configuration...")) -        elif status == "WAIT": +        elif vpn_state == "WAIT":              self.set_eip_status(self.tr("Waiting to start...")) -        elif status == "ASSIGN_IP": +        elif vpn_state == "ASSIGN_IP":              self.set_eip_status(self.tr("Assigning IP")) -        elif status == "RECONNECTING": +        elif vpn_state == "RECONNECTING":              self.set_eip_status(self.tr("Reconnecting...")) -        elif status == "ALREADYRUNNING": +        elif vpn_state == "ALREADYRUNNING":              # Put the following calls in Qt's event queue, otherwise              # the UI won't update properly              QtCore.QTimer.singleShot( @@ -419,7 +420,7 @@ class EIPStatusWidget(QtGui.QWidget):              msg = self.tr("Unable to start VPN, it's already running.")              QtCore.QTimer.singleShot(0, partial(self.set_eip_status, msg))          else: -            self.set_eip_status(status) +            self.set_eip_status(vpn_state)      def set_eip_icon(self, icon):          """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 6eaf2dfb..985ad945 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -33,16 +33,16 @@ from leap.bitmask.config import flags  from leap.bitmask.config.leapsettings import LeapSettings  from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.gui.loggerwindow import LoggerWindow +from leap.bitmask.gui import statemachines  from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement -from leap.bitmask.gui.login import LoginWidget -from leap.bitmask.gui.preferenceswindow import PreferencesWindow  from leap.bitmask.gui.eip_preferenceswindow import EIPPreferencesWindow -from leap.bitmask.gui import statemachines  from leap.bitmask.gui.eip_status import EIPStatusWidget +from leap.bitmask.gui.loggerwindow import LoggerWindow +from leap.bitmask.gui.login import LoginWidget  from leap.bitmask.gui.mail_status import MailStatusWidget -from leap.bitmask.gui.wizard import Wizard +from leap.bitmask.gui.preferenceswindow import PreferencesWindow  from leap.bitmask.gui.systray import SysTray +from leap.bitmask.gui.wizard import Wizard  from leap.bitmask import provider  from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX @@ -55,19 +55,7 @@ from leap.bitmask.services import get_service_display_name  from leap.bitmask.services.mail import conductor as mail_conductor  from leap.bitmask.services import EIP_SERVICE, MX_SERVICE -from leap.bitmask.services.eip import eipconfig -from leap.bitmask.services.eip import get_openvpn_management  from leap.bitmask.services.eip.connection import EIPConnection -from leap.bitmask.services.eip.vpnprocess import VPN -from leap.bitmask.services.eip.vpnprocess import OpenVPNAlreadyRunning -from leap.bitmask.services.eip.vpnprocess import AlienOpenVPNAlreadyRunning - -from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException -from leap.bitmask.services.eip.vpnlauncher import OpenVPNNotFoundException -from leap.bitmask.services.eip.linuxvpnlauncher import EIPNoPkexecAvailable -from leap.bitmask.services.eip.linuxvpnlauncher import \ -    EIPNoPolkitAuthAgentAvailable -from leap.bitmask.services.eip.darwinvpnlauncher import EIPNoTunKextLoaded  from leap.bitmask.services.soledad.soledadbootstrapper import \      SoledadBootstrapper @@ -94,11 +82,6 @@ class MainWindow(QtGui.QMainWindow):      """      Main window for login and presenting status updates to the user      """ - -    # StackedWidget indexes -    LOGIN_INDEX = 0 -    EIP_STATUS_INDEX = 1 -      # Signals      eip_needs_login = QtCore.Signal([])      offline_mode_bypass_login = QtCore.Signal([]) @@ -117,9 +100,7 @@ class MainWindow(QtGui.QMainWindow):      # We give each service some time to come to a halt before forcing quit      SERVICE_STOP_TIMEOUT = 20 -    def __init__(self, quit_callback, -                 bypass_checks=False, -                 start_hidden=False): +    def __init__(self, quit_callback, bypass_checks=False, start_hidden=False):          """          Constructor for the client main window @@ -127,9 +108,8 @@ class MainWindow(QtGui.QMainWindow):                                the application.          :type quit_callback: callable -        :param bypass_checks: Set to true if the app should bypass -                              first round of checks for CA -                              certificates at bootstrap +        :param bypass_checks: Set to true if the app should bypass first round +                              of checks for CA certificates at bootstrap          :type bypass_checks: bool          :param start_hidden: Set to true if the app should not show the window                               but just the tray. @@ -188,21 +168,19 @@ class MainWindow(QtGui.QMainWindow):          # XXX this should be handled by EIP Conductor          self._eip_connection.qtsigs.connecting_signal.connect( -            self._start_eip) +            self._start_EIP)          self._eip_connection.qtsigs.disconnecting_signal.connect(              self._stop_eip)          self._eip_status.eip_connection_connected.connect( -            self._on_eip_connected) +            self._on_eip_connection_connected)          self._eip_status.eip_connection_connected.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.eip_needs_login.connect(self._eip_status.disable_eip_start) +        self.eip_needs_login.connect(self._disable_eip_start_action)          # This is loaded only once, there's a bug when doing that more          # than once @@ -212,7 +190,6 @@ class MainWindow(QtGui.QMainWindow):          # Used for automatic start of EIP          self._provisional_provider_config = ProviderConfig() -        self._eip_config = eipconfig.EIPConfig()          self._already_started_eip = False          self._already_started_soledad = False @@ -225,22 +202,6 @@ class MainWindow(QtGui.QMainWindow):          self._backend_connected_signals = {}          self._backend_connect() -        self._vpn = VPN() - -        # connect vpn process signals -        self._vpn.qtsigs.state_changed.connect( -            self._eip_status.update_vpn_state) -        self._vpn.qtsigs.status_changed.connect( -            self._eip_status.update_vpn_status) -        self._vpn.qtsigs.process_finished.connect( -            self._eip_finished) -        self._vpn.qtsigs.network_unreachable.connect( -            self._on_eip_network_unreachable) -        self._vpn.qtsigs.process_restart_tls.connect( -            self._do_eip_restart) -        self._vpn.qtsigs.process_restart_ping.connect( -            self._do_eip_restart) -          self._soledad_bootstrapper = SoledadBootstrapper()          self._soledad_bootstrapper.download_config.connect(              self._soledad_intermediate_stage) @@ -419,6 +380,7 @@ class MainWindow(QtGui.QMainWindow):          self._connect_and_track(sig.prov_cancelled_setup,                                  self._set_login_cancelled) +        # Login signals          self._connect_and_track(sig.srp_auth_ok, self._authentication_finished)          auth_error = ( @@ -442,16 +404,44 @@ class MainWindow(QtGui.QMainWindow):          self._connect_and_track(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) +        # EIP bootstrap signals +        self._connect_and_track(sig.eip_download_config, +                                self._eip_intermediate_stage) +        self._connect_and_track(sig.eip_download_client_certificate, +                                self._finish_eip_bootstrap) +          # We don't want to disconnect some signals so don't track them:          sig.prov_unsupported_client.connect(self._needs_update)          sig.prov_unsupported_api.connect(self._incompatible_api) +        # 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) +      def _disconnect_and_untrack(self):          """          Helper to disconnect the tracked signals. @@ -1495,7 +1485,7 @@ class MainWindow(QtGui.QMainWindow):          self._action_eip_startstop.setEnabled(True)      @QtCore.Slot() -    def _on_eip_connected(self): +    def _on_eip_connection_connected(self):          """          SLOT          TRIGGERS: @@ -1585,114 +1575,101 @@ class MainWindow(QtGui.QMainWindow):                           (default_provider,))      @QtCore.Slot() -    def _start_eip(self): +    def _start_EIP(self):          """ -        SLOT -        TRIGGERS: -          self._eip_connection.qtsigs.do_connect_signal -          (via state machine) -        or called from _finish_eip_bootstrap -          Starts EIP          """ -        provider_config = self._get_best_provider_config() -        provider = provider_config.get_domain()          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. +        # 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) -        loaded = eipconfig.load_eipconfig_if_needed( -            provider_config, self._eip_config, provider) +        self._backend.start_eip() -        if not loaded: -            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() -            logger.error("Tried to start EIP but cannot find any " -                         "available provider!") -            return +    def _on_eip_connection_aborted(self): +        """ +        SLOT +        TRIGGERS: +            Signaler.eip_connection_aborted +        """ +        logger.error("Tried to start EIP but cannot find any " +                     "available provider!") -        try: -            # XXX move this to EIPConductor -            host, port = get_openvpn_management() -            self._vpn.start(eipconfig=self._eip_config, -                            providerconfig=provider_config, -                            socket_host=host, -                            socket_port=port) -            self._settings.set_defaultprovider(provider) - -            # XXX move to the state machine too -            self._eip_status.set_provider(provider) - -        # TODO refactor exceptions so they provide translatable -        # usef-facing messages. -        except EIPNoPolkitAuthAgentAvailable: -            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() -        except EIPNoTunKextLoaded: -            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() -        except EIPNoPkexecAvailable: -            self._eip_status.set_eip_status( -                self.tr("We could not find <b>pkexec</b> " -                        "in your system."), -                error=True) -            self._set_eipstatus_off() -        except OpenVPNNotFoundException: -            self._eip_status.set_eip_status( -                self.tr("We could not find openvpn binary."), -                error=True) -            self._set_eipstatus_off() -        except OpenVPNAlreadyRunning as e: -            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() -        except AlienOpenVPNAlreadyRunning as e: -            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() -        except VPNLauncherException as e: -            # 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("%s" % (e,), error=True) -            self._set_eipstatus_off() -        else: -            self._already_started_eip = True +        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() + +    def _on_eip_connected(self): +        # XXX move to the state machine too +        self._eip_status.set_provider(provider) + +        self._settings.set_defaultprovider(provider) +        self._already_started_eip = True      @QtCore.Slot()      def _stop_eip(self):          """          SLOT          TRIGGERS: -          self._eip_connection.qtsigs.do_disconnect_signal -          (via state machine) -        or called from _eip_finished +          self._eip_connection.qtsigs.do_disconnect_signal (via state machine)          Stops vpn process and makes gui adjustments to reflect          the change of state. @@ -1701,7 +1678,7 @@ class MainWindow(QtGui.QMainWindow):          :type abnormal: bool          """          self.user_stopped_eip = True -        self._vpn.terminate() +        self._backend.stop_eip()          self._set_eipstatus_off(False)          self._already_started_eip = False @@ -1742,7 +1719,7 @@ class MainWindow(QtGui.QMainWindow):          # for some reason, emitting the do_disconnect/do_connect          # signals hangs the UI.          self._stop_eip() -        QtCore.QTimer.singleShot(2000, self._start_eip) +        QtCore.QTimer.singleShot(2000, self._start_EIP)      def _set_eipstatus_off(self, error=True):          """ @@ -1756,7 +1733,7 @@ class MainWindow(QtGui.QMainWindow):          """          SLOT          TRIGGERS: -          self._vpn.process_finished +            Signaler.eip_process_finished          Triggered when the EIP/VPN process finishes to set the UI          accordingly. @@ -1792,8 +1769,8 @@ class MainWindow(QtGui.QMainWindow):                  "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) -            self._vpn.killit()              signal = qtsigs.connection_aborted_signal +            self._backend.terminate_eip()          elif exitCode != 0 or not self.user_stopped_eip:              eip_status_label = self.tr("{0} finished in an unexpected manner!") @@ -1853,7 +1830,6 @@ class MainWindow(QtGui.QMainWindow):          Starts the VPN thread if the eip configuration is properly          loaded          """ -        leap_assert(self._eip_config, "We need an eip config!")          passed = data[self._backend.PASSED_KEY]          if not passed: @@ -1863,20 +1839,8 @@ class MainWindow(QtGui.QMainWindow):              self._already_started_eip = False              return -        provider_config = self._get_best_provider_config() -        domain = provider_config.get_domain() - -        # XXX  move check to _start_eip ? -        loaded = eipconfig.load_eipconfig_if_needed( -            provider_config, self._eip_config, domain) - -        if loaded: -            # DO START EIP Connection! -            self._eip_connection.qtsigs.do_connect_signal.emit() -        else: -            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) +        # DO START EIP Connection! +        self._eip_connection.qtsigs.do_connect_signal.emit()      def _eip_intermediate_stage(self, data):          # TODO missing param @@ -2051,7 +2015,7 @@ class MainWindow(QtGui.QMainWindow):              logger.error("No instance of soledad was found.")          logger.debug('Terminating vpn') -        self._vpn.terminate(shutdown=True) +        self._backend.stop_eip(shutdown=True)          self._cancel_ongoing_defers() @@ -2079,8 +2043,11 @@ class MainWindow(QtGui.QMainWindow):          # Set this in case that the app is hidden          QtGui.QApplication.setQuitOnLastWindowClosed(True) -        self._backend.stop()          self._cleanup_and_quit() + +        # We queue the call to stop since we need to wait until EIP is stopped. +        # Otherwise we may exit leaving an unmanaged openvpn process. +        reactor.callLater(0, self._backend.stop)          self._really_quit = True          if self._wizard: diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 4f797722..a17d94d2 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -33,8 +33,6 @@ except ImportError:      # psutil >= 2.0.0      from psutil import AccessDenied as psutil_AccessDenied -from PySide import QtCore -  from leap.bitmask.config import flags  from leap.bitmask.config.providerconfig import ProviderConfig  from leap.bitmask.services.eip import get_vpn_launcher @@ -53,28 +51,6 @@ from twisted.internet import error as internet_error  from twisted.internet.task import LoopingCall -class VPNSignals(QtCore.QObject): -    """ -    These are the signals that we use to let the UI know -    about the events we are polling. -    They are instantiated in the VPN object and passed along -    till the VPNProcess. -    """ -    # signals for the process -    state_changed = QtCore.Signal(dict) -    status_changed = QtCore.Signal(dict) -    process_finished = QtCore.Signal(int) - -    # signals that come from parsing -    # openvpn output -    network_unreachable = QtCore.Signal() -    process_restart_tls = QtCore.Signal() -    process_restart_ping = QtCore.Signal() - -    def __init__(self): -        QtCore.QObject.__init__(self) - -  class VPNObserver(object):      """      A class containing different patterns in the openvpn output that @@ -96,14 +72,8 @@ class VPNObserver(object):              "Initialization Sequence Completed",),      } -    def __init__(self, qtsigs): -        """ -        Initializer. Keeps a reference to the passed qtsigs object -        :param qtsigs: an object containing the different qt signals to -                       be used to communicate with different parts of -                       the application (the EIP state machine, for instance). -        """ -        self._qtsigs = qtsigs +    def __init__(self, signaler=None): +        self._signaler = signaler      def watch(self, line):          """ @@ -124,27 +94,29 @@ class VPNObserver(object):              return          sig = self._get_signal(event) -        if sig: -            sig.emit() +        if sig is not None: +            self._signaler.signal(sig)              return          else: -            logger.debug( -                'We got %s event from openvpn output but we ' -                'could not find a matching signal for it.' -                % event) +            logger.debug('We got %s event from openvpn output but we could ' +                         'not find a matching signal for it.' % event)      def _get_signal(self, event):          """          Tries to get the matching signal from the eip signals          objects based on the name of the passed event (in lowercase) -        :param event: the name of the event that we want to get a signal -                      for +        :param event: the name of the event that we want to get a signal for          :type event: str -        :returns: a QtSignal, or None -        :rtype: QtSignal or None +        :returns: a Signaler signal or None +        :rtype: str or None          """ -        return getattr(self._qtsigs, event.lower(), None) +        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, +        } +        return signals.get(event.lower())  class OpenVPNAlreadyRunning(Exception): @@ -182,14 +154,10 @@ class VPN(object):          self._vpnproc = None          self._pollers = []          self._reactor = reactor -        self._qtsigs = VPNSignals() +        self._signaler = kwargs['signaler']          self._openvpn_verb = flags.OPENVPN_VERBOSITY -    @property -    def qtsigs(self): -        return self._qtsigs -      def start(self, *args, **kwargs):          """          Starts the openvpn subprocess. @@ -200,14 +168,13 @@ class VPN(object):          :param kwargs: kwargs to be passed to the VPNProcess          :type kwargs: dict          """ +        logger.debug('VPN: start')          self._stop_pollers() -        kwargs['qtsigs'] = self.qtsigs          kwargs['openvpn_verb'] = self._openvpn_verb +        kwargs['signaler'] = self._signaler          # start the main vpn subprocess          vpnproc = VPNProcess(*args, **kwargs) -                             #qtsigs=self.qtsigs, -                             #openvpn_verb=self._openvpn_verb)          if vpnproc.get_openvpn_process():              logger.info("Another vpn process is running. Will try to stop it.") @@ -328,37 +295,21 @@ class VPNManager(object):      POLL_TIME = 2.5 if IS_MAC else 1.0      CONNECTION_RETRY_TIME = 1 -    TS_KEY = "ts" -    STATUS_STEP_KEY = "status_step" -    OK_KEY = "ok" -    IP_KEY = "ip" -    REMOTE_KEY = "remote" - -    TUNTAP_READ_KEY = "tun_tap_read" -    TUNTAP_WRITE_KEY = "tun_tap_write" -    TCPUDP_READ_KEY = "tcp_udp_read" -    TCPUDP_WRITE_KEY = "tcp_udp_write" -    AUTH_READ_KEY = "auth_read" - -    def __init__(self, qtsigs=None): +    def __init__(self, signaler=None):          """          Initializes the VPNManager. -        :param qtsigs: a QObject containing the Qt signals used by the UI -                       to give feedback about state changes. -        :type qtsigs: QObject +        :param signaler: Signaler object used to send notifications to the +                         backend +        :type signaler: backend.Signaler          """          from twisted.internet import reactor          self._reactor = reactor          self._tn = None -        self._qtsigs = qtsigs +        self._signaler = signaler          self._aborted = False      @property -    def qtsigs(self): -        return self._qtsigs - -    @property      def aborted(self):          return self._aborted @@ -552,17 +503,10 @@ class VPNManager(object):                  continue              ts, status_step, ok, ip, remote = parts -            state_dict = { -                self.TS_KEY: ts, -                self.STATUS_STEP_KEY: status_step, -                self.OK_KEY: ok, -                self.IP_KEY: ip, -                self.REMOTE_KEY: remote -            } - -            if state_dict != self._last_state: -                self.qtsigs.state_changed.emit(state_dict) -                self._last_state = state_dict +            state = status_step +            if state != self._last_state: +                self._signaler.signal(self._signaler.EIP_STATE_CHANGED, state) +                self._last_state = state      def _parse_status_and_notify(self, output):          """ @@ -575,9 +519,7 @@ class VPNManager(object):          """          tun_tap_read = ""          tun_tap_write = "" -        tcp_udp_read = "" -        tcp_udp_write = "" -        auth_read = "" +          for line in output:              stripped = line.strip()              if stripped.endswith("STATISTICS") or stripped == "END": @@ -585,28 +527,24 @@ class VPNManager(object):              parts = stripped.split(",")              if len(parts) < 2:                  continue -            if parts[0].strip() == "TUN/TAP read bytes": -                tun_tap_read = parts[1] -            elif parts[0].strip() == "TUN/TAP write bytes": -                tun_tap_write = parts[1] -            elif parts[0].strip() == "TCP/UDP read bytes": -                tcp_udp_read = parts[1] -            elif parts[0].strip() == "TCP/UDP write bytes": -                tcp_udp_write = parts[1] -            elif parts[0].strip() == "Auth read bytes": -                auth_read = parts[1] - -        status_dict = { -            self.TUNTAP_READ_KEY: tun_tap_read, -            self.TUNTAP_WRITE_KEY: tun_tap_write, -            self.TCPUDP_READ_KEY: tcp_udp_read, -            self.TCPUDP_WRITE_KEY: tcp_udp_write, -            self.AUTH_READ_KEY: auth_read -        } -        if status_dict != self._last_status: -            self.qtsigs.status_changed.emit(status_dict) -            self._last_status = status_dict +            text, value = parts +            # text can be: +            #   "TUN/TAP read bytes" +            #   "TUN/TAP write bytes" +            #   "TCP/UDP read bytes" +            #   "TCP/UDP write bytes" +            #   "Auth read bytes" + +            if text == "TUN/TAP read bytes": +                tun_tap_read = value +            elif text == "TUN/TAP write bytes": +                tun_tap_write = value + +        status = (tun_tap_write, tun_tap_read) +        if status != self._last_status: +            self._signaler.signal(self._signaler.EIP_STATUS_CHANGED, status) +            self._last_status = status      def get_state(self):          """ @@ -754,7 +692,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):      """      def __init__(self, eipconfig, providerconfig, socket_host, socket_port, -                 qtsigs, openvpn_verb): +                 signaler, openvpn_verb):          """          :param eipconfig: eip configuration object          :type eipconfig: EIPConfig @@ -769,18 +707,17 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):                              socket, or port otherwise          :type socket_port: str -        :param qtsigs: a QObject containing the Qt signals used to notify the -                       UI. -        :type qtsigs: QObject +        :param signaler: Signaler object used to receive notifications to the +                         backend +        :type signaler: backend.Signaler          :param openvpn_verb: the desired level of verbosity in the                               openvpn invocation          :type openvpn_verb: int          """ -        VPNManager.__init__(self, qtsigs=qtsigs) +        VPNManager.__init__(self, signaler=signaler)          leap_assert_type(eipconfig, EIPConfig)          leap_assert_type(providerconfig, ProviderConfig) -        leap_assert_type(qtsigs, QtCore.QObject)          #leap_assert(not self.isRunning(), "Starting process more than once!") @@ -799,7 +736,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):          # the parameter around.          self._openvpn_verb = openvpn_verb -        self._vpn_observer = VPNObserver(qtsigs) +        self._vpn_observer = VPNObserver(signaler)      # processProtocol methods @@ -835,7 +772,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):          exit_code = reason.value.exitCode          if isinstance(exit_code, int):              logger.debug("processExited, status %d" % (exit_code,)) -        self.qtsigs.process_finished.emit(exit_code) +        self._signaler.signal(self._signaler.EIP_PROCESS_FINISHED, exit_code)          self._alive = False      def processEnded(self, reason): | 
