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") |