diff options
| -rw-r--r-- | changes/bug-3401_login-fail-left-ui-inconsistent | 1 | ||||
| -rw-r--r-- | changes/feature_3305_openvpn_verbosity | 1 | ||||
| -rw-r--r-- | changes/feature_add-imap-service | 1 | ||||
| -rw-r--r-- | changes/feature_add-uic-support-in-virtualenv | 1 | ||||
| -rwxr-xr-x | pkg/postmkvenv.sh | 2 | ||||
| -rw-r--r-- | src/leap/app.py | 8 | ||||
| -rw-r--r-- | src/leap/gui/login.py | 6 | ||||
| -rw-r--r-- | src/leap/gui/mainwindow.py | 157 | ||||
| -rw-r--r-- | src/leap/services/eip/vpnlaunchers.py | 26 | ||||
| -rw-r--r-- | src/leap/services/eip/vpnprocess.py | 27 | ||||
| -rw-r--r-- | src/leap/services/mail/imap.py | 42 | ||||
| -rw-r--r-- | src/leap/services/soledad/soledadbootstrapper.py | 22 | 
12 files changed, 209 insertions, 85 deletions
diff --git a/changes/bug-3401_login-fail-left-ui-inconsistent b/changes/bug-3401_login-fail-left-ui-inconsistent new file mode 100644 index 00000000..2403fe0e --- /dev/null +++ b/changes/bug-3401_login-fail-left-ui-inconsistent @@ -0,0 +1 @@ +  o Properly handle login failures. Closes bug #3401. diff --git a/changes/feature_3305_openvpn_verbosity b/changes/feature_3305_openvpn_verbosity new file mode 100644 index 00000000..d838861f --- /dev/null +++ b/changes/feature_3305_openvpn_verbosity @@ -0,0 +1 @@ +  o Accept flag for changing openvpn verbosity in logs. Closes: #3305 diff --git a/changes/feature_add-imap-service b/changes/feature_add-imap-service new file mode 100644 index 00000000..6721e2cd --- /dev/null +++ b/changes/feature_add-imap-service @@ -0,0 +1 @@ +  o Add imap service to the client. Closes: #2579 diff --git a/changes/feature_add-uic-support-in-virtualenv b/changes/feature_add-uic-support-in-virtualenv new file mode 100644 index 00000000..2c067f80 --- /dev/null +++ b/changes/feature_add-uic-support-in-virtualenv @@ -0,0 +1 @@ +  o Add pyside-uic support inside the virtualenv. This way it won't fail to 'make' if the virtualenv is activated. Closes #3411. diff --git a/pkg/postmkvenv.sh b/pkg/postmkvenv.sh index 2f0cba45..04f8d372 100755 --- a/pkg/postmkvenv.sh +++ b/pkg/postmkvenv.sh @@ -16,7 +16,7 @@ elif [[ "$unamestr" == 'Darwin' ]]; then     platform='darwin'  fi -LIBS=( PySide ) +LIBS=( PySide pysideuic )  PYTHON_VERSION=python$(python -c "import sys; print (str(sys.version_info[0])+'.'+str(sys.version_info[1]))")  VAR=( $(which -a $PYTHON_VERSION) ) diff --git a/src/leap/app.py b/src/leap/app.py index 6ba27813..e7a8aa42 100644 --- a/src/leap/app.py +++ b/src/leap/app.py @@ -142,6 +142,7 @@ def main():      bypass_checks = getattr(opts, 'danger', False)      debug = opts.debug      logfile = opts.log_file +    openvpn_verb = opts.openvpn_verb      logger = add_logger_handlers(debug, logfile)      replace_stdout_stderr_with_logging(logger) @@ -202,6 +203,7 @@ def main():      window = MainWindow(          lambda: twisted_main.quit(app),          standalone=standalone, +        openvpn_verb=openvpn_verb,          bypass_checks=bypass_checks)      sigint_window = partial(sigint_handler, window, logger=logger) @@ -210,8 +212,10 @@ def main():      if IS_MAC:          window.raise_() -    tx_app = leap_services() -    assert(tx_app) +    # This was a good idea, but for this to work as intended we +    # should centralize the start of all services in there. +    #tx_app = leap_services() +    #assert(tx_app)      # Run main loop      twisted_main.start(app) diff --git a/src/leap/gui/login.py b/src/leap/gui/login.py index 3c994597..de0b2d50 100644 --- a/src/leap/gui/login.py +++ b/src/leap/gui/login.py @@ -197,18 +197,18 @@ class LoginWidget(QtGui.QWidget):          """          self.ui.lnUser.setEnabled(enabled)          self.ui.lnPassword.setEnabled(enabled) -        self.ui.btnLogin.setEnabled(enabled)          self.ui.chkRemember.setEnabled(enabled)          self.ui.cmbProviders.setEnabled(enabled) -    def set_cancel(self, enabled=False): +        self._set_cancel(not enabled) + +    def _set_cancel(self, enabled=False):          """          Enables or disables the cancel action in the "log in" process.          :param enabled: wether it should be enabled or not          :type enabled: bool          """ -        self.ui.btnLogin.setEnabled(enabled)          text = self.tr("Cancel")          login_or_cancel = self.cancel_login diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index a1ba4df4..5c7a3928 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -45,6 +45,7 @@ from leap.services.eip.providerbootstrapper import ProviderBootstrapper  # XXX: Soledad might not work out of the box in Windows, issue #2932  from leap.services.soledad.soledadbootstrapper import SoledadBootstrapper  from leap.services.mail.smtpbootstrapper import SMTPBootstrapper +from leap.services.mail import imap  from leap.platform_init import IS_WIN, IS_MAC  from leap.platform_init.initializers import init_platform @@ -94,12 +95,15 @@ class MainWindow(QtGui.QMainWindow):      # Signals      new_updates = QtCore.Signal(object)      raise_window = QtCore.Signal([]) +    soledad_ready = QtCore.Signal([])      # We use this flag to detect abnormal terminations      user_stopped_eip = False      def __init__(self, quit_callback, -                 standalone=False, bypass_checks=False): +                 standalone=False, +                 openvpn_verb=1, +                 bypass_checks=False):          """          Constructor for the client main window @@ -140,6 +144,9 @@ class MainWindow(QtGui.QMainWindow):              self.ui.stackedWidget.widget(self.LOGIN_INDEX))          self.ui.loginLayout.addWidget(self._login_widget) +        # Signals +        # TODO separate logic from ui signals. +          self._login_widget.login.connect(self._login)          self._login_widget.cancel_login.connect(self._cancel_login)          self._login_widget.show_wizard.connect( @@ -210,7 +217,7 @@ class MainWindow(QtGui.QMainWindow):          self._smtp_bootstrapper.download_config.connect(              self._smtp_bootstrapped_stage) -        self._vpn = VPN() +        self._vpn = VPN(openvpn_verb=openvpn_verb)          self._vpn.qtsigs.state_changed.connect(              self._status_panel.update_vpn_state)          self._vpn.qtsigs.status_changed.connect( @@ -260,7 +267,9 @@ class MainWindow(QtGui.QMainWindow):          self.ui.lblNewUpdates.setVisible(False)          self.ui.btnMore.setVisible(False)          self.ui.btnMore.clicked.connect(self._updates_details) +          self.new_updates.connect(self._react_to_new_updates) +        self.soledad_ready.connect(self._start_imap_service)          init_platform() @@ -273,6 +282,7 @@ class MainWindow(QtGui.QMainWindow):          self._soledad = None          self._keymanager = None +        self._imap_service = None          self._login_defer = None          self._download_provider_defer = None @@ -804,7 +814,6 @@ class MainWindow(QtGui.QMainWindow):          self._login_widget.set_status(self.tr("Logging in..."), error=False)          self._login_widget.set_enabled(False) -        self._login_widget.set_cancel(True)          if self._login_widget.get_remember() and has_keyring():              # in the keyring and in the settings @@ -832,7 +841,6 @@ class MainWindow(QtGui.QMainWindow):          Stops the login sequence.          """          logger.debug("Cancelling log in.") -        self._login_widget.set_cancel(False)          if self._download_provider_defer:              logger.debug("Cancelling download provider defer.") @@ -951,29 +959,41 @@ class MainWindow(QtGui.QMainWindow):          passed = data[self._soledad_bootstrapper.PASSED_KEY]          if not passed:              logger.error(data[self._soledad_bootstrapper.ERROR_KEY]) -        else: -            logger.debug("Done bootstrapping Soledad") +            return -            self._soledad = self._soledad_bootstrapper.soledad -            self._keymanager = self._soledad_bootstrapper.keymanager +        logger.debug("Done bootstrapping Soledad") -            if self._provider_config.provides_mx() and \ -                    self._enabled_services.count(self.MX_SERVICE) > 0: -                self._smtp_bootstrapper.run_smtp_setup_checks( -                    self._provider_config, -                    self._smtp_config, -                    True) +        self._soledad = self._soledad_bootstrapper.soledad +        self._keymanager = self._soledad_bootstrapper.keymanager + +        # Ok, now soledad is ready, so we can allow other things that +        # depend on soledad to start. + +        # this will trigger start_imap_service +        self.soledad_ready.emit() + +        # TODO connect all these activations to the soledad_ready +        # signal so the logic is clearer to follow. + +        if self._provider_config.provides_mx() and \ +                self._enabled_services.count(self.MX_SERVICE) > 0: +            self._smtp_bootstrapper.run_smtp_setup_checks( +                self._provider_config, +                self._smtp_config, +                True) +        else: +            if self._enabled_services.count(self.MX_SERVICE) > 0: +                pass  # TODO: show MX status +                #self._status_panel.set_eip_status( +                #    self.tr("%s does not support MX") % +                #    (self._provider_config.get_domain(),), +                #                     error=True)              else: -                if self._enabled_services.count(self.MX_SERVICE) > 0: -                    pass  # TODO: show MX status -                    #self._status_panel.set_eip_status( -                    #    self.tr("%s does not support MX") % -                    #    (self._provider_config.get_domain(),), -                    #                     error=True) -                else: -                    pass  # TODO: show MX status -                    #self._status_panel.set_eip_status( -                    #    self.tr("MX is disabled")) +                pass  # TODO: show MX status +                #self._status_panel.set_eip_status( +                #    self.tr("MX is disabled")) + +    # Service control methods: smtp      def _smtp_bootstrapped_stage(self, data):          """ @@ -991,29 +1011,41 @@ class MainWindow(QtGui.QMainWindow):          passed = data[self._smtp_bootstrapper.PASSED_KEY]          if not passed:              logger.error(data[self._smtp_bootstrapper.ERROR_KEY]) -        else: -            logger.debug("Done bootstrapping SMTP") - -            hosts = self._smtp_config.get_hosts() -            # TODO: handle more than one host and define how to choose -            if len(hosts) > 0: -                hostname = hosts.keys()[0] -                logger.debug("Using hostname %s for SMTP" % (hostname,)) -                host = hosts[hostname][self.IP_KEY].encode("utf-8") -                port = hosts[hostname][self.PORT_KEY] -                # TODO: pick local smtp port in a better way -                # TODO: Make the encrypted_only configurable - -                from leap.mail.smtp import setup_smtp_relay -                client_cert = self._eip_config.get_client_cert_path( -                    self._provider_config) -                setup_smtp_relay(port=1234, -                                 keymanager=self._keymanager, -                                 smtp_host=host, -                                 smtp_port=port, -                                 smtp_cert=client_cert, -                                 smtp_key=client_cert, -                                 encrypted_only=False) +            return +        logger.debug("Done bootstrapping SMTP") + +        hosts = self._smtp_config.get_hosts() +        # TODO: handle more than one host and define how to choose +        if len(hosts) > 0: +            hostname = hosts.keys()[0] +            logger.debug("Using hostname %s for SMTP" % (hostname,)) +            host = hosts[hostname][self.IP_KEY].encode("utf-8") +            port = hosts[hostname][self.PORT_KEY] +            # TODO: pick local smtp port in a better way +            # TODO: Make the encrypted_only configurable + +            from leap.mail.smtp import setup_smtp_relay +            client_cert = self._eip_config.get_client_cert_path( +                self._provider_config) +            setup_smtp_relay(port=1234, +                             keymanager=self._keymanager, +                             smtp_host=host, +                             smtp_port=port, +                             smtp_cert=client_cert, +                             smtp_key=client_cert, +                             encrypted_only=False) + +    def _start_imap_service(self): +        """ +        SLOT +        TRIGGERS: +            soledad_ready +        """ +        logger.debug('Starting imap service') + +        self._imap_service = imap.start_imap_service( +            self._soledad, +            self._keymanager)      def _get_socket_host(self):          """ @@ -1068,6 +1100,7 @@ class MainWindow(QtGui.QMainWindow):              self._status_panel.eip_started() +            # XXX refactor into status_panel method?              self._action_eip_startstop.setText(self.tr("Turn OFF"))              self._action_eip_startstop.disconnect(self)              self._action_eip_startstop.triggered.connect( @@ -1301,7 +1334,6 @@ class MainWindow(QtGui.QMainWindow):          """          passed = data[self._provider_bootstrapper.PASSED_KEY]          if not passed: -            self._login_widget.set_cancel(False)              self._login_widget.set_enabled(True)              self._login_widget.set_status(                  self.tr("Unable to connect: Problem with provider")) @@ -1409,6 +1441,9 @@ class MainWindow(QtGui.QMainWindow):          """          logger.debug('About to quit, doing cleanup...') +        if self._imap_service is not None: +            self._imap_service.stop() +          if self._srp_auth is not None:              if self._srp_auth.get_session_id() is not None or \                 self._srp_auth.get_token() is not None: @@ -1421,16 +1456,28 @@ class MainWindow(QtGui.QMainWindow):          else:              logger.error("No instance of soledad was found.") -        logger.debug('Cleaning pidfiles') -        self._cleanup_pidfiles() -          logger.debug('Terminating vpn')          self._vpn.terminate(shutdown=True) +        if self._login_defer: +            logger.debug("Cancelling login defer.") +            self._login_defer.cancel() + +        if self._download_provider_defer: +            logger.debug("Cancelling download provider defer.") +            self._download_provider_defer.cancel() + +        # TODO missing any more cancels? + +        logger.debug('Cleaning pidfiles') +        self._cleanup_pidfiles() +      def quit(self):          """          Cleanup and tidely close the main window before quitting.          """ +        # TODO: separate the shutting down of services from the +        # UI stuff.          self._cleanup_and_quit()          self._really_quit = True @@ -1441,14 +1488,6 @@ class MainWindow(QtGui.QMainWindow):          if self._logger_window:              self._logger_window.close() -        if self._login_defer: -            logger.debug("Cancelling login defer.") -            self._login_defer.cancel() - -        if self._download_provider_defer: -            logger.debug("Cancelling download provider defer.") -            self._download_provider_defer.cancel() -          self.close()          if self._quit_callback: diff --git a/src/leap/services/eip/vpnlaunchers.py b/src/leap/services/eip/vpnlaunchers.py index 526f1ba4..dadbf859 100644 --- a/src/leap/services/eip/vpnlaunchers.py +++ b/src/leap/services/eip/vpnlaunchers.py @@ -352,7 +352,7 @@ class LinuxVPNLauncher(VPNLauncher):          return None      def get_vpn_command(self, eipconfig=None, providerconfig=None, -                        socket_host=None, socket_port="unix"): +                        socket_host=None, socket_port="unix", openvpn_verb=1):          """          Returns the platform dependant vpn launching command. It will          look for openvpn in the regular paths and algo in @@ -375,6 +375,9 @@ class LinuxVPNLauncher(VPNLauncher):                              socket, or port otherwise          :type socket_port: str +        :param openvpn_verb: openvpn verbosity wanted +        :type openvpn_verb: int +          :return: A VPN command ready to be launched          :rtype: list          """ @@ -404,7 +407,7 @@ class LinuxVPNLauncher(VPNLauncher):              args.append(openvpn)              openvpn = first(pkexec) -        # TODO: handle verbosity +        args += ['--verb', '%d' % (openvpn_verb,)]          gateway_selector = VPNGatewaySelector(eipconfig)          gateways = gateway_selector.get_gateways() @@ -604,7 +607,7 @@ class DarwinVPNLauncher(VPNLauncher):          return self.COCOASUDO, args      def get_vpn_command(self, eipconfig=None, providerconfig=None, -                        socket_host=None, socket_port="unix"): +                        socket_host=None, socket_port="unix", openvpn_verb=1):          """          Returns the platform dependant vpn launching command @@ -623,6 +626,9 @@ class DarwinVPNLauncher(VPNLauncher):                              socket, or port otherwise          :type socket_port: str +        :param openvpn_verb: openvpn verbosity wanted +        :type openvpn_verb: int +          :return: A VPN command ready to be launched          :rtype: list          """ @@ -651,7 +657,7 @@ class DarwinVPNLauncher(VPNLauncher):          openvpn = first(openvpn_possibilities)          args = [openvpn] -        # TODO: handle verbosity +        args += ['--verb', '%d' % (openvpn_verb,)]          gateway_selector = VPNGatewaySelector(eipconfig)          gateways = gateway_selector.get_gateways() @@ -768,9 +774,10 @@ class WindowsVPNLauncher(VPNLauncher):      OPENVPN_BIN = 'openvpn_leap.exe'      # XXX UPDOWN_FILES ... we do not have updown files defined yet! +    # (and maybe we won't)      def get_vpn_command(self, eipconfig=None, providerconfig=None, -                        socket_host=None, socket_port="9876"): +                        socket_host=None, socket_port="9876", openvpn_verb=1):          """          Returns the platform dependant vpn launching command. It will          look for openvpn in the regular paths and algo in @@ -780,14 +787,20 @@ class WindowsVPNLauncher(VPNLauncher):          :param eipconfig: eip configuration object          :type eipconfig: EIPConfig +          :param providerconfig: provider specific configuration          :type providerconfig: ProviderConfig +          :param socket_host: either socket path (unix) or socket IP          :type socket_host: str +          :param socket_port: either string "unix" if it's a unix          socket, or port otherwise          :type socket_port: str +        :param openvpn_verb: the openvpn verbosity wanted +        :type openvpn_verb: int +          :return: A VPN command ready to be launched          :rtype: list          """ @@ -810,8 +823,7 @@ class WindowsVPNLauncher(VPNLauncher):          openvpn = first(openvpn_possibilities)          args = [] - -        # TODO: handle verbosity +        args += ['--verb', '%d' % (openvpn_verb,)]          gateway_selector = VPNGatewaySelector(eipconfig)          gateways = gateway_selector.get_gateways() diff --git a/src/leap/services/eip/vpnprocess.py b/src/leap/services/eip/vpnprocess.py index c4bdb30c..5b07a3cf 100644 --- a/src/leap/services/eip/vpnprocess.py +++ b/src/leap/services/eip/vpnprocess.py @@ -80,7 +80,9 @@ class VPN(object):      TERMINATE_MAXTRIES = 10      TERMINATE_WAIT = 1  # secs -    def __init__(self): +    OPENVPN_VERB = "openvpn_verb" + +    def __init__(self, **kwargs):          """          Instantiate empty attributes and get a copy          of a QObject containing the QSignals that we will pass along @@ -92,6 +94,8 @@ class VPN(object):          self._reactor = reactor          self._qtsigs = VPNSignals() +        self._openvpn_verb = kwargs.get(self.OPENVPN_VERB, None) +      @property      def qtsigs(self):          return self._qtsigs @@ -108,9 +112,12 @@ class VPN(object):          """          self._stop_pollers()          kwargs['qtsigs'] = self.qtsigs +        kwargs['openvpn_verb'] = self._openvpn_verb          # 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.") @@ -566,7 +573,12 @@ class VPNManager(object):                  # we should check that cmdline BEGINS                  # with openvpn or with our wrapper                  # (pkexec / osascript / whatever) -                if "openvpn" in ' '.join(p.cmdline): + +                # This needs more work, see #3268, but for the moment +                # we need to be able to filter out arguments in the form +                # --openvpn-foo, since otherwise we are shooting ourselves +                # in the feet. +                if any(map(lambda s: s.startswith("openvpn"), p.cmdline)):                      openvpn_process = p                      break              except psutil.error.AccessDenied: @@ -645,7 +657,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):      """      def __init__(self, eipconfig, providerconfig, socket_host, socket_port, -                 qtsigs): +                 qtsigs, openvpn_verb):          """          :param eipconfig: eip configuration object          :type eipconfig: EIPConfig @@ -663,6 +675,10 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):          :param qtsigs: a QObject containing the Qt signals used to notify the                         UI.          :type qtsigs: QObject + +        :param openvpn_verb: the desired level of verbosity in the +                             openvpn invocation +        :type openvpn_verb: int          """          VPNManager.__init__(self, qtsigs=qtsigs)          leap_assert_type(eipconfig, EIPConfig) @@ -682,6 +698,8 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):          self._last_status = None          self._alive = False +        self._openvpn_verb = openvpn_verb +      # processProtocol methods      def connectionMade(self): @@ -757,7 +775,8 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):              eipconfig=self._eipconfig,              providerconfig=self._providerconfig,              socket_host=self._socket_host, -            socket_port=self._socket_port) +            socket_port=self._socket_port, +            openvpn_verb=self._openvpn_verb)          return map(str, cmd)      # shutdown diff --git a/src/leap/services/mail/imap.py b/src/leap/services/mail/imap.py new file mode 100644 index 00000000..4dceb2ad --- /dev/null +++ b/src/leap/services/mail/imap.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# imap.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +""" +Initialization of imap service +""" +import logging +import sys + +from leap.mail.imap.service import imap +from twisted.python import log + +logger = logging.getLogger(__name__) + + +def start_imap_service(*args, **kwargs): +    """ +    Initializes and run imap service. + +    :returns: twisted.internet.task.LoopingCall instance +    """ +    logger.debug('Launching imap service') + +    # Uncomment the next two lines to get a separate debugging log +    # TODO handle this by a separate flag. +    #log.startLogging(open('/tmp/leap-imap.log', 'w')) +    #log.startLogging(sys.stdout) + +    return imap.run_service(*args, **kwargs) diff --git a/src/leap/services/soledad/soledadbootstrapper.py b/src/leap/services/soledad/soledadbootstrapper.py index 422352ea..7ac4203b 100644 --- a/src/leap/services/soledad/soledadbootstrapper.py +++ b/src/leap/services/soledad/soledadbootstrapper.py @@ -23,6 +23,7 @@ import logging  import os  from PySide import QtCore +from u1db import errors as u1db_errors  from leap.common.check import leap_assert, leap_assert_type  from leap.common.files import get_mtime @@ -103,15 +104,18 @@ class SoledadBootstrapper(AbstractBootstrapper):              # TODO: If selected server fails, retry with another host              # (issue #3309) -            self._soledad = Soledad(uuid, -                                    self._password.encode("utf-8"), -                                    secrets_path=secrets_path, -                                    local_db_path=local_db_path, -                                    server_url=server_url, -                                    cert_file=cert_file, -                                    auth_token=srp_auth.get_token()) - -            self._soledad.sync() +            try: +                self._soledad = Soledad( +                    uuid, +                    self._password.encode("utf-8"), +                    secrets_path=secrets_path, +                    local_db_path=local_db_path, +                    server_url=server_url, +                    cert_file=cert_file, +                    auth_token=srp_auth.get_token()) +                self._soledad.sync() +            except u1db_errors.Unauthorized: +                logger.error("Error while initializing soledad.")          else:              raise Exception("No soledad server found")  | 
