diff options
25 files changed, 349 insertions, 136 deletions
| diff --git a/changes/VERSION_COMPAT b/changes/VERSION_COMPAT index cc00ecf7..a4f0a0ac 100644 --- a/changes/VERSION_COMPAT +++ b/changes/VERSION_COMPAT @@ -8,3 +8,5 @@  #  # BEGIN DEPENDENCY LIST -------------------------  # leap.foo.bar>=x.y.z +leap.keymanager>=0.3.4 +leap.mail>=0.3.5 diff --git a/changes/bug-4025_display-soledad-errors b/changes/bug-4025_display-soledad-errors new file mode 100644 index 00000000..35fd0025 --- /dev/null +++ b/changes/bug-4025_display-soledad-errors @@ -0,0 +1,2 @@ +  o In case of Soledad failure, display to the user that there was a problem. +    Closes #4025. diff --git a/changes/bug-4058_spacer-policy-problem b/changes/bug-4058_spacer-policy-problem new file mode 100644 index 00000000..b4759c7b --- /dev/null +++ b/changes/bug-4058_spacer-policy-problem @@ -0,0 +1 @@ +  o Widget squashing problem in wizard cheking a new provider. Closes #4058. diff --git a/changes/bug_catch_u1db_httperror b/changes/bug_catch_u1db_httperror new file mode 100644 index 00000000..1045f1d1 --- /dev/null +++ b/changes/bug_catch_u1db_httperror @@ -0,0 +1 @@ +  o Catch u1db errors during soledad initialization. diff --git a/changes/bug_display_mail_status_enabled b/changes/bug_display_mail_status_enabled new file mode 100644 index 00000000..44a94d5f --- /dev/null +++ b/changes/bug_display_mail_status_enabled @@ -0,0 +1,2 @@ +  o Display mail status in the tray icon as an enabled item. Fixes +    #4036.
\ No newline at end of file diff --git a/changes/bug_hide_error_message b/changes/bug_hide_error_message new file mode 100644 index 00000000..512f35da --- /dev/null +++ b/changes/bug_hide_error_message @@ -0,0 +1,2 @@ +  o Hide login error message when the user interacts with the widgets +    to fix the potential problem. Fixes #4022.
\ No newline at end of file diff --git a/changes/bug_increase_retries_logout b/changes/bug_increase_retries_logout new file mode 100644 index 00000000..a3d1458b --- /dev/null +++ b/changes/bug_increase_retries_logout @@ -0,0 +1,2 @@ +  o Increase the amount of retries for the authentication request +    session. Fixes #4037.
\ No newline at end of file diff --git a/changes/bug_util_menu_as_file b/changes/bug_util_menu_as_file new file mode 100644 index 00000000..48228f26 --- /dev/null +++ b/changes/bug_util_menu_as_file @@ -0,0 +1 @@ +  o Change the Util menu to be named File in OSX. Fixes #4039.
\ No newline at end of file diff --git a/changes/feature-3923_more-verbose-logs b/changes/feature-3923_more-verbose-logs new file mode 100644 index 00000000..b6ac6edd --- /dev/null +++ b/changes/feature-3923_more-verbose-logs @@ -0,0 +1 @@ +  o Show more context information in the logs. Closes #3923. diff --git a/changes/feature-4028_support-lxpolkit b/changes/feature-4028_support-lxpolkit new file mode 100644 index 00000000..95aa5220 --- /dev/null +++ b/changes/feature-4028_support-lxpolkit @@ -0,0 +1 @@ +  o Add support for running lxde polkit agent. Closes #4028. diff --git a/changes/feature_openvpn-observer b/changes/feature_openvpn-observer new file mode 100644 index 00000000..dc9ba0af --- /dev/null +++ b/changes/feature_openvpn-observer @@ -0,0 +1,3 @@ +  o Implements openvpn observer. Closes: #3901 +  o Reconnect EIP if network down. Closes #3790 +  o Reconnect if tls-restart. Closes: #3262 diff --git a/data/images/mask-icon.png b/data/images/mask-icon.pngBinary files differ index 3504bae9..97c5d7ec 100644 --- a/data/images/mask-icon.png +++ b/data/images/mask-icon.png diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index c1859478..5982fe91 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -99,7 +99,7 @@ def add_logger_handlers(debug=False, logfile=None):      logger = logging.getLogger(name='leap')      logger.setLevel(level) -    log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' +    log_format = '%(asctime)s - %(name)s:%(funcName)s:L#%(lineno)s - %(levelname)s - %(message)s'      formatter = logging.Formatter(log_format)      # Console handler diff --git a/src/leap/bitmask/config/__init__.py b/src/leap/bitmask/config/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/leap/bitmask/config/__init__.py diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index cbff4b49..47ed21b0 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -25,6 +25,7 @@ import json  #this error is raised from requests  from simplejson.decoder import JSONDecodeError  from functools import partial +from requests.adapters import HTTPAdapter  from PySide import QtCore  from twisted.internet import threads @@ -154,7 +155,8 @@ class SRPAuth(QtCore.QObject):              self._ng = self._srp.NG_1024              # **************************************************** # -            self._session = self._fetcher.session() +            self._reset_session() +              self._session_id = None              self._session_id_lock = QtCore.QMutex()              self._uid = None @@ -172,6 +174,18 @@ class SRPAuth(QtCore.QObject):              self._username = None              self._password = None +        def _reset_session(self): +            """ +            Resets the current session and sets max retries to 30. +            """ +            self._session = self._fetcher.session() +            # We need to bump the default retries, otherwise logout +            # fails most of the times +            # NOTE: This is a workaround for the moment, the server +            # side seems to return correctly every time, but it fails +            # on the client end. +            self._session.mount('https://', HTTPAdapter(max_retries=30)) +          def _safe_unhexlify(self, val):              """              Rounds the val to a multiple of 2 and returns the @@ -508,7 +522,7 @@ class SRPAuth(QtCore.QObject):              self._username = username              self._password = password -            self._session = self._fetcher.session() +            self._reset_session()              d = threads.deferToThread(self._authentication_preprocessing,                                        username=username, diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index 582f26be..c8c5c402 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -94,6 +94,9 @@ class LoginWidget(QtGui.QWidget):          self.ui.clblErrorMsg.hide()          self.ui.clblErrorMsg.clicked.connect(self.ui.clblErrorMsg.hide) +        self.ui.lnUser.textEdited.connect(self.ui.clblErrorMsg.hide) +        self.ui.lnPassword.textEdited.connect(self.ui.clblErrorMsg.hide) +      def _remember_state_changed(self, state):          """          Saves the remember state in the LeapSettings diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index ab9052d7..2ac9a332 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -35,7 +35,6 @@ class MailStatusWidget(QtGui.QWidget):      """      Status widget that displays the state of the LEAP Mail service      """ -    eip_connection_connected = QtCore.Signal()      _soledad_event = QtCore.Signal(object)      _smtp_event = QtCore.Signal(object)      _imap_event = QtCore.Signal(object) @@ -193,6 +192,17 @@ class MailStatusWidget(QtGui.QWidget):          leap_assert_type(action_mail_status, QtGui.QAction)          self._action_mail_status = action_mail_status +    def set_soledad_failed(self): +        """ +        SLOT +        TRIGGER: +            SoledadBootstrapper.soledad_failed + +        This method is called whenever soledad has a failure. +        """ +        msg = self.tr("There was an unexpected problem with Soledad.") +        self._set_mail_status(msg, ready=-1) +      def _set_mail_status(self, status, ready=0):          """          Sets the Mail status in the label and in the tray icon. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 84f09fd9..69545751 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -174,10 +174,12 @@ class MainWindow(QtGui.QMainWindow):          self._eip_connection = EIPConnection() +        # 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._eip_status.eip_connection_connected.connect(              self._on_eip_connected)          self.eip_needs_login.connect( @@ -228,13 +230,22 @@ class MainWindow(QtGui.QMainWindow):              self._eip_intermediate_stage)          self._eip_bootstrapper.download_client_certificate.connect(              self._finish_eip_bootstrap) +          self._vpn = VPN(openvpn_verb=openvpn_verb) + +        # 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( @@ -243,8 +254,8 @@ class MainWindow(QtGui.QMainWindow):              self._soledad_bootstrapped_stage)          self._soledad_bootstrapper.soledad_timeout.connect(              self._retry_soledad_connection) -        # XXX missing connect to soledad_failed (signal unrecoverable to user) -        # TODO wait until chiiph ui refactor. +        self._soledad_bootstrapper.soledad_failed.connect( +            self._mail_status.set_soledad_failed)          self._smtp_bootstrapper = SMTPBootstrapper()          self._smtp_bootstrapper.download_config.connect( @@ -258,7 +269,7 @@ class MainWindow(QtGui.QMainWindow):              self._launch_wizard)          if IS_MAC: -            self.ui.menuFile.menuAction().setText(self.tr("Util")) +            self.ui.menuFile.menuAction().setText(self.tr("File"))          self.raise_window.connect(self._do_raise_mainwindow) @@ -267,8 +278,9 @@ class MainWindow(QtGui.QMainWindow):          self._systray = None +        # XXX separate actions into a different +        # module.          self._action_mail_status = QtGui.QAction(self.tr("Mail is OFF"), self) -        self._action_mail_status.setEnabled(False)          self._mail_status.set_action_mail_status(self._action_mail_status)          self._action_eip_startstop = QtGui.QAction("", self) @@ -399,6 +411,8 @@ class MainWindow(QtGui.QMainWindow):          :return: a logging handler or None          :rtype: LeapLogHandler or None          """ +        # TODO this can be a function, does not need +        # to be a method.          leap_logger = logging.getLogger('leap')          for h in leap_logger.handlers:              if isinstance(h, LeapLogHandler): @@ -464,6 +478,10 @@ class MainWindow(QtGui.QMainWindow):          """          self._soledad_ready = True +    # +    # updates +    # +      def _new_updates_available(self, req):          """          Callback for the new updates event @@ -591,43 +609,9 @@ class MainWindow(QtGui.QMainWindow):                          saved_password.decode("utf8"))                      self._login() -    def _try_autostart_eip(self): -        """ -        Tries to autostart EIP -        """ -        settings = self._settings - -        should_autostart = settings.get_autostart_eip() -        if not should_autostart: -            logger.debug('Will not autostart EIP since it is setup ' -                         'to not to do it') -            self.eip_needs_login.emit() -            return - -        default_provider = settings.get_defaultprovider() - -        if default_provider is None: -            logger.info("Cannot autostart Encrypted Internet because there is " -                        "no default provider configured") -            self.eip_needs_login.emit() -            return - -        self._enabled_services = settings.get_enabled_services( -            default_provider) - -        loaded = self._provisional_provider_config.load( -            provider.get_provider_path(default_provider)) -        if loaded: -            # XXX I think we should not try to re-download config every time, -            # it adds some delay. -            # Maybe if it's the first run in a session, -            # or we can try only if it fails. -            self._download_eip_config() -        else: -            # XXX: Display a proper message to the user -            self.eip_needs_login.emit() -            logger.error("Unable to load %s config, cannot autostart." % -                         (default_provider,)) +    # +    # systray +    #      def _show_systray(self):          """ @@ -971,7 +955,11 @@ class MainWindow(QtGui.QMainWindow):          self._download_eip_config() +    ################################################################### +    # Service control methods: soledad +      def _soledad_intermediate_stage(self, data): +        # TODO missing param docstring          """          SLOT          TRIGGERS: @@ -1222,16 +1210,53 @@ class MainWindow(QtGui.QMainWindow):          signal that currently is beeing processed under status_panel.          After the refactor to EIPConductor this should not be necessary.          """ -        logger.debug('EIP connected signal received ...')          self._eip_connection.qtsigs.connected_signal.emit() +    def _try_autostart_eip(self): +        """ +        Tries to autostart EIP +        """ +        settings = self._settings + +        should_autostart = settings.get_autostart_eip() +        if not should_autostart: +            logger.debug('Will not autostart EIP since it is setup ' +                         'to not to do it') +            self.eip_needs_login.emit() +            return + +        default_provider = settings.get_defaultprovider() + +        if default_provider is None: +            logger.info("Cannot autostart Encrypted Internet because there is " +                        "no default provider configured") +            self.eip_needs_login.emit() +            return + +        self._enabled_services = settings.get_enabled_services( +            default_provider) + +        loaded = self._provisional_provider_config.load( +            provider.get_provider_path(default_provider)) +        if loaded: +            # XXX I think we should not try to re-download config every time, +            # it adds some delay. +            # Maybe if it's the first run in a session, +            # or we can try only if it fails. +            self._download_eip_config() +        else: +            # XXX: Display a proper message to the user +            self.eip_needs_login.emit() +            logger.error("Unable to load %s config, cannot autostart." % +                         (default_provider,)) +      @QtCore.Slot()      def _start_eip(self):          """          SLOT          TRIGGERS: -          self._eip_status.start_eip -          self._action_eip_startstop.triggered +          self._eip_connection.qtsigs.do_connect_signal +          (via state machine)          or called from _finish_eip_bootstrap          Starts EIP @@ -1332,8 +1357,8 @@ class MainWindow(QtGui.QMainWindow):          """          SLOT          TRIGGERS: -          self._eip_status.stop_eip -          self._action_eip_startstop.triggered +          self._eip_connection.qtsigs.do_disconnect_signal +          (via state machine)          or called from _eip_finished          Stops vpn process and makes gui adjustments to reflect @@ -1357,14 +1382,100 @@ class MainWindow(QtGui.QMainWindow):                             self._get_best_provider_config().get_domain()))          self._eip_status.eip_stopped() +    @QtCore.Slot() +    def _on_eip_network_unreachable(self): +        # XXX Should move to EIP Conductor +        """ +        SLOT +        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 +        """ +        SLOT +            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() +        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(self.tr("EIP has stopped"),                                          error=error)          self._eip_status.set_eip_status_icon("error") +    def _eip_finished(self, exitCode): +        """ +        SLOT +        TRIGGERS: +          self._vpn.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): +            self._eip_status.set_eip_status( +                self.tr("Encrypted Internet could not be launched " +                        "because you did not authenticate properly."), +                error=True) +            self._vpn.killit() +            signal = qtsigs.connection_aborted_signal + +        elif exitCode != 0 or not self.user_stopped_eip: +            self._eip_status.set_eip_status( +                self.tr("Encrypted Internet finished in an " +                        "unexpected manner!"), 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 _download_eip_config(self):          """          Starts the EIP bootstrapping sequence @@ -1426,7 +1537,25 @@ class MainWindow(QtGui.QMainWindow):                          "Configuration."),                  error=True) -    # end eip methods ------------------------------------------- +    def _eip_intermediate_stage(self, data): +        # TODO missing param +        """ +        SLOT +        TRIGGERS: +          self._eip_bootstrapper.download_config + +        If there was a problem, displays it, otherwise it does nothing. +        This is used for intermediate bootstrapping stages, in case +        they fail. +        """ +        passed = data[self._provider_bootstrapper.PASSED_KEY] +        if not passed: +            self._login_widget.set_status( +                self.tr("Unable to connect: Problem with provider")) +            logger.error(data[self._provider_bootstrapper.ERROR_KEY]) +            self._already_started_eip = False + +    # end of EIP methods ---------------------------------------------      def _get_best_provider_config(self):          """ @@ -1469,6 +1598,7 @@ class MainWindow(QtGui.QMainWindow):          self.logout.emit()      def _done_logging_out(self, ok, message): +        # TODO missing params in docstring          """          SLOT          TRIGGER: self._srp_auth.logout_finished @@ -1489,6 +1619,7 @@ class MainWindow(QtGui.QMainWindow):                  error=True)      def _intermediate_stage(self, data): +        # TODO this method name is confusing as hell.          """          SLOT          TRIGGERS: @@ -1508,80 +1639,9 @@ class MainWindow(QtGui.QMainWindow):                  self.tr("Unable to connect: Problem with provider"))              logger.error(data[self._provider_bootstrapper.ERROR_KEY]) -    def _eip_intermediate_stage(self, data): -        """ -        SLOT -        TRIGGERS: -          self._eip_bootstrapper.download_config - -        If there was a problem, displays it, otherwise it does nothing. -        This is used for intermediate bootstrapping stages, in case -        they fail. -        """ -        passed = data[self._provider_bootstrapper.PASSED_KEY] -        if not passed: -            self._login_widget.set_status( -                self.tr("Unable to connect: Problem with provider")) -            logger.error(data[self._provider_bootstrapper.ERROR_KEY]) -            self._already_started_eip = False - -    def _eip_finished(self, exitCode): -        """ -        SLOT -        TRIGGERS: -          self._vpn.process_finished - -        Triggered when the EIP/VPN process finishes to set the UI -        accordingly. -        """ -        # TODO move to EIPConductor. -        logger.info("VPN process finished with exitCode %s..." -                    % (exitCode,)) - -        # Ideally we would have the right exit code here, -        # but the use of different wrappers (pkexec, cocoasudo) swallows -        # the openvpn exit code so we get zero exit in some cases  where we -        # shouldn't. As a workaround we just use a flag to indicate -        # a purposeful switch off, and mark everything else as unexpected. - -        # In the near future we should trigger a native notification from here, -        # since the user really really wants to know she is unprotected asap. -        # And the right thing to do will be to fail-close. - -        # TODO we should have a way of parsing the latest lines in the vpn -        # log buffer so we can have a more precise idea of which type -        # of error did we have (server side, local problem, etc) - -        qtsigs = self._eip_connection.qtsigs -        signal = qtsigs.disconnected_signal - -        # XXX check if these exitCodes are pkexec/cocoasudo specific -        if exitCode in (126, 127): -            self._eip_status.set_eip_status( -                self.tr("Encrypted Internet could not be launched " -                        "because you did not authenticate properly."), -                error=True) -            self._vpn.killit() -            signal = qtsigs.connection_aborted_signal - -        elif exitCode != 0 or not self.user_stopped_eip: -            self._eip_status.set_eip_status( -                self.tr("Encrypted Internet finished in an " -                        "unexpected manner!"), 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.") - -        # XXX verify that the logic kees the same w/o the abnormal flag -        # after the refactor to EIPConnection has been completed -        # (eipconductor taking the most of the logic under transitions -        # that right now are handled under status_panel) -        #self._stop_eip(abnormal) - -        # We emit signals to trigger transitions in the state machine: -        signal.emit() +    # +    # window handling methods +    #      def _on_raise_window_event(self, req):          """ @@ -1607,6 +1667,10 @@ class MainWindow(QtGui.QMainWindow):          if IS_MAC:              self.raise_() +    # +    # cleanup and quit methods +    # +      def _cleanup_pidfiles(self):          """          Removes lockfiles on a clean shutdown. diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui index 0f6eef6e..cf591470 100644 --- a/src/leap/bitmask/gui/ui/wizard.ui +++ b/src/leap/bitmask/gui/ui/wizard.ui @@ -7,19 +7,19 @@      <x>0</x>      <y>0</y>      <width>536</width> -    <height>490</height> +    <height>510</height>     </rect>    </property>    <property name="minimumSize">     <size>      <width>536</width> -    <height>490</height> +    <height>510</height>     </size>    </property>    <property name="maximumSize">     <size>      <width>536</width> -    <height>490</height> +    <height>510</height>     </size>    </property>    <property name="windowTitle"> @@ -124,7 +124,7 @@         <enum>Qt::Vertical</enum>        </property>        <property name="sizeType"> -       <enum>QSizePolicy::Fixed</enum> +       <enum>QSizePolicy::Expanding</enum>        </property>        <property name="sizeHint" stdset="0">         <size> diff --git a/src/leap/bitmask/services/eip/connection.py b/src/leap/bitmask/services/eip/connection.py index 08b29070..962d9cf2 100644 --- a/src/leap/bitmask/services/eip/connection.py +++ b/src/leap/bitmask/services/eip/connection.py @@ -46,4 +46,5 @@ class EIPConnectionSignals(QtCore.QObject):  class EIPConnection(AbstractLEAPConnection):      def __init__(self): +        # XXX this should be public instead          self._qtsigs = EIPConnectionSignals() diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index c2c28627..efb23285 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -61,9 +61,13 @@ def _is_auth_agent_running():      :return: True if it's running, False if it's not.      :rtype: boolean      """ -    ps = 'ps aux | grep polkit-%s-authentication-agent-1' -    opts = (ps % case for case in ['[g]nome', '[k]de']) -    is_running = map(lambda l: commands.getoutput(l), opts) +    # the [x] thing is to avoid grep match itself +    polkit_options = [ +        'ps aux | grep polkit-[g]nome-authentication-agent-1', +        'ps aux | grep polkit-[k]de-authentication-agent-1', +        'ps aux | grep [l]xpolkit' +    ] +    is_running = [commands.getoutput(cmd) for cmd in polkit_options]      return any(is_running) diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index 935d75f1..82d8ea48 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -241,6 +241,10 @@ class VPNLauncher(object):              '--ca', providerconfig.get_ca_cert_path()          ] +        args += [ +            '--ping', '10', +            '--ping-restart', '30'] +          command_and_args = [openvpn] + args          logger.debug("Running VPN with command:")          logger.debug(" ".join(command_and_args)) diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 707967e0..9baa4c53 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -24,6 +24,8 @@ import psutil.error  import shutil  import socket +from itertools import chain, repeat +  from PySide import QtCore  from leap.bitmask.config.providerconfig import ProviderConfig @@ -50,14 +52,93 @@ class VPNSignals(QtCore.QObject):      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 +    we can react upon. +    """ + +    # TODO this is i18n-sensitive, right? +    # in that case, we should add the translations :/ +    # until we find something better. + +    _events = { +        'NETWORK_UNREACHABLE': ( +            'Network is unreachable (code=101)',), +        'PROCESS_RESTART_TLS': ( +            "SIGUSR1[soft,tls-error]",), +        'PROCESS_RESTART_PING': ( +            "SIGUSR1[soft,ping-restart]",), +        'INITIALIZATION_COMPLETED': ( +            "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 watch(self, line): +        """ +        Inspects line searching for the different patterns. If a match +        is found, try to emit the corresponding signal. + +        :param line: a line of openvpn output +        :type line: str +        """ +        chained_iter = chain(*[ +            zip(repeat(key, len(l)), l) +            for key, l in self._events.iteritems()]) +        for event, pattern in chained_iter: +            if pattern in line: +                logger.debug('pattern matched! %s' % pattern) +                break +        else: +            return + +        sig = self._get_signal(event) +        if sig: +            sig.emit() +            return +        else: +            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 +        :type event: str +        :returns: a QtSignal, or None +        :rtype: QtSignal or None +        """ +        return getattr(self._qtsigs, event.lower(), None) + +  class OpenVPNAlreadyRunning(Exception):      message = ("Another openvpn instance is already running, and could "                 "not be stopped.") @@ -160,10 +241,14 @@ class VPN(object):              tries += 1              reactor.callLater(self.TERMINATE_WAIT,                                self._kill_if_left_alive, tries) +            return          # after running out of patience, we try a killProcess          logger.debug("Process did not died. Sending a SIGKILL.") -        self.killit() +        try: +            self.killit() +        except OSError: +            logger.error("Could not kill process!")      def killit(self):          """ @@ -654,6 +739,7 @@ class VPNManager(object):              raise OpenVPNAlreadyRunning +  class VPNProcess(protocol.ProcessProtocol, VPNManager):      """      A ProcessProtocol class that can be used to spawn a process that will @@ -703,8 +789,12 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):          self._last_status = None          self._alive = False +        # XXX use flags, maybe, instead of passing +        # the parameter around.          self._openvpn_verb = openvpn_verb +        self._vpn_observer = VPNObserver(qtsigs) +      # processProtocol methods      def connectionMade(self): @@ -726,8 +816,9 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):          .. seeAlso: `http://twistedmatrix.com/documents/13.0.0/api/twisted.internet.protocol.ProcessProtocol.html` # noqa          """          # truncate the newline -        # should send this to the logging window -        vpnlog.info(data[:-1]) +        line = data[:-1] +        vpnlog.info(line) +        self._vpn_observer.watch(line)      def processExited(self, reason):          """ diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 7968dd6a..409389be 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -264,6 +264,10 @@ class SoledadBootstrapper(AbstractBootstrapper):              logger.error("Error while initializing soledad "                           "(unauthorized).")              self.soledad_failed.emit() +        except u1db_errors.HTTPError as exc: +            logger.exception("Error whie initializing soledad " +                             "(HTTPError)") +            self.soledad_failed.emit()          except Exception as exc:              logger.exception("Unhandled error while initializating "                               "soledad: %r" % (exc,)) diff --git a/src/leap/bitmask/util/leap_log_handler.py b/src/leap/bitmask/util/leap_log_handler.py index 98924c12..262f2f65 100644 --- a/src/leap/bitmask/util/leap_log_handler.py +++ b/src/leap/bitmask/util/leap_log_handler.py @@ -52,7 +52,7 @@ class LogHandler(logging.Handler):          :param logging_level: the debug level to define the color.          :type logging_level: str.          """ -        log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' +        log_format = '%(asctime)s - %(name)s:%(funcName)s:L#%(lineno)s - %(levelname)s - %(message)s'          formatter = logging.Formatter(log_format)          return formatter | 
