From 5c37f0a24a5304b8b986385c023875e6a2bf6b95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 19 May 2014 11:48:40 -0300 Subject: Add OPENVPN_BIN_PATH for OSX --- changes/bug_add_openvpn_bin_path_osx | 1 + src/leap/bitmask/services/eip/darwinvpnlauncher.py | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 changes/bug_add_openvpn_bin_path_osx diff --git a/changes/bug_add_openvpn_bin_path_osx b/changes/bug_add_openvpn_bin_path_osx new file mode 100644 index 00000000..367b5b8e --- /dev/null +++ b/changes/bug_add_openvpn_bin_path_osx @@ -0,0 +1 @@ +- Add OPENVPN_BIN_PATH for OSX so that EIP starts properly. \ No newline at end of file diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py index a03bfc44..41d75052 100644 --- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py +++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py @@ -52,6 +52,8 @@ class DarwinVPNLauncher(VPNLauncher): OPENVPN_PATH = "%s/Contents/Resources/openvpn" % (INSTALL_PATH,) OPENVPN_PATH_ESCAPED = "%s/Contents/Resources/openvpn" % ( INSTALL_PATH_ESCAPED,) + OPENVPN_BIN_PATH = "%s/Contents/Resources/%s" % (INSTALL_PATH, + OPENVPN_BIN) UP_SCRIPT = "%s/client.up.sh" % (OPENVPN_PATH,) DOWN_SCRIPT = "%s/client.down.sh" % (OPENVPN_PATH,) -- cgit v1.2.3 From 8f442c2aa6fcdef882d2ff0684f896a3807dee01 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 19 May 2014 16:23:13 -0500 Subject: update manpage --- docs/man/bitmask.1.rst | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/docs/man/bitmask.1.rst b/docs/man/bitmask.1.rst index ed4f7133..28fc945a 100644 --- a/docs/man/bitmask.1.rst +++ b/docs/man/bitmask.1.rst @@ -85,21 +85,16 @@ At the current time, Bitmask is not compatible with ``openresolv``, but it works FILES ===== -/etc/leap/resolv-update ------------------------ -Post up/down script passed to openvpn. It writes /etc/resolv.conf to avoid dns leaks, and restores the original resolv.conf on exit. -/etc/leap/resolv-head ---------------------- -/etc/leap/resolv-tail ---------------------- +/usr/share/polkit-1/actions/se.leap.bitmask.policy +------------------------------------------------------- -Custom entries that will appear in the written resolv.conf +PolicyKit policy file, used for granting access to bitmask-root without the need of entering a password each time. -/usr/share/polkit-1/actions/net.openvpn.gui.leap.policy -------------------------------------------------------- +/usr/sbin/bitmask-root +------------------------ -PolicyKit policy file, used for granting access to openvpn without the need of entering a password each time. +Helper to launch and stop openvpn and the bitmask firewall. ~/.config/leap/ --------------- -- cgit v1.2.3 From 1ef5b43cead587b3962b4d633c5faa658471a912 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 19 May 2014 16:35:19 -0500 Subject: manpage source for bitmask-root --- docs/man/bitmask-root.1.rst | 54 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 docs/man/bitmask-root.1.rst diff --git a/docs/man/bitmask-root.1.rst b/docs/man/bitmask-root.1.rst new file mode 100644 index 00000000..7ed53aa9 --- /dev/null +++ b/docs/man/bitmask-root.1.rst @@ -0,0 +1,54 @@ +============ +bitmask-root +============ + +------------------------------------------------------------------------ +privileged helper for bitmask, the encrypted internet access toolkit. +------------------------------------------------------------------------ + +:Author: LEAP Encryption Access Project https://leap.se +:Date: 2014-05-19 +:Copyright: GPLv3+ +:Version: 0.5.1 +:Manual section: 1 +:Manual group: General Commands Manual + +SYNOPSIS +======== + +bitmask-root [openvpn | firewall | isup ] [start | stop] [ARGS] + +DESCRIPTION +=========== + +*bitmask-root* is a privileged helper for bitmask. + +It is used to start or stop openvpn and the bitmask firewall. + + +OPTIONS +======= + +openvpn +-------- + +**start** [ARGS] Starts openvpn. All args are passed to openvpn, and + filtered against a list of allowed args. + +**stop** Stops openvpn. + + +firewall +--------- + +**start** [GATEWAYS] Starts the firewall. GATEWAYS is a list of EIP + gateways to allow in the firewall. + +**stop** Stops the firewall. + + + +BUGS +==== + +Please report any bugs to https://leap.se/code -- cgit v1.2.3 From e373a9d071c2c45952e8f7ef68a3471b5e2aa9f5 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 19 May 2014 16:37:56 -0500 Subject: update bitmask manpage date and version --- docs/man/bitmask.1.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/man/bitmask.1.rst b/docs/man/bitmask.1.rst index 28fc945a..2711d6a0 100644 --- a/docs/man/bitmask.1.rst +++ b/docs/man/bitmask.1.rst @@ -7,9 +7,9 @@ graphical client to control LEAP, the encrypted internet access toolkit. ------------------------------------------------------------------------ :Author: LEAP Encryption Access Project https://leap.se -:Date: 2013-08-23 +:Date: 2014-05-19 :Copyright: GPLv3+ -:Version: 0.3.1 +:Version: 0.5.1 :Manual section: 1 :Manual group: General Commands Manual -- cgit v1.2.3 From 7b9338214dce27676e7e2008bd23e678688aa658 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 20 May 2014 17:55:30 -0500 Subject: remove reference to openresolv conflict from manpage --- docs/man/bitmask.1.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/man/bitmask.1.rst b/docs/man/bitmask.1.rst index 2711d6a0..38da64af 100644 --- a/docs/man/bitmask.1.rst +++ b/docs/man/bitmask.1.rst @@ -80,7 +80,6 @@ WARNING This software is still in its early phases of testing. So don't trust your life to it! -At the current time, Bitmask is not compatible with ``openresolv``, but it works with ``resolvconf``. FILES ===== -- cgit v1.2.3 From f43920745aab9ccfe49696b62bc0dd78c030854b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 19 May 2014 16:24:47 -0500 Subject: modify setup.py to install the new root helper --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index de31be4b..3d12db64 100755 --- a/setup.py +++ b/setup.py @@ -203,9 +203,9 @@ if IS_LINUX: # globally. Or make specific install command. See #3805 data_files = [ ("share/polkit-1/actions", - ["pkg/linux/polkit/net.openvpn.gui.leap.policy"]), - ("etc/leap/", - ["pkg/linux/resolv-update"]), + ["pkg/linux/polkit/se.leap.bitmask.policy"]), + ("/usr/sbin", + ["pkg/linux/bitmask-root"]), ] -- cgit v1.2.3 From 62555000b628f3389370135a6f58a3deeb63ae22 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 19 May 2014 23:58:06 -0500 Subject: backward compat for psutil p.cmdline. Closes: #5689 --- src/leap/bitmask/services/eip/vpnprocess.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 1559ea8b..734b88df 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -30,9 +30,11 @@ import psutil try: # psutil < 2.0.0 from psutil.error import AccessDenied as psutil_AccessDenied + PSUTIL_2 = False except ImportError: # psutil >= 2.0.0 from psutil import AccessDenied as psutil_AccessDenied + PSUTIL_2 = True from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig @@ -676,7 +678,13 @@ class VPNManager(object): # 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.find("LEAPOPENVPN") != -1, p.cmdline)): + + if PSUTIL_2: + cmdline = p.cmdline() + else: + cmdline = p.cmdline + if any(map(lambda s: s.find( + "LEAPOPENVPN") != -1, cmdline)): openvpn_process = p break except psutil_AccessDenied: -- cgit v1.2.3 From 8fbcc9219f58f17a60c58089dcdfd286d5646133 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 20 May 2014 10:59:44 -0500 Subject: remove psutil <2.0 pinning --- pkg/requirements.pip | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pkg/requirements.pip b/pkg/requirements.pip index 70427e63..3d6b33a3 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -11,10 +11,7 @@ srp>=1.0.2 pyopenssl python-dateutil -# since gnupg requires exactly 1.2.1, this chokes if we -# don't specify a version. Selecting something lesser than -# 2.0 is equivalent to pick 1.2.1. See #5489 -psutil<2.0 +psutil ipaddr twisted -- cgit v1.2.3 From a68960c1dcb7670687cb08610c9132cdf9b5a595 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 20 May 2014 18:54:57 -0500 Subject: changes file --- changes/feature-5689-psutil-compat | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/feature-5689-psutil-compat diff --git a/changes/feature-5689-psutil-compat b/changes/feature-5689-psutil-compat new file mode 100644 index 00000000..be11aea3 --- /dev/null +++ b/changes/feature-5689-psutil-compat @@ -0,0 +1 @@ +- Make use of cmdline in psutil backwards-compatible. Closes: #5689 -- cgit v1.2.3 From f952bc10e1e10c51e07295250168ed56d7e39e06 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 20 May 2014 18:03:03 -0500 Subject: fix nameserver restoring. Closes: #5692 --- pkg/linux/bitmask-root | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 136fd6a4..6badeedd 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -566,7 +566,7 @@ class NameserverRestorer(Daemon): A daemon that will restore the previous nameservers. """ - def run(self): + def run(self, *args): """ Run when daemonized. """ -- cgit v1.2.3 From 73801e0db2601301f06d388fa7e258eccf093373 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 20 May 2014 18:58:35 -0500 Subject: changes file --- changes/feature_5692-fix-nameserver-restoring | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/feature_5692-fix-nameserver-restoring diff --git a/changes/feature_5692-fix-nameserver-restoring b/changes/feature_5692-fix-nameserver-restoring new file mode 100644 index 00000000..10ce3427 --- /dev/null +++ b/changes/feature_5692-fix-nameserver-restoring @@ -0,0 +1 @@ +- Fix nameserver restoring. Closes: #5692 -- cgit v1.2.3 From 9ff5e5e8db715d84b5ae369cc59aa991cea3893c Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 21 May 2014 08:29:51 -0500 Subject: block ipv6 traffic --- pkg/linux/bitmask-root | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 6badeedd..6d296ecf 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -765,6 +765,17 @@ def firewall_start(args): "--dport", "53", "--destination", allowed_dns, "--jump", "ACCEPT") + # workaround for ipv6 servers being blocked and not falling back to ipv4. + # See #5693 + ip6tables("--append", "OUTPUT", "--jump", "REJECT", + "-s", "::/0", "-d", "::/0", + "-p", "tcp", + "--reject-with", "icmp6-port-unreachable") + ip6tables("--append", "OUTPUT", "--jump", "REJECT", + "-s", "::/0", "-d", "::/0", + "-p", "udp", + "--reject-with", "icmp6-port-unreachable") + def firewall_stop(): """ -- cgit v1.2.3 From 7360aa6241607825117b81b32cbd122bdce67beb Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 21 May 2014 08:30:02 -0500 Subject: changes file --- changes/bug-block-ipv6-clientside | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/bug-block-ipv6-clientside diff --git a/changes/bug-block-ipv6-clientside b/changes/bug-block-ipv6-clientside new file mode 100644 index 00000000..9e6b88c4 --- /dev/null +++ b/changes/bug-block-ipv6-clientside @@ -0,0 +1 @@ +- Block ipv6 traffic for the moment. Closes: #5693 -- cgit v1.2.3 From e9df1f8bb233bebee602fa11d6702eb97d8a7d7c Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 29 Apr 2014 13:48:01 -0300 Subject: Use ProviderConfig helper to get object from domain. --- src/leap/bitmask/backend.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index a2df465d..65f12685 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -197,11 +197,15 @@ class Provider(object): """ d = None + # TODO: use this commented code when we don't need the provider config + # in the maiwindow. + # config = ProviderConfig.get_provider_config(provider) + # self._provider_config = config + # if config is not None: config = self._provider_config if get_provider_config(config, provider): d = self._provider_bootstrapper.run_provider_setup_checks( - self._provider_config, - download_if_needed=True) + config, download_if_needed=True) else: if self._signaler is not None: self._signaler.signal( @@ -246,8 +250,9 @@ class Register(object): :returns: the defer for the operation running in a thread. :rtype: twisted.internet.defer.Deferred """ - config = ProviderConfig() - if get_provider_config(config, domain): + config = ProviderConfig.get_provider_config(domain) + self._provider_config = config + if config is not None: srpregister = SRPRegister(signaler=self._signaler, provider_config=config) return threads.deferToThread( @@ -294,8 +299,9 @@ class EIP(object): :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): + config = ProviderConfig.get_provider_config(domain) + self._provider_config = config + if config is not None: if skip_network: return defer.Deferred() eb = self._eip_bootstrapper @@ -572,8 +578,8 @@ class Authenticate(object): :returns: the defer for the operation running in a thread. :rtype: twisted.internet.defer.Deferred """ - config = ProviderConfig() - if get_provider_config(config, domain): + config = ProviderConfig.get_provider_config(domain) + if config is not None: self._srp_auth = SRPAuth(config, self._signaler) self._login_defer = self._srp_auth.authenticate(username, password) return self._login_defer -- cgit v1.2.3 From 72b7d49966637c019e97fae7f186774097e5e96f Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 28 Apr 2014 18:19:36 -0300 Subject: Refactor SoledadBootstrapper to backend. --- src/leap/bitmask/backend.py | 166 +++++++++++++++++++++ src/leap/bitmask/gui/mail_status.py | 2 +- src/leap/bitmask/gui/mainwindow.py | 122 ++++----------- src/leap/bitmask/services/mail/conductor.py | 6 +- .../services/soledad/soledadbootstrapper.py | 101 +++++++------ 5 files changed, 253 insertions(+), 144 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 65f12685..327131b3 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -31,6 +31,7 @@ from twisted.internet.task import LoopingCall from twisted.python import log import zope.interface +import zope.proxy from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpauth import SRPAuth @@ -45,6 +46,9 @@ from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper from leap.bitmask.services.eip import vpnlauncher, vpnprocess from leap.bitmask.services.eip import linuxvpnlauncher, darwinvpnlauncher +from leap.bitmask.services.soledad.soledadbootstrapper import \ + SoledadBootstrapper + from leap.common import certs as leap_certs # Frontend side @@ -545,6 +549,89 @@ class EIP(object): self._signaler.signal(self._signaler.EIP_CANNOT_START) +class Soledad(object): + """ + Interfaces with setup of Soledad. + """ + zope.interface.implements(ILEAPComponent) + + def __init__(self, signaler=None): + """ + Constructor for the Soledad component. + + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "soledad" + self._signaler = signaler + self._soledad_bootstrapper = SoledadBootstrapper(signaler) + self._soledad_defer = None + + def bootstrap(self, username, domain, password): + """ + Bootstrap Soledad with the user credentials. + + Signals: + soledad_download_config + soledad_gen_key + + :param user: user's login + :type user: unicode + :param domain: the domain that we are using. + :type domain: unicode + :param password: user's password + :type password: unicode + """ + provider_config = ProviderConfig.get_provider_config(domain) + if provider_config is not None: + self._soledad_defer = threads.deferToThread( + self._soledad_bootstrapper.run_soledad_setup_checks, + provider_config, username, password, + download_if_needed=True) + else: + if self._signaler is not None: + self._signaler.signal(self._signaler.SOLEDAD_BOOTSTRAP_FAILED) + logger.error("Could not load provider configuration.") + + return self._soledad_defer + + def load_offline(self, username, password, uuid): + """ + Load the soledad database in offline mode. + + :param username: full user id (user@provider) + :type username: str or unicode + :param password: the soledad passphrase + :type password: unicode + :param uuid: the user uuid + :type uuid: str or unicode + + Signals: + Signaler.soledad_offline_finished + Signaler.soledad_offline_failed + """ + self._soledad_bootstrapper.load_offline_soledad( + username, password, uuid) + + def cancel_bootstrap(self): + """ + Cancel the ongoing soledad bootstrap (if any). + """ + if self._soledad_defer is not None: + logger.debug("Cancelling soledad defer.") + self._soledad_defer.cancel() + self._soledad_defer = None + + def close(self): + """ + Close soledad database. + """ + soledad = self._soledad_bootstrapper.soledad + if soledad is not None: + soledad.close() + + class Authenticate(object): """ Interfaces with setup and bootstrapping operations for a provider @@ -738,6 +825,14 @@ class Signaler(QtCore.QObject): eip_can_start = QtCore.Signal(object) eip_cannot_start = QtCore.Signal(object) + # Signals for Soledad + soledad_bootstrap_failed = QtCore.Signal(object) + soledad_bootstrap_finished = QtCore.Signal(object) + soledad_offline_failed = QtCore.Signal(object) + soledad_offline_finished = QtCore.Signal(object) + soledad_invalid_auth_token = QtCore.Signal(object) + soledad_cancelled_bootstrap = QtCore.Signal(object) + # This signal is used to warn the backend user that is doing something # wrong backend_bad_call = QtCore.Signal(object) @@ -807,6 +902,14 @@ class Signaler(QtCore.QObject): EIP_CAN_START = "eip_can_start" EIP_CANNOT_START = "eip_cannot_start" + SOLEDAD_BOOTSTRAP_FAILED = "soledad_bootstrap_failed" + SOLEDAD_BOOTSTRAP_FINISHED = "soledad_bootstrap_finished" + SOLEDAD_OFFLINE_FAILED = "soledad_offline_failed" + SOLEDAD_OFFLINE_FINISHED = "soledad_offline_finished" + SOLEDAD_INVALID_AUTH_TOKEN = "soledad_invalid_auth_token" + + SOLEDAD_CANCELLED_BOOTSTRAP = "soledad_cancelled_bootstrap" + BACKEND_BAD_CALL = "backend_bad_call" def __init__(self): @@ -878,6 +981,13 @@ class Signaler(QtCore.QObject): self.SRP_STATUS_LOGGED_IN, self.SRP_STATUS_NOT_LOGGED_IN, + self.SOLEDAD_BOOTSTRAP_FAILED, + self.SOLEDAD_BOOTSTRAP_FINISHED, + self.SOLEDAD_OFFLINE_FAILED, + self.SOLEDAD_OFFLINE_FINISHED, + self.SOLEDAD_INVALID_AUTH_TOKEN, + self.SOLEDAD_CANCELLED_BOOTSTRAP, + self.BACKEND_BAD_CALL, ] @@ -937,6 +1047,7 @@ class Backend(object): self._register(Register(self._signaler)) self._register(Authenticate(self._signaler)) self._register(EIP(self._signaler)) + self._register(Soledad(self._signaler)) # We have a looping call on a thread executing all the # commands in queue. Right now this queue is an actual Queue @@ -1282,6 +1393,53 @@ class Backend(object): """ self._call_queue.put(("authenticate", "get_logged_in_status", None)) + def soledad_bootstrap(self, username, domain, password): + """ + Bootstrap the soledad database. + + :param username: the user name + :type username: unicode + :param domain: the domain that we are using. + :type domain: unicode + :param password: the password for the username + :type password: unicode + + Signals: + soledad_bootstrap_finished + soledad_bootstrap_failed + soledad_invalid_auth_token + """ + self._call_queue.put(("soledad", "bootstrap", None, + username, domain, password)) + + def load_offline_soledad(self, username, password, uuid): + """ + Load the soledad database in offline mode. + + :param username: full user id (user@provider) + :type username: str or unicode + :param password: the soledad passphrase + :type password: unicode + :param uuid: the user uuid + :type uuid: str or unicode + + Signals: + """ + self._call_queue.put(("soledad", "load_offline", None, + username, password, uuid)) + + def cancel_soledad_bootstrap(self): + """ + Cancel the ongoing soledad bootstrapping process (if any). + """ + self._call_queue.put(("soledad", "cancel_bootstrap", None)) + + def close_soledad(self): + """ + Close soledad database. + """ + self._call_queue.put(("soledad", "close", None)) + ########################################################################### # XXX HACK: this section is meant to be a place to hold methods and # variables needed in the meantime while we migrate all to the backend. @@ -1289,3 +1447,11 @@ class Backend(object): def get_provider_config(self): provider_config = self._components["provider"]._provider_config return provider_config + + def get_soledad(self): + soledad = self._components["soledad"]._soledad_bootstrapper._soledad + return soledad + + def get_keymanager(self): + km = self._components["soledad"]._soledad_bootstrapper._keymanager + return km diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index d3346780..5caef745 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -188,7 +188,7 @@ class MailStatusWidget(QtGui.QWidget): def set_soledad_failed(self): """ TRIGGERS: - SoledadBootstrapper.soledad_failed + Signaler.soledad_bootstrap_failed This method is called whenever soledad has a failure. """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index e3848c46..cceb1efe 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -57,8 +57,6 @@ from leap.bitmask.services.mail import conductor as mail_conductor from leap.bitmask.services import EIP_SERVICE, MX_SERVICE from leap.bitmask.services.eip.connection import EIPConnection -from leap.bitmask.services.soledad.soledadbootstrapper import \ - SoledadBootstrapper from leap.bitmask.util import make_address from leap.bitmask.util.keyring_helpers import has_keyring @@ -195,7 +193,6 @@ class MainWindow(QtGui.QMainWindow): self._provisional_provider_config = ProviderConfig() self._already_started_eip = False - self._already_started_soledad = False # This is created once we have a valid provider config self._srp_auth = None @@ -205,18 +202,6 @@ class MainWindow(QtGui.QMainWindow): self._backend_connected_signals = {} self._backend_connect() - self._soledad_bootstrapper = SoledadBootstrapper() - self._soledad_bootstrapper.download_config.connect( - self._soledad_intermediate_stage) - self._soledad_bootstrapper.gen_key.connect( - self._soledad_bootstrapped_stage) - self._soledad_bootstrapper.local_only_ready.connect( - self._soledad_bootstrapped_stage) - self._soledad_bootstrapper.soledad_invalid_auth_token.connect( - self._mail_status.set_soledad_invalid_auth_token) - self._soledad_bootstrapper.soledad_failed.connect( - self._mail_status.set_soledad_failed) - self.ui.action_preferences.triggered.connect(self._show_preferences) self.ui.action_eip_preferences.triggered.connect( self._show_eip_preferences) @@ -302,8 +287,6 @@ class MainWindow(QtGui.QMainWindow): self._soledad = ProxyBase(None) self._keymanager = ProxyBase(None) - self._soledad_defer = None - self._mail_conductor = mail_conductor.MailConductor( self._soledad, self._keymanager) self._mail_conductor.connect_mail_signals(self._mail_status) @@ -461,6 +444,21 @@ class MainWindow(QtGui.QMainWindow): sig.eip_can_start.connect(self._backend_can_start_eip) sig.eip_cannot_start.connect(self._backend_cannot_start_eip) + # Soledad signals + sig.soledad_bootstrap_failed.connect( + self._mail_status.set_soledad_failed) + sig.soledad_bootstrap_finished.connect(self._on_soledad_ready) + + sig.soledad_offline_failed.connect( + self._mail_status.set_soledad_failed) + sig.soledad_offline_finished.connect(self._on_soledad_ready) + + sig.soledad_invalid_auth_token.connect( + self._mail_status.set_soledad_invalid_auth_token) + + # TODO: connect this with something + # sig.soledad_cancelled_bootstrap.connect() + def _disconnect_and_untrack(self): """ Helper to disconnect the tracked signals. @@ -1252,11 +1250,7 @@ class MainWindow(QtGui.QMainWindow): # XXX: Should we stop all the backend defers? self._backend.cancel_setup_provider() self._backend.cancel_login() - - if self._soledad_defer is not None: - logger.debug("Cancelling soledad defer.") - self._soledad_defer.cancel() - self._soledad_defer = None + self._backend.cancel_soledad_bootstrap() @QtCore.Slot() def _set_login_cancelled(self): @@ -1317,9 +1311,9 @@ class MainWindow(QtGui.QMainWindow): if MX_SERVICE in self._enabled_services: btn_enabled = self._login_widget.set_logout_btn_enabled btn_enabled(False) - self.soledad_ready.connect(lambda: btn_enabled(True)) - self._soledad_bootstrapper.soledad_failed.connect( - lambda: btn_enabled(True)) + sig = self._backend.signaler + sig.soledad_bootstrap_failed.connect(lambda: btn_enabled(True)) + sig.soledad_bootstrap_finished.connect(lambda: btn_enabled(True)) if not self._get_best_provider_config().provides_mx(): self._set_mx_visible(False) @@ -1372,9 +1366,6 @@ class MainWindow(QtGui.QMainWindow): Conditionally start Soledad. """ # TODO split. - if self._already_started_soledad is True: - return - if not self._provides_mx_and_enabled(): return @@ -1382,11 +1373,7 @@ class MainWindow(QtGui.QMainWindow): password = unicode(self._login_widget.get_password()) provider_domain = self._login_widget.get_selected_provider() - sb = self._soledad_bootstrapper if flags.OFFLINE is True: - provider_domain = self._login_widget.get_selected_provider() - sb._password = password - self._provisional_provider_config.load( provider.get_provider_path(provider_domain)) @@ -1399,74 +1386,32 @@ class MainWindow(QtGui.QMainWindow): # this is mostly for internal use/debug for now. logger.warning("Sorry! Log-in at least one time.") return - fun = sb.load_offline_soledad - fun(full_user_id, password, uuid) + self._backend.load_offline_soledad(full_user_id, password, uuid) else: - provider_config = self._provider_config - if self._logged_user is not None: - self._soledad_defer = sb.run_soledad_setup_checks( - provider_config, username, password, - download_if_needed=True) + domain = self._provider_config.get_domain() + self._backend.soledad_bootstrap(username, domain, password) ################################################################### # Service control methods: soledad - @QtCore.Slot(dict) - def _soledad_intermediate_stage(self, data): - # TODO missing param docstring - """ - TRIGGERS: - self._soledad_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._soledad_bootstrapper.PASSED_KEY] - if not passed: - # TODO display in the GUI: - # should pass signal to a slot in status_panel - # that sets the global status - logger.error("Soledad failed to start: %s" % - (data[self._soledad_bootstrapper.ERROR_KEY],)) - - @QtCore.Slot(dict) - def _soledad_bootstrapped_stage(self, data): + @QtCore.Slot() + def _on_soledad_ready(self): """ TRIGGERS: - self._soledad_bootstrapper.gen_key - self._soledad_bootstrapper.local_only_ready + Signaler.soledad_bootstrap_finished - If there was a problem, displays it, otherwise it does nothing. - This is used for intermediate bootstrapping stages, in case - they fail. - - :param data: result from the bootstrapping stage for Soledad - :type data: dict + Actions to take when Soledad is ready. """ - passed = data[self._soledad_bootstrapper.PASSED_KEY] - if not passed: - # TODO should actually *display* on the panel. - logger.debug("ERROR on soledad bootstrapping:") - logger.error("%r" % data[self._soledad_bootstrapper.ERROR_KEY]) - return - logger.debug("Done bootstrapping Soledad") # Update the proxy objects to point to # the initialized instances. - setProxiedObject(self._soledad, - self._soledad_bootstrapper.soledad) - setProxiedObject(self._keymanager, - self._soledad_bootstrapper.keymanager) + setProxiedObject(self._soledad, self._backend.get_soledad()) + setProxiedObject(self._keymanager, self._backend.get_keymanager()) - # Ok, now soledad is ready, so we can allow other things that - # depend on soledad to start. - self._soledad_defer = None + self._soledad_started = True - # this will trigger start_imap_service - # and start_smtp_boostrapping self.soledad_ready.emit() ################################################################### @@ -1991,9 +1936,6 @@ class MainWindow(QtGui.QMainWindow): self._cancel_ongoing_defers() - # reset soledad status flag - self._already_started_soledad = False - # XXX: If other defers are doing authenticated stuff, this # might conflict with those. CHECK! self._backend.logout() @@ -2101,11 +2043,7 @@ class MainWindow(QtGui.QMainWindow): if self._logged_user is not None: self._backend.logout() - if self._soledad_bootstrapper.soledad is not None: - logger.debug("Closing soledad...") - self._soledad_bootstrapper.soledad.close() - else: - logger.error("No instance of soledad was found.") + self._backend.close_soledad() logger.debug('Terminating vpn') self._backend.stop_eip(shutdown=True) diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 1766a39d..b4e97ac1 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -72,11 +72,9 @@ class IMAPControl(object): from leap.bitmask.config import flags logger.debug('Starting imap service') - leap_assert(sameProxiedObjects(self._soledad, None) - is not True, + leap_assert(not sameProxiedObjects(self._soledad, None), "We need a non-null soledad for initializing imap service") - leap_assert(sameProxiedObjects(self._keymanager, None) - is not True, + leap_assert(not sameProxiedObjects(self._keymanager, None), "We need a non-null keymanager for initializing imap " "service") diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 6bb7c036..c015f5b7 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -25,7 +25,6 @@ import sys from ssl import SSLError from sqlite3 import ProgrammingError as sqlite_ProgrammingError -from PySide import QtCore from u1db import errors as u1db_errors from twisted.internet import threads from zope.proxy import sameProxiedObjects @@ -134,16 +133,11 @@ class SoledadBootstrapper(AbstractBootstrapper): MAX_INIT_RETRIES = 10 MAX_SYNC_RETRIES = 10 - # All dicts returned are of the form - # {"passed": bool, "error": str} - download_config = QtCore.Signal(dict) - gen_key = QtCore.Signal(dict) - local_only_ready = QtCore.Signal(dict) - soledad_invalid_auth_token = QtCore.Signal() - soledad_failed = QtCore.Signal() + def __init__(self, signaler=None): + AbstractBootstrapper.__init__(self, signaler) - def __init__(self): - AbstractBootstrapper.__init__(self) + if signaler is not None: + self._cancel_signal = signaler.SOLEDAD_CANCELLED_BOOTSTRAP self._provider_config = None self._soledad_config = None @@ -181,16 +175,22 @@ class SoledadBootstrapper(AbstractBootstrapper): Instantiate Soledad for offline use. :param username: full user id (user@provider) - :type username: basestring + :type username: str or unicode :param password: the soledad passphrase :type password: unicode :param uuid: the user uuid - :type uuid: basestring + :type uuid: str or unicode """ print "UUID ", uuid self._address = username + self._password = password self._uuid = uuid - return self.load_and_sync_soledad(uuid, offline=True) + try: + self.load_and_sync_soledad(uuid, offline=True) + self._signaler.signal(self._signaler.SOLEDAD_OFFLINE_FINISHED) + except Exception: + # TODO: we should handle more specific exceptions in here + self._signaler.signal(self._signaler.SOLEDAD_OFFLINE_FAILED) def _get_soledad_local_params(self, uuid, offline=False): """ @@ -245,7 +245,7 @@ class SoledadBootstrapper(AbstractBootstrapper): def _do_soledad_init(self, uuid, secrets_path, local_db_path, server_url, cert_file, token): """ - Initialize soledad, retry if necessary and emit soledad_failed if we + Initialize soledad, retry if necessary and raise an exception if we can't succeed. :param uuid: user identifier @@ -263,19 +263,22 @@ class SoledadBootstrapper(AbstractBootstrapper): :param auth token: auth token :type auth_token: str """ - init_tries = self.MAX_INIT_RETRIES - while init_tries > 0: + init_tries = 1 + while init_tries <= self.MAX_INIT_RETRIES: try: + logger.debug("Trying to init soledad....") self._try_soledad_init( uuid, secrets_path, local_db_path, server_url, cert_file, token) logger.debug("Soledad has been initialized.") return except Exception: - init_tries -= 1 + init_tries += 1 + msg = "Init failed, retrying... (retry {0} of {1})".format( + init_tries, self.MAX_INIT_RETRIES) + logger.warning(msg) continue - self.soledad_failed.emit() raise SoledadInitError() def load_and_sync_soledad(self, uuid=None, offline=False): @@ -306,9 +309,8 @@ class SoledadBootstrapper(AbstractBootstrapper): leap_assert(not sameProxiedObjects(self._soledad, None), "Null soledad, error while initializing") - if flags.OFFLINE is True: + if flags.OFFLINE: self._init_keymanager(self._address, token) - self.local_only_ready.emit({self.PASSED_KEY: True}) else: try: address = make_address( @@ -353,9 +355,10 @@ class SoledadBootstrapper(AbstractBootstrapper): Do several retries to get an initial soledad sync. """ # and now, let's sync - sync_tries = self.MAX_SYNC_RETRIES - while sync_tries > 0: + sync_tries = 1 + while sync_tries <= self.MAX_SYNC_RETRIES: try: + logger.debug("Trying to sync soledad....") self._try_soledad_sync() logger.debug("Soledad has been synced.") # so long, and thanks for all the fish @@ -368,19 +371,20 @@ class SoledadBootstrapper(AbstractBootstrapper): # retry strategy can be pushed to u1db, or at least # it's something worthy to talk about with the # ubuntu folks. - sync_tries -= 1 + sync_tries += 1 + msg = "Sync failed, retrying... (retry {0} of {1})".format( + sync_tries, self.MAX_SYNC_RETRIES) + logger.warning(msg) continue except InvalidAuthTokenError: - self.soledad_invalid_auth_token.emit() + self._signaler.signal( + self._signaler.SOLEDAD_INVALID_AUTH_TOKEN) raise except Exception as e: logger.exception("Unhandled error while syncing " "soledad: %r" % (e,)) break - # reached bottom, failed to sync - # and there's nothing we can do... - self.soledad_failed.emit() raise SoledadSyncError() def _try_soledad_init(self, uuid, secrets_path, local_db_path, @@ -443,7 +447,6 @@ class SoledadBootstrapper(AbstractBootstrapper): Raises SoledadSyncError if not successful. """ try: - logger.debug("trying to sync soledad....") self._soledad.sync() except SSLError as exc: logger.error("%r" % (exc,)) @@ -467,7 +470,6 @@ class SoledadBootstrapper(AbstractBootstrapper): """ Download the Soledad config for the given provider """ - leap_assert(self._provider_config, "We need a provider configuration!") logger.debug("Downloading Soledad config for %s" % @@ -480,14 +482,6 @@ class SoledadBootstrapper(AbstractBootstrapper): self._session, self._download_if_needed) - # soledad config is ok, let's proceed to load and sync soledad - # XXX but honestly, this is a pretty strange entry point for that. - # it feels like it should be the other way around: - # load_and_sync, and from there, if needed, call download_config - - uuid = self.srpauth.get_uuid() - self.load_and_sync_soledad(uuid) - def _get_gpg_bin_path(self): """ Return the path to gpg binary. @@ -574,7 +568,7 @@ class SoledadBootstrapper(AbstractBootstrapper): logger.exception(exc) # but we do not raise - def _gen_key(self, _): + def _gen_key(self): """ Generates the key pair if needed, uploads it to the webapp and nickserver @@ -613,10 +607,7 @@ class SoledadBootstrapper(AbstractBootstrapper): logger.debug("Key generated successfully.") - def run_soledad_setup_checks(self, - provider_config, - user, - password, + def run_soledad_setup_checks(self, provider_config, user, password, download_if_needed=False): """ Starts the checks needed for a new soledad setup @@ -640,9 +631,25 @@ class SoledadBootstrapper(AbstractBootstrapper): self._user = user self._password = password - cb_chain = [ - (self._download_config, self.download_config), - (self._gen_key, self.gen_key) - ] + if flags.OFFLINE: + signal_finished = self._signaler.SOLEDAD_OFFLINE_FINISHED + signal_failed = self._signaler.SOLEDAD_OFFLINE_FAILED + else: + signal_finished = self._signaler.SOLEDAD_BOOTSTRAP_FINISHED + signal_failed = self._signaler.SOLEDAD_BOOTSTRAP_FAILED + + try: + self._download_config() + + # soledad config is ok, let's proceed to load and sync soledad + uuid = self.srpauth.get_uuid() + self.load_and_sync_soledad(uuid) + + if not flags.OFFLINE: + self._gen_key() - return self.addCallbackChain(cb_chain) + self._signaler.signal(signal_finished) + except Exception as e: + # TODO: we should handle more specific exceptions in here + logger.exception("Error while bootstrapping Soledad: %r" % (e, )) + self._signaler.signal(signal_failed) -- cgit v1.2.3 From 2f57c31cda8208340322efd5c02f8b9120ce29aa Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 6 May 2014 16:16:40 -0300 Subject: Separate imap/smtp logic from conductor. --- src/leap/bitmask/backend.py | 33 +++++++ src/leap/bitmask/gui/mainwindow.py | 26 +----- src/leap/bitmask/services/mail/conductor.py | 104 +++++++++-------------- src/leap/bitmask/services/mail/imapcontroller.py | 98 +++++++++++++++++++++ 4 files changed, 172 insertions(+), 89 deletions(-) create mode 100644 src/leap/bitmask/services/mail/imapcontroller.py diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 327131b3..fd09929a 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -46,6 +46,8 @@ from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper from leap.bitmask.services.eip import vpnlauncher, vpnprocess from leap.bitmask.services.eip import linuxvpnlauncher, darwinvpnlauncher +from leap.bitmask.services.mail import imap + from leap.bitmask.services.soledad.soledadbootstrapper import \ SoledadBootstrapper @@ -632,6 +634,37 @@ class Soledad(object): soledad.close() +class Mail(object): + """ + Interfaces with setup and launch of Mail. + """ + + zope.interface.implements(ILEAPComponent) + + def __init__(self, signaler=None): + """ + Constructor for the Mail component. + + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "mail" + self._signaler = signaler + + def start_smtp_service(self): + pass + + def start_imap_service(self, offline=False): + pass + + def stop_smtp_service(self): + pass + + def stop_imap_service(self): + pass + + class Authenticate(object): """ Interfaces with setup and bootstrapping operations for a provider diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index cceb1efe..bf76f574 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -87,7 +87,6 @@ class MainWindow(QtGui.QMainWindow): new_updates = QtCore.Signal(object) raise_window = QtCore.Signal([]) soledad_ready = QtCore.Signal([]) - mail_client_logged_in = QtCore.Signal([]) logout = QtCore.Signal([]) # We use this flag to detect abnormal terminations @@ -123,9 +122,6 @@ class MainWindow(QtGui.QMainWindow): register(signal=proto.RAISE_WINDOW, callback=self._on_raise_window_event, reqcbk=lambda req, resp: None) # make rpc call async - register(signal=proto.IMAP_CLIENT_LOGIN, - callback=self._on_mail_client_logged_in, - reqcbk=lambda req, resp: None) # make rpc call async # end register leap events #################################### self._quit_callback = quit_callback @@ -265,7 +261,6 @@ class MainWindow(QtGui.QMainWindow): # XXX should connect to mail_conductor.start_mail_service instead self.soledad_ready.connect(self._start_smtp_bootstrapping) self.soledad_ready.connect(self._start_imap_service) - self.mail_client_logged_in.connect(self._fetch_incoming_mail) self.logout.connect(self._stop_imap_service) self.logout.connect(self._stop_smtp_service) @@ -1373,7 +1368,7 @@ class MainWindow(QtGui.QMainWindow): password = unicode(self._login_widget.get_password()) provider_domain = self._login_widget.get_selected_provider() - if flags.OFFLINE is True: + if flags.OFFLINE: self._provisional_provider_config.load( provider.get_provider_path(provider_domain)) @@ -1455,34 +1450,19 @@ class MainWindow(QtGui.QMainWindow): # in the mail state machine so it shows that imap is active # (but not smtp since it's not yet ready for offline use) start_fun = self._mail_conductor.start_imap_service - if flags.OFFLINE is True: + if flags.OFFLINE: provider_domain = self._login_widget.get_selected_provider() self._provider_config.load( provider.get_provider_path(provider_domain)) provides_mx = self._provider_config.provides_mx() - if flags.OFFLINE is True and provides_mx: + if flags.OFFLINE and provides_mx: start_fun() return if self._provides_mx_and_enabled(): start_fun() - def _on_mail_client_logged_in(self, req): - """ - Triggers qt signal when client login event is received. - """ - self.mail_client_logged_in.emit() - - @QtCore.Slot() - def _fetch_incoming_mail(self): - """ - TRIGGERS: - self.mail_client_logged_in - """ - # TODO connect signal directly!!! - self._mail_conductor.fetch_incoming_mail() - @QtCore.Slot() def _stop_imap_service(self): """ diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index b4e97ac1..aa22afe4 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -19,15 +19,15 @@ Mail Services Conductor """ import logging -from zope.proxy import sameProxiedObjects +from twisted.internet import threads +from leap.bitmask.config import flags from leap.bitmask.gui import statemachines from leap.bitmask.services.mail import connection as mail_connection -from leap.bitmask.services.mail import imap from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper from leap.bitmask.services.mail.smtpconfig import SMTPConfig +from leap.bitmask.services.mail.imapcontroller import IMAPController -from leap.common.check import leap_assert from leap.common.events import events_pb2 as leap_events from leap.common.events import register as leap_register @@ -39,15 +39,20 @@ class IMAPControl(object): """ Methods related to IMAP control. """ - def __init__(self): + def __init__(self, soledad, keymanager): """ Initializes smtp variables. + + :param soledad: a transparent proxy that eventually will point to a + Soledad Instance. + :type soledad: zope.proxy.ProxyBase + :param keymanager: a transparent proxy that eventually will point to a + Keymanager Instance. + :type keymanager: zope.proxy.ProxyBase """ self.imap_machine = None - self.imap_service = None - self.imap_port = None - self.imap_factory = None self.imap_connection = None + self._imap_controller = IMAPController(soledad, keymanager) leap_register(signal=leap_events.IMAP_SERVICE_STARTED, callback=self._handle_imap_events, @@ -55,10 +60,13 @@ class IMAPControl(object): leap_register(signal=leap_events.IMAP_SERVICE_FAILED_TO_START, callback=self._handle_imap_events, reqcbk=lambda req, resp: None) + leap_register(signal=leap_events.IMAP_CLIENT_LOGIN, + callback=self._handle_imap_events, + reqcbk=lambda req, resp: None) def set_imap_connection(self, imap_connection): """ - Sets the imap connection to an initialized connection. + Set the imap connection to an initialized connection. :param imap_connection: an initialized imap connection :type imap_connection: IMAPConnection instance. @@ -67,65 +75,23 @@ class IMAPControl(object): def start_imap_service(self): """ - Starts imap service. + Start imap service. """ - from leap.bitmask.config import flags - - logger.debug('Starting imap service') - leap_assert(not sameProxiedObjects(self._soledad, None), - "We need a non-null soledad for initializing imap service") - leap_assert(not sameProxiedObjects(self._keymanager, None), - "We need a non-null keymanager for initializing imap " - "service") - - offline = flags.OFFLINE - self.imap_service, self.imap_port, \ - self.imap_factory = imap.start_imap_service( - self._soledad, - self._keymanager, - userid=self.userid, - offline=offline) - - if offline is False: - logger.debug("Starting loop") - self.imap_service.start_loop() + threads.deferToThread(self._imap_controller.start_imap_service, + self.userid, flags.OFFLINE) def stop_imap_service(self, cv): """ - Stops imap service (fetcher, factory and port). + Stop imap service (fetcher, factory and port). :param cv: A condition variable to which we can signal when imap indeed stops. :type cv: threading.Condition """ self.imap_connection.qtsigs.disconnecting_signal.emit() - # TODO We should homogenize both services. - if self.imap_service is not None: - logger.debug('Stopping imap service.') - # Stop the loop call in the fetcher - self.imap_service.stop() - self.imap_service = None - # Stop listening on the IMAP port - self.imap_port.stopListening() - # Stop the protocol - self.imap_factory.theAccount.closed = True - self.imap_factory.doStop(cv) - else: - # main window does not have to wait because there's no service to - # be stopped, so we release the condition variable - cv.acquire() - cv.notify() - cv.release() - - def fetch_incoming_mail(self): - """ - Fetches incoming mail. - """ - if self.imap_service: - logger.debug('Client connected, fetching mail...') - self.imap_service.fetch() - - # handle events + logger.debug('Stopping imap service.') + + self._imap_controller.stop_imap_service(cv) def _handle_imap_events(self, req): """ @@ -135,25 +101,31 @@ class IMAPControl(object): :type req: leap.common.events.events_pb2.SignalRequest """ if req.event == leap_events.IMAP_SERVICE_STARTED: - self.on_imap_connected() + self._on_imap_connected() elif req.event == leap_events.IMAP_SERVICE_FAILED_TO_START: - self.on_imap_failed() + self._on_imap_failed() + elif req.event == leap_events.IMAP_CLIENT_LOGIN: + self._on_mail_client_logged_in() - # emit connection signals + def _on_mail_client_logged_in(self): + """ + On mail client logged in, fetch incoming mail. + """ + self._controller.imap_service_fetch() - def on_imap_connecting(self): + def _on_imap_connecting(self): """ Callback for IMAP connecting state. """ self.imap_connection.qtsigs.connecting_signal.emit() - def on_imap_connected(self): + def _on_imap_connected(self): """ Callback for IMAP connected state. """ self.imap_connection.qtsigs.connected_signal.emit() - def on_imap_failed(self): + def _on_imap_failed(self): """ Callback for IMAP failed state. """ @@ -197,7 +169,8 @@ class SMTPControl(object): :type download_if_needed: bool """ self.smtp_connection.qtsigs.connecting_signal.emit() - self.smtp_bootstrapper.start_smtp_service( + threads.deferToThread( + self.smtp_bootstrapper.start_smtp_service, provider_config, self.smtp_config, self._keymanager, self.userid, download_if_needed) @@ -258,12 +231,11 @@ class MailConductor(IMAPControl, SMTPControl): :param soledad: a transparent proxy that eventually will point to a Soledad Instance. :type soledad: zope.proxy.ProxyBase - :param keymanager: a transparent proxy that eventually will point to a Keymanager Instance. :type keymanager: zope.proxy.ProxyBase """ - IMAPControl.__init__(self) + IMAPControl.__init__(self, soledad, keymanager) SMTPControl.__init__(self) self._soledad = soledad self._keymanager = keymanager diff --git a/src/leap/bitmask/services/mail/imapcontroller.py b/src/leap/bitmask/services/mail/imapcontroller.py new file mode 100644 index 00000000..efca5867 --- /dev/null +++ b/src/leap/bitmask/services/mail/imapcontroller.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# imapcontroller.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 . +""" +IMAP service controller. +""" +import logging + +from leap.bitmask.services.mail import imap + + +logger = logging.getLogger(__name__) + + +class IMAPController(object): + """ + IMAP Controller. + """ + def __init__(self, soledad, keymanager): + """ + Initialize IMAP variables. + + :param soledad: a transparent proxy that eventually will point to a + Soledad Instance. + :type soledad: zope.proxy.ProxyBase + :param keymanager: a transparent proxy that eventually will point to a + Keymanager Instance. + :type keymanager: zope.proxy.ProxyBase + """ + self._soledad = soledad + self._keymanager = keymanager + + self.imap_service = None + self.imap_port = None + self.imap_factory = None + + def start_imap_service(self, userid, offline=False): + """ + Start IMAP service. + """ + logger.debug('Starting imap service') + + self.imap_service, self.imap_port, \ + self.imap_factory = imap.start_imap_service( + self._soledad, + self._keymanager, + userid=userid, + offline=offline) + + if offline is False: + logger.debug("Starting loop") + self.imap_service.start_loop() + + def stop_imap_service(self, cv): + """ + Stop IMAP service (fetcher, factory and port). + + :param cv: A condition variable to which we can signal when imap + indeed stops. + :type cv: threading.Condition + """ + if self.imap_service is not None: + # Stop the loop call in the fetcher + self.imap_service.stop() + self.imap_service = None + + # Stop listening on the IMAP port + self.imap_port.stopListening() + + # Stop the protocol + self.imap_factory.theAccount.closed = True + self.imap_factory.doStop(cv) + else: + # Release the condition variable so the caller doesn't have to wait + cv.acquire() + cv.notify() + cv.release() + + def fetch_incoming_mail(self): + """ + Fetch incoming mail. + """ + if self.imap_service: + logger.debug('Client connected, fetching mail...') + self.imap_service.fetch() -- cgit v1.2.3 From 3a81b31204680bf2ba0abe26467aef201cf030fa Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 14 May 2014 12:25:00 -0300 Subject: Move Mail logic to backend. --- src/leap/bitmask/backend.py | 137 +++++++++++++++++++-- src/leap/bitmask/gui/mainwindow.py | 8 +- src/leap/bitmask/services/mail/conductor.py | 56 ++------- src/leap/bitmask/services/mail/imapcontroller.py | 5 + src/leap/bitmask/services/mail/smtpbootstrapper.py | 24 ++-- .../services/soledad/soledadbootstrapper.py | 2 + 6 files changed, 163 insertions(+), 69 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index fd09929a..6a6b1065 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -46,7 +46,10 @@ from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper from leap.bitmask.services.eip import vpnlauncher, vpnprocess from leap.bitmask.services.eip import linuxvpnlauncher, darwinvpnlauncher -from leap.bitmask.services.mail import imap + +from leap.bitmask.services.mail.imapcontroller import IMAPController +from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper +from leap.bitmask.services.mail.smtpconfig import SMTPConfig from leap.bitmask.services.soledad.soledadbootstrapper import \ SoledadBootstrapper @@ -557,15 +560,21 @@ class Soledad(object): """ zope.interface.implements(ILEAPComponent) - def __init__(self, signaler=None): + def __init__(self, soledad_proxy, keymanager_proxy, signaler=None): """ Constructor for the Soledad component. + :param soledad_proxy: proxy to pass around a Soledad object. + :type soledad_proxy: zope.ProxyBase + :param keymanager_proxy: proxy to pass around a Keymanager object. + :type keymanager_proxy: zope.ProxyBase :param signaler: Object in charge of handling communication back to the frontend :type signaler: Signaler """ self.key = "soledad" + self._soledad_proxy = soledad_proxy + self._keymanager_proxy = keymanager_proxy self._signaler = signaler self._soledad_bootstrapper = SoledadBootstrapper(signaler) self._soledad_defer = None @@ -591,6 +600,7 @@ class Soledad(object): self._soledad_bootstrapper.run_soledad_setup_checks, provider_config, username, password, download_if_needed=True) + self._soledad_defer.addCallback(self._set_proxies_cb) else: if self._signaler is not None: self._signaler.signal(self._signaler.SOLEDAD_BOOTSTRAP_FAILED) @@ -598,6 +608,16 @@ class Soledad(object): return self._soledad_defer + def _set_proxies_cb(self, _): + """ + Update the soledad and keymanager proxies to reference the ones created + in the bootstrapper. + """ + zope.proxy.setProxiedObject(self._soledad_proxy, + self._soledad_bootstrapper.soledad) + zope.proxy.setProxiedObject(self._keymanager_proxy, + self._soledad_bootstrapper.keymanager) + def load_offline(self, username, password, uuid): """ Load the soledad database in offline mode. @@ -641,28 +661,70 @@ class Mail(object): zope.interface.implements(ILEAPComponent) - def __init__(self, signaler=None): + def __init__(self, soledad_proxy, keymanager_proxy, signaler=None): """ Constructor for the Mail component. + :param soledad_proxy: proxy to pass around a Soledad object. + :type soledad_proxy: zope.ProxyBase + :param keymanager_proxy: proxy to pass around a Keymanager object. + :type keymanager_proxy: zope.ProxyBase :param signaler: Object in charge of handling communication back to the frontend :type signaler: Signaler """ self.key = "mail" self._signaler = signaler + self._soledad_proxy = soledad_proxy + self._keymanager_proxy = keymanager_proxy + self._imap_controller = IMAPController(self._soledad_proxy, + self._keymanager_proxy) + self._smtp_bootstrapper = SMTPBootstrapper() + self._smtp_config = SMTPConfig() - def start_smtp_service(self): - pass + def start_smtp_service(self, full_user_id, download_if_needed=False): + """ + Start the SMTP service. - def start_imap_service(self, offline=False): - pass + :param full_user_id: user id, in the form "user@provider" + :type full_user_id: str + :param download_if_needed: True if it should check for mtime + for the file + :type download_if_needed: bool + """ + return threads.deferToThread( + self._smtp_bootstrapper.start_smtp_service, + self._keymanager_proxy, full_user_id, download_if_needed) + + def start_imap_service(self, full_user_id, offline=False): + """ + Start the IMAP service. + + :param full_user_id: user id, in the form "user@provider" + :type full_user_id: str + :param offline: whether imap should start in offline mode or not. + :type offline: bool + """ + return threads.deferToThread( + self._imap_controller.start_imap_service, + full_user_id, offline) def stop_smtp_service(self): - pass + """ + Stop the SMTP service. + """ + return threads.deferToThread(self._smtp_bootstrapper.stop_smtp_service) - def stop_imap_service(self): - pass + def stop_imap_service(self, cv): + """ + Stop imap service (fetcher, factory and port). + + :param cv: A condition variable to which we can signal when imap + indeed stops. + :type cv: threading.Condition + """ + return threads.deferToThread( + self._imap_controller.stop_imap_service, cv) class Authenticate(object): @@ -1075,12 +1137,22 @@ class Backend(object): # Signaler object to translate commands into Qt signals self._signaler = Signaler() + # Objects needed by several components, so we make a proxy and pass + # them around + self._soledad_proxy = zope.proxy.ProxyBase(None) + self._keymanager_proxy = zope.proxy.ProxyBase(None) + # Component registration self._register(Provider(self._signaler, bypass_checks)) self._register(Register(self._signaler)) self._register(Authenticate(self._signaler)) self._register(EIP(self._signaler)) - self._register(Soledad(self._signaler)) + self._register(Soledad(self._soledad_proxy, + self._keymanager_proxy, + self._signaler)) + self._register(Mail(self._soledad_proxy, + self._keymanager_proxy, + self._signaler)) # We have a looping call on a thread executing all the # commands in queue. Right now this queue is an actual Queue @@ -1473,11 +1545,54 @@ class Backend(object): """ self._call_queue.put(("soledad", "close", None)) + def start_smtp_service(self, full_user_id, download_if_needed=False): + """ + Start the SMTP service. + + :param full_user_id: user id, in the form "user@provider" + :type full_user_id: str + :param download_if_needed: True if it should check for mtime + for the file + :type download_if_needed: bool + """ + self._call_queue.put(("mail", "start_smtp_service", None, + full_user_id, download_if_needed)) + + def start_imap_service(self, full_user_id, offline=False): + """ + Start the IMAP service. + + :param full_user_id: user id, in the form "user@provider" + :type full_user_id: str + :param offline: whether imap should start in offline mode or not. + :type offline: bool + """ + self._call_queue.put(("mail", "start_imap_service", None, + full_user_id, offline)) + + def stop_smtp_service(self): + """ + Stop the SMTP service. + """ + self._call_queue.put(("mail", "stop_smtp_service", None)) + + def stop_imap_service(self, cv): + """ + Stop imap service. + + :param cv: A condition variable to which we can signal when imap + indeed stops. + :type cv: threading.Condition + """ + self._call_queue.put(("mail", "stop_imap_service", None, cv)) + ########################################################################### # XXX HACK: this section is meant to be a place to hold methods and # variables needed in the meantime while we migrate all to the backend. def get_provider_config(self): + # TODO: refactor the provider config into a singleton/global loading it + # every time from the file. provider_config = self._components["provider"]._provider_config return provider_config diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index bf76f574..370a9d43 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -282,8 +282,9 @@ class MainWindow(QtGui.QMainWindow): self._soledad = ProxyBase(None) self._keymanager = ProxyBase(None) - self._mail_conductor = mail_conductor.MailConductor( - self._soledad, self._keymanager) + self._mail_conductor = mail_conductor.MailConductor(self._backend) + # self._mail_conductor = mail_conductor.MailConductor( + # self._soledad, self._keymanager) self._mail_conductor.connect_mail_signals(self._mail_status) # Eip machine is a public attribute where the state machine for @@ -1423,8 +1424,7 @@ class MainWindow(QtGui.QMainWindow): return if self._provides_mx_and_enabled(): - self._mail_conductor.start_smtp_service(self._provider_config, - download_if_needed=True) + self._mail_conductor.start_smtp_service(download_if_needed=True) # XXX --- should remove from here, and connecte directly to the state # machine. diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index aa22afe4..67bc007e 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -19,14 +19,9 @@ Mail Services Conductor """ import logging -from twisted.internet import threads - from leap.bitmask.config import flags from leap.bitmask.gui import statemachines from leap.bitmask.services.mail import connection as mail_connection -from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper -from leap.bitmask.services.mail.smtpconfig import SMTPConfig -from leap.bitmask.services.mail.imapcontroller import IMAPController from leap.common.events import events_pb2 as leap_events from leap.common.events import register as leap_register @@ -39,20 +34,12 @@ class IMAPControl(object): """ Methods related to IMAP control. """ - def __init__(self, soledad, keymanager): + def __init__(self): """ Initializes smtp variables. - - :param soledad: a transparent proxy that eventually will point to a - Soledad Instance. - :type soledad: zope.proxy.ProxyBase - :param keymanager: a transparent proxy that eventually will point to a - Keymanager Instance. - :type keymanager: zope.proxy.ProxyBase """ self.imap_machine = None self.imap_connection = None - self._imap_controller = IMAPController(soledad, keymanager) leap_register(signal=leap_events.IMAP_SERVICE_STARTED, callback=self._handle_imap_events, @@ -77,12 +64,11 @@ class IMAPControl(object): """ Start imap service. """ - threads.deferToThread(self._imap_controller.start_imap_service, - self.userid, flags.OFFLINE) + self._backend.start_imap_service(self.userid, flags.OFFLINE) def stop_imap_service(self, cv): """ - Stop imap service (fetcher, factory and port). + Stop imap service. :param cv: A condition variable to which we can signal when imap indeed stops. @@ -91,7 +77,7 @@ class IMAPControl(object): self.imap_connection.qtsigs.disconnecting_signal.emit() logger.debug('Stopping imap service.') - self._imap_controller.stop_imap_service(cv) + self._backend.stop_imap_service(cv) def _handle_imap_events(self, req): """ @@ -137,12 +123,9 @@ class SMTPControl(object): """ Initializes smtp variables. """ - self.smtp_config = SMTPConfig() self.smtp_connection = None self.smtp_machine = None - self.smtp_bootstrapper = SMTPBootstrapper() - leap_register(signal=leap_events.SMTP_SERVICE_STARTED, callback=self._handle_smtp_events, reqcbk=lambda req, resp: None) @@ -158,30 +141,23 @@ class SMTPControl(object): """ self.smtp_connection = smtp_connection - def start_smtp_service(self, provider_config, download_if_needed=False): + def start_smtp_service(self, download_if_needed=False): """ Starts the SMTP service. - :param provider_config: Provider configuration - :type provider_config: ProviderConfig :param download_if_needed: True if it should check for mtime for the file :type download_if_needed: bool """ self.smtp_connection.qtsigs.connecting_signal.emit() - threads.deferToThread( - self.smtp_bootstrapper.start_smtp_service, - provider_config, self.smtp_config, self._keymanager, - self.userid, download_if_needed) + self._backend.start_smtp_service(self.userid, download_if_needed) def stop_smtp_service(self): """ Stops the SMTP service. """ self.smtp_connection.qtsigs.disconnecting_signal.emit() - self.smtp_bootstrapper.stop_smtp_service() - - # handle smtp events + self._backend.stop_smtp_service() def _handle_smtp_events(self, req): """ @@ -195,8 +171,6 @@ class SMTPControl(object): elif req.event == leap_events.SMTP_SERVICE_FAILED_TO_START: self.on_smtp_failed() - # emit connection signals - def on_smtp_connecting(self): """ Callback for SMTP connecting state. @@ -224,21 +198,17 @@ class MailConductor(IMAPControl, SMTPControl): """ # XXX We could consider to use composition instead of inheritance here. - def __init__(self, soledad, keymanager): + def __init__(self, backend): """ Initializes the mail conductor. - :param soledad: a transparent proxy that eventually will point to a - Soledad Instance. - :type soledad: zope.proxy.ProxyBase - :param keymanager: a transparent proxy that eventually will point to a - Keymanager Instance. - :type keymanager: zope.proxy.ProxyBase + :param backend: Backend being used + :type backend: Backend """ - IMAPControl.__init__(self, soledad, keymanager) + IMAPControl.__init__(self) SMTPControl.__init__(self) - self._soledad = soledad - self._keymanager = keymanager + + self._backend = backend self._mail_machine = None self._mail_connection = mail_connection.MailConnection() diff --git a/src/leap/bitmask/services/mail/imapcontroller.py b/src/leap/bitmask/services/mail/imapcontroller.py index efca5867..d0bf4c34 100644 --- a/src/leap/bitmask/services/mail/imapcontroller.py +++ b/src/leap/bitmask/services/mail/imapcontroller.py @@ -50,6 +50,11 @@ class IMAPController(object): def start_imap_service(self, userid, offline=False): """ Start IMAP service. + + :param userid: user id, in the form "user@provider" + :type userid: str + :param offline: whether imap should start in offline mode or not. + :type offline: bool """ logger.debug('Starting imap service') diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py index 7ecf8134..785fe404 100644 --- a/src/leap/bitmask/services/mail/smtpbootstrapper.py +++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py @@ -28,7 +28,7 @@ from leap.bitmask.services.mail.smtpconfig import SMTPConfig from leap.bitmask.util import is_file from leap.common import certs as leap_certs -from leap.common.check import leap_assert, leap_assert_type +from leap.common.check import leap_assert from leap.common.files import check_and_fix_urw_only logger = logging.getLogger(__name__) @@ -38,6 +38,10 @@ class NoSMTPHosts(Exception): """This is raised when there is no SMTP host to use.""" +class MalformedUserId(Exception): + """This is raised when an userid does not have the form user@provider.""" + + class SMTPBootstrapper(AbstractBootstrapper): """ SMTP init procedure @@ -126,15 +130,10 @@ class SMTPBootstrapper(AbstractBootstrapper): smtp_key=client_cert_path, encrypted_only=False) - def start_smtp_service(self, provider_config, smtp_config, keymanager, - userid, download_if_needed=False): + def start_smtp_service(self, keymanager, userid, download_if_needed=False): """ Starts the SMTP service. - :param provider_config: Provider configuration - :type provider_config: ProviderConfig - :param smtp_config: SMTP configuration to populate - :type smtp_config: SMTPConfig :param keymanager: a transparent proxy that eventually will point to a Keymanager Instance. :type keymanager: zope.proxy.ProxyBase @@ -144,12 +143,15 @@ class SMTPBootstrapper(AbstractBootstrapper): for the file :type download_if_needed: bool """ - leap_assert_type(provider_config, ProviderConfig) - leap_assert_type(smtp_config, SMTPConfig) + try: + username, domain = userid.split('@') + except ValueError: + logger.critical("Malformed userid parameter!") + raise MalformedUserId() - self._provider_config = provider_config + self._provider_config = ProviderConfig.get_provider_config(domain) self._keymanager = keymanager - self._smtp_config = smtp_config + self._smtp_config = SMTPConfig() self._useid = userid self._download_if_needed = download_if_needed diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index c015f5b7..2bdad7e2 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -651,5 +651,7 @@ class SoledadBootstrapper(AbstractBootstrapper): self._signaler.signal(signal_finished) except Exception as e: # TODO: we should handle more specific exceptions in here + self._soledad = None + self._keymanager = None logger.exception("Error while bootstrapping Soledad: %r" % (e, )) self._signaler.signal(signal_failed) -- cgit v1.2.3 From 8b7e1336806332c3466405bc1df1da3682689524 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 14 May 2014 17:53:29 -0300 Subject: Avoid reconnect same signals several times. We don't want to connect again the untracked signals after the wizard was finished. --- src/leap/bitmask/gui/mainwindow.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 370a9d43..cd3e77c9 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -349,14 +349,23 @@ class MainWindow(QtGui.QMainWindow): logger.error("Bad call to the backend:") logger.error(data) - def _backend_connect(self): + def _backend_connect(self, only_tracked=False): """ - Helper to connect to backend signals + Connect to backend signals. + + We track some signals in order to disconnect them on demand. + For instance, in the wizard we need to connect to some signals that are + already connected in the mainwindow, so to avoid conflicts we do: + - disconnect signals needed in wizard (`_disconnect_and_untrack`) + - use wizard + - reconnect disconnected signals (we use the `only_tracked` param) + + :param only_tracked: whether or not we should connect only the signals + that we are tracking to disconnect later. + :type only_tracked: bool """ sig = self._backend.signaler - sig.backend_bad_call.connect(self._backend_bad_call) - self._connect_and_track(sig.prov_name_resolution, self._intermediate_stage) self._connect_and_track(sig.prov_https_connection, @@ -412,7 +421,16 @@ class MainWindow(QtGui.QMainWindow): self._connect_and_track(sig.eip_client_certificate_ready, self._finish_eip_bootstrap) + ################################################### + # Add tracked signals above this, untracked bellow! + ################################################### + if only_tracked: + return + # We don't want to disconnect some signals so don't track them: + + sig.backend_bad_call.connect(self._backend_bad_call) + sig.prov_unsupported_client.connect(self._needs_update) sig.prov_unsupported_api.connect(self._incompatible_api) @@ -491,7 +509,7 @@ class MainWindow(QtGui.QMainWindow): # This happens if the user finishes the provider # setup but does not register self._wizard = None - self._backend_connect() + self._backend_connect(only_tracked=True) if self._wizard_firstrun: self._finish_init() @@ -811,7 +829,7 @@ class MainWindow(QtGui.QMainWindow): self.eip_needs_login.emit() self._wizard = None - self._backend_connect() + self._backend_connect(only_tracked=True) else: self._update_eip_enabled_status() -- cgit v1.2.3 From f0951ad92cb0bf2116a333b8df8279918c5febd6 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 15 May 2014 16:46:00 -0300 Subject: Move soledad password change to backend. Also cleanup soledad usage in the GUI. --- src/leap/bitmask/backend.py | 84 +++++++++++++++++++++++-- src/leap/bitmask/gui/advanced_key_management.py | 12 ++-- src/leap/bitmask/gui/mainwindow.py | 27 +++----- src/leap/bitmask/gui/preferenceswindow.py | 70 ++++++++++++++------- 4 files changed, 141 insertions(+), 52 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 6a6b1065..49a5ca0f 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -56,6 +56,8 @@ from leap.bitmask.services.soledad.soledadbootstrapper import \ from leap.common import certs as leap_certs +from leap.soledad.client import NoStorageSecret, PassphraseTooShort + # Frontend side from PySide import QtCore @@ -644,14 +646,52 @@ class Soledad(object): logger.debug("Cancelling soledad defer.") self._soledad_defer.cancel() self._soledad_defer = None + zope.proxy.setProxiedObject(self._soledad_proxy, None) def close(self): """ Close soledad database. """ - soledad = self._soledad_bootstrapper.soledad - if soledad is not None: - soledad.close() + if not zope.proxy.sameProxiedObjects(self._soledad_proxy, None): + self._soledad_proxy.close() + zope.proxy.setProxiedObject(self._soledad_proxy, None) + + def _change_password_ok(self, _): + """ + Password change callback. + """ + if self._signaler is not None: + self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_OK) + + def _change_password_error(self, failure): + """ + Password change errback. + + :param failure: failure object containing problem. + :type failure: twisted.python.failure.Failure + """ + if failure.check(NoStorageSecret): + logger.error("No storage secret for password change in Soledad.") + if failure.check(PassphraseTooShort): + logger.error("Passphrase too short.") + + if self._signaler is not None: + self._signaler.signal(self._signaler.SOLEDAD_PASSWORD_CHANGE_ERROR) + + def change_password(self, new_password): + """ + Change the database's password. + + :param new_password: the new password. + :type new_password: unicode + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + d = threads.deferToThread(self._soledad_proxy.change_passphrase, + new_password) + d.addCallback(self._change_password_ok) + d.addErrback(self._change_password_error) class Mail(object): @@ -691,6 +731,9 @@ class Mail(object): :param download_if_needed: True if it should check for mtime for the file :type download_if_needed: bool + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred """ return threads.deferToThread( self._smtp_bootstrapper.start_smtp_service, @@ -704,6 +747,9 @@ class Mail(object): :type full_user_id: str :param offline: whether imap should start in offline mode or not. :type offline: bool + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred """ return threads.deferToThread( self._imap_controller.start_imap_service, @@ -712,6 +758,9 @@ class Mail(object): def stop_smtp_service(self): """ Stop the SMTP service. + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred """ return threads.deferToThread(self._smtp_bootstrapper.stop_smtp_service) @@ -722,6 +771,9 @@ class Mail(object): :param cv: A condition variable to which we can signal when imap indeed stops. :type cv: threading.Condition + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred """ return threads.deferToThread( self._imap_controller.stop_imap_service, cv) @@ -927,6 +979,8 @@ class Signaler(QtCore.QObject): soledad_offline_finished = QtCore.Signal(object) soledad_invalid_auth_token = QtCore.Signal(object) soledad_cancelled_bootstrap = QtCore.Signal(object) + soledad_password_change_ok = QtCore.Signal(object) + soledad_password_change_error = QtCore.Signal(object) # This signal is used to warn the backend user that is doing something # wrong @@ -1003,6 +1057,9 @@ class Signaler(QtCore.QObject): SOLEDAD_OFFLINE_FINISHED = "soledad_offline_finished" SOLEDAD_INVALID_AUTH_TOKEN = "soledad_invalid_auth_token" + SOLEDAD_PASSWORD_CHANGE_OK = "soledad_password_change_ok" + SOLEDAD_PASSWORD_CHANGE_ERROR = "soledad_password_change_error" + SOLEDAD_CANCELLED_BOOTSTRAP = "soledad_cancelled_bootstrap" BACKEND_BAD_CALL = "backend_bad_call" @@ -1083,6 +1140,9 @@ class Signaler(QtCore.QObject): self.SOLEDAD_INVALID_AUTH_TOKEN, self.SOLEDAD_CANCELLED_BOOTSTRAP, + self.SOLEDAD_PASSWORD_CHANGE_OK, + self.SOLEDAD_PASSWORD_CHANGE_ERROR, + self.BACKEND_BAD_CALL, ] @@ -1470,7 +1530,7 @@ class Backend(object): """ self._call_queue.put(("authenticate", "cancel_login", None)) - def change_password(self, current_password, new_password): + def auth_change_password(self, current_password, new_password): """ Change the user's password. @@ -1488,6 +1548,22 @@ class Backend(object): self._call_queue.put(("authenticate", "change_password", None, current_password, new_password)) + def soledad_change_password(self, new_password): + """ + Change the database's password. + + :param new_password: the new password for the user. + :type new_password: unicode + + Signals: + srp_not_logged_in_error + srp_password_change_ok + srp_password_change_badpw + srp_password_change_error + """ + self._call_queue.put(("soledad", "change_password", None, + new_password)) + def get_logged_in_status(self): """ Signal if the user is currently logged in or not. diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py index be6b4410..1681caca 100644 --- a/src/leap/bitmask/gui/advanced_key_management.py +++ b/src/leap/bitmask/gui/advanced_key_management.py @@ -20,7 +20,6 @@ Advanced Key Management import logging from PySide import QtGui -from zope.proxy import sameProxiedObjects from leap.keymanager import openpgp from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch @@ -34,7 +33,7 @@ class AdvancedKeyManagement(QtGui.QDialog): """ Advanced Key Management """ - def __init__(self, parent, has_mx, user, keymanager, soledad): + def __init__(self, parent, has_mx, user, keymanager, soledad_started): """ :param parent: parent object of AdvancedKeyManagement. :parent type: QWidget @@ -45,8 +44,8 @@ class AdvancedKeyManagement(QtGui.QDialog): :type user: unicode :param keymanager: the existing keymanager instance :type keymanager: KeyManager - :param soledad: a loaded instance of Soledad - :type soledad: Soledad + :param soledad_started: whether soledad has started or not + :type soledad_started: bool """ QtGui.QDialog.__init__(self, parent) @@ -56,7 +55,6 @@ class AdvancedKeyManagement(QtGui.QDialog): # XXX: Temporarily disable the key import. self.ui.pbImportKeys.setVisible(False) - # if Soledad is not started yet if not has_mx: msg = self.tr("The provider that you are using " "does not support {0}.") @@ -64,8 +62,7 @@ class AdvancedKeyManagement(QtGui.QDialog): self._disable_ui(msg) return - # if Soledad is not started yet - if sameProxiedObjects(soledad, None): + if not soledad_started: msg = self.tr("To use this, you need to enable/start {0}.") msg = msg.format(get_service_display_name(MX_SERVICE)) self._disable_ui(msg) @@ -79,7 +76,6 @@ class AdvancedKeyManagement(QtGui.QDialog): # self.ui.lblStatus.setText(msg) self._keymanager = keymanager - self._soledad = soledad self._key = keymanager.get_key(user, openpgp.OpenPGPKey) self._key_priv = keymanager.get_key( diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index cd3e77c9..e1527bbe 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -189,6 +189,7 @@ class MainWindow(QtGui.QMainWindow): self._provisional_provider_config = ProviderConfig() self._already_started_eip = False + self._soledad_started = False # This is created once we have a valid provider config self._srp_auth = None @@ -276,15 +277,9 @@ class MainWindow(QtGui.QMainWindow): self._bypass_checks = bypass_checks self._start_hidden = start_hidden - # We initialize Soledad and Keymanager instances as - # transparent proxies, so we can pass the reference freely - # around. - self._soledad = ProxyBase(None) self._keymanager = ProxyBase(None) self._mail_conductor = mail_conductor.MailConductor(self._backend) - # self._mail_conductor = mail_conductor.MailConductor( - # self._soledad, self._keymanager) self._mail_conductor.connect_mail_signals(self._mail_status) # Eip machine is a public attribute where the state machine for @@ -603,8 +598,8 @@ class MainWindow(QtGui.QMainWindow): provider_config = self._get_best_provider_config() has_mx = provider_config.provides_mx() - akm = AdvancedKeyManagement( - self, has_mx, logged_user, self._keymanager, self._soledad) + akm = AdvancedKeyManagement(self, has_mx, logged_user, + self._keymanager, self._soledad_started) akm.show() @QtCore.Slot() @@ -619,8 +614,8 @@ class MainWindow(QtGui.QMainWindow): user = self._login_widget.get_user() prov = self._login_widget.get_selected_provider() preferences = PreferencesWindow( - self, self._backend, self._provider_config, self._soledad, - user, prov) + self, self._backend, self._provider_config, + self._soledad_started, user, prov) self.soledad_ready.connect(preferences.set_soledad_ready) preferences.show() @@ -1265,6 +1260,9 @@ class MainWindow(QtGui.QMainWindow): self._backend.cancel_setup_provider() self._backend.cancel_login() self._backend.cancel_soledad_bootstrap() + self._backend.close_soledad() + + self._soledad_started = False @QtCore.Slot() def _set_login_cancelled(self): @@ -1419,9 +1417,8 @@ class MainWindow(QtGui.QMainWindow): """ logger.debug("Done bootstrapping Soledad") - # Update the proxy objects to point to - # the initialized instances. - setProxiedObject(self._soledad, self._backend.get_soledad()) + # Update the proxy objects to point to the initialized instances. + # setProxiedObject(self._soledad, self._backend.get_soledad()) setProxiedObject(self._keymanager, self._backend.get_keymanager()) self._soledad_started = True @@ -1930,8 +1927,6 @@ class MainWindow(QtGui.QMainWindow): Starts the logout sequence """ - setProxiedObject(self._soledad, None) - self._cancel_ongoing_defers() # XXX: If other defers are doing authenticated stuff, this @@ -2041,8 +2036,6 @@ class MainWindow(QtGui.QMainWindow): if self._logged_user is not None: self._backend.logout() - self._backend.close_soledad() - logger.debug('Terminating vpn') self._backend.stop_eip(shutdown=True) diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 2947c5db..77a3994f 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -23,12 +23,10 @@ import logging from functools import partial from PySide import QtCore, QtGui -from zope.proxy import sameProxiedObjects from leap.bitmask.provider import get_provider_path from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.gui.ui_preferences import Ui_Preferences -from leap.soledad.client import NoStorageSecret from leap.bitmask.util.password import basic_password_checks from leap.bitmask.services import get_supported from leap.bitmask.config.providerconfig import ProviderConfig @@ -43,8 +41,8 @@ class PreferencesWindow(QtGui.QDialog): """ preferences_saved = QtCore.Signal() - def __init__(self, parent, backend, provider_config, - soledad, username, domain): + def __init__(self, parent, backend, provider_config, soledad_started, + username, domain): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget @@ -52,8 +50,8 @@ class PreferencesWindow(QtGui.QDialog): :type backend: Backend :param provider_config: ProviderConfig object. :type provider_config: ProviderConfig - :param soledad: Soledad instance - :type soledad: Soledad + :param soledad_started: whether soledad has started or not + :type soledad_started: bool :param username: the user set in the login widget :type username: unicode :param domain: the selected domain in the login widget @@ -64,8 +62,8 @@ class PreferencesWindow(QtGui.QDialog): self._backend = backend self._settings = LeapSettings() - self._soledad = soledad self._provider_config = provider_config + self._soledad_started = soledad_started self._username = username self._domain = domain @@ -118,7 +116,7 @@ class PreferencesWindow(QtGui.QDialog): pw_enabled = False else: # check if Soledad is bootstrapped - if sameProxiedObjects(self._soledad, None): + if not self._soledad_started: msg = self.tr( "You need to wait until {0} is ready in " "order to change the password.".format(mx_name)) @@ -209,10 +207,10 @@ class PreferencesWindow(QtGui.QDialog): return self._set_changing_password(True) - self._backend.change_password(current_password, new_password) + self._backend.auth_change_password(current_password, new_password) @QtCore.Slot() - def _change_password_ok(self): + def _srp_change_password_ok(self): """ TRIGGERS: self._backend.signaler.srp_password_change_ok @@ -221,12 +219,33 @@ class PreferencesWindow(QtGui.QDialog): """ new_password = self.ui.leNewPassword.text() logger.debug("SRP password changed successfully.") - try: - self._soledad.change_passphrase(new_password) - logger.debug("Soledad password changed successfully.") - except NoStorageSecret: - logger.debug( - "No storage secret for password change in Soledad.") + self._backend.soledad_change_password(new_password) + + @QtCore.Slot(unicode) + def _srp_change_password_problem(self, msg): + """ + TRIGGERS: + self._backend.signaler.srp_password_change_error + self._backend.signaler.srp_password_change_badpw + + Callback used to display an error on changing password. + + :param msg: the message to show to the user. + :type msg: unicode + """ + logger.error("Error changing password") + self._set_password_change_status(msg, error=True) + self._set_changing_password(False) + + @QtCore.Slot() + def _soledad_change_password_ok(self): + """ + TRIGGERS: + Signaler.soledad_password_change_ok + + Callback used to display a successfully changed password. + """ + logger.debug("Soledad password changed successfully.") self._set_password_change_status( self.tr("Password changed successfully."), success=True) @@ -234,18 +253,17 @@ class PreferencesWindow(QtGui.QDialog): self._set_changing_password(False) @QtCore.Slot(unicode) - def _change_password_problem(self, msg): + def _soledad_change_password_problem(self, msg): """ TRIGGERS: - self._backend.signaler.srp_password_change_error - self._backend.signaler.srp_password_change_badpw + Signaler.soledad_password_change_error Callback used to display an error on changing password. :param msg: the message to show to the user. :type msg: unicode """ - logger.error("Error changing password") + logger.error("Error changing soledad password") self._set_password_change_status(msg, error=True) self._set_changing_password(False) @@ -418,12 +436,18 @@ class PreferencesWindow(QtGui.QDialog): sig.srp_status_logged_in.connect(self._is_logged_in) sig.srp_status_not_logged_in.connect(self._not_logged_in) - sig.srp_password_change_ok.connect(self._change_password_ok) + sig.srp_password_change_ok.connect(self._srp_change_password_ok) - pwd_change_error = lambda: self._change_password_problem( + pwd_change_error = lambda: self._srp_change_password_problem( self.tr("There was a problem changing the password.")) sig.srp_password_change_error.connect(pwd_change_error) - pwd_change_badpw = lambda: self._change_password_problem( + pwd_change_badpw = lambda: self._srp_change_password_problem( self.tr("You did not enter a correct current password.")) sig.srp_password_change_badpw.connect(pwd_change_badpw) + + sig.soledad_password_change_ok.connect( + self._soledad_change_password_ok) + + sig.soledad_password_change_error.connect( + self._soledad_change_password_problem) -- cgit v1.2.3 From 10cf84e5f8be978574b7a7e1a145903b37801753 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 16 May 2014 16:17:14 -0300 Subject: Move waiting logic for imap stop to the backend. Also, improve quit and cleanup calls. --- src/leap/bitmask/backend.py | 40 +++++++---- src/leap/bitmask/gui/mainwindow.py | 107 +++++++++++++--------------- src/leap/bitmask/services/mail/conductor.py | 15 ++-- 3 files changed, 87 insertions(+), 75 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 49a5ca0f..9661bda0 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -24,6 +24,7 @@ import time from functools import partial from Queue import Queue, Empty +from threading import Condition from twisted.internet import reactor from twisted.internet import threads, defer @@ -698,6 +699,8 @@ class Mail(object): """ Interfaces with setup and launch of Mail. """ + # We give each service some time to come to a halt before forcing quit + SERVICE_STOP_TIMEOUT = 20 zope.interface.implements(ILEAPComponent) @@ -764,19 +767,25 @@ class Mail(object): """ return threads.deferToThread(self._smtp_bootstrapper.stop_smtp_service) - def stop_imap_service(self, cv): + def _stop_imap_service(self): """ - Stop imap service (fetcher, factory and port). + Stop imap and wait until the service is stopped to signal that is done. + """ + cv = Condition() + cv.acquire() + threads.deferToThread(self._imap_controller.stop_imap_service, cv) + logger.debug('Waiting for imap service to stop.') + cv.wait(self.SERVICE_STOP_TIMEOUT) + self._signaler.signal(self._signaler.IMAP_STOPPED) - :param cv: A condition variable to which we can signal when imap - indeed stops. - :type cv: threading.Condition + def stop_imap_service(self): + """ + Stop imap service (fetcher, factory and port). :returns: a defer to interact with. :rtype: twisted.internet.defer.Deferred """ - return threads.deferToThread( - self._imap_controller.stop_imap_service, cv) + return threads.deferToThread(self._stop_imap_service) class Authenticate(object): @@ -796,6 +805,7 @@ class Authenticate(object): """ self.key = "authenticate" self._signaler = signaler + self._login_defer = None self._srp_auth = SRPAuth(ProviderConfig(), self._signaler) def login(self, domain, username, password): @@ -982,6 +992,9 @@ class Signaler(QtCore.QObject): soledad_password_change_ok = QtCore.Signal(object) soledad_password_change_error = QtCore.Signal(object) + # mail related signals + imap_stopped = QtCore.Signal(object) + # This signal is used to warn the backend user that is doing something # wrong backend_bad_call = QtCore.Signal(object) @@ -1062,6 +1075,8 @@ class Signaler(QtCore.QObject): SOLEDAD_CANCELLED_BOOTSTRAP = "soledad_cancelled_bootstrap" + IMAP_STOPPED = "imap_stopped" + BACKEND_BAD_CALL = "backend_bad_call" def __init__(self): @@ -1143,6 +1158,8 @@ class Signaler(QtCore.QObject): self.SOLEDAD_PASSWORD_CHANGE_OK, self.SOLEDAD_PASSWORD_CHANGE_ERROR, + self.IMAP_STOPPED, + self.BACKEND_BAD_CALL, ] @@ -1652,15 +1669,14 @@ class Backend(object): """ self._call_queue.put(("mail", "stop_smtp_service", None)) - def stop_imap_service(self, cv): + def stop_imap_service(self): """ Stop imap service. - :param cv: A condition variable to which we can signal when imap - indeed stops. - :type cv: threading.Condition + Signals: + imap_stopped """ - self._call_queue.put(("mail", "stop_imap_service", None, cv)) + self._call_queue.put(("mail", "stop_imap_service", None)) ########################################################################### # XXX HACK: this section is meant to be a place to hold methods and diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index e1527bbe..557f3828 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -21,7 +21,6 @@ import logging import socket import time -from threading import Condition from datetime import datetime from PySide import QtCore, QtGui @@ -93,10 +92,10 @@ class MainWindow(QtGui.QMainWindow): user_stopped_eip = False # We give EIP some time to come up before starting soledad anyway - EIP_TIMEOUT = 60000 # in milliseconds + EIP_START_TIMEOUT = 60000 # in milliseconds - # We give each service some time to come to a halt before forcing quit - SERVICE_STOP_TIMEOUT = 20 + # We give the services some time to a halt before forcing quit. + SERVICES_STOP_TIMEOUT = 20 def __init__(self, quit_callback, bypass_checks=False, start_hidden=False): """ @@ -262,9 +261,6 @@ class MainWindow(QtGui.QMainWindow): # XXX should connect to mail_conductor.start_mail_service instead self.soledad_ready.connect(self._start_smtp_bootstrapping) self.soledad_ready.connect(self._start_imap_service) - self.logout.connect(self._stop_imap_service) - self.logout.connect(self._stop_smtp_service) - ################################# end Qt Signals connection ######## init_platform() @@ -282,6 +278,8 @@ class MainWindow(QtGui.QMainWindow): self._mail_conductor = mail_conductor.MailConductor(self._backend) self._mail_conductor.connect_mail_signals(self._mail_status) + self.logout.connect(self._mail_conductor.stop_mail_services) + # Eip machine is a public attribute where the state machine for # the eip connection will be available to the different components. # Remember that this will not live in the +1600LOC mainwindow for @@ -1441,17 +1439,6 @@ class MainWindow(QtGui.QMainWindow): if self._provides_mx_and_enabled(): self._mail_conductor.start_smtp_service(download_if_needed=True) - # XXX --- should remove from here, and connecte directly to the state - # machine. - @QtCore.Slot() - def _stop_smtp_service(self): - """ - TRIGGERS: - self.logout - """ - # TODO call stop_mail_service - self._mail_conductor.stop_smtp_service() - ################################################################### # Service control methods: imap @@ -1478,20 +1465,6 @@ class MainWindow(QtGui.QMainWindow): if self._provides_mx_and_enabled(): start_fun() - @QtCore.Slot() - def _stop_imap_service(self): - """ - TRIGGERS: - self.logout - """ - cv = Condition() - cv.acquire() - # TODO call stop_mail_service - threads.deferToThread(self._mail_conductor.stop_imap_service, cv) - # and wait for it to be stopped - logger.debug('Waiting for imap service to stop.') - cv.wait(self.SERVICE_STOP_TIMEOUT) - # end service control methods (imap) ################################################################### @@ -1840,7 +1813,7 @@ class MainWindow(QtGui.QMainWindow): # we want to start soledad anyway after a certain timeout if eip # fails to come up QtCore.QTimer.singleShot( - self.EIP_TIMEOUT, + self.EIP_START_TIMEOUT, self._maybe_run_soledad_setup_checks) else: if not self._already_started_eip: @@ -2015,25 +1988,20 @@ class MainWindow(QtGui.QMainWindow): # cleanup and quit methods # - def _cleanup_pidfiles(self): + def _stop_services(self): """ - Removes lockfiles on a clean shutdown. - - Triggered after aboutToQuit signal. + Stop services and cancel ongoing actions (if any). """ - if IS_WIN: - WindowsLock.release_all_locks() + logger.debug('About to quit, doing cleanup.') - def _cleanup_and_quit(self): - """ - Call all the cleanup actions in a serialized way. - Should be called from the quit function. - """ - logger.debug('About to quit, doing cleanup...') + self._cancel_ongoing_defers() - self._stop_imap_service() + logger.debug('Stopping mail services') + self._backend.stop_imap_service() + self._backend.stop_smtp_service() if self._logged_user is not None: + logger.debug("Doing logout") self._backend.logout() logger.debug('Terminating vpn') @@ -2042,7 +2010,8 @@ class MainWindow(QtGui.QMainWindow): # We need to give some time to the ongoing signals for shutdown # to come into action. This needs to be solved using # back-communication from backend. - QtCore.QTimer.singleShot(3000, self._shutdown) + # TODO: handle this, I commented this fix to merge 'cleanly' + # QtCore.QTimer.singleShot(3000, self._shutdown) def _shutdown(self): """ @@ -2061,7 +2030,8 @@ class MainWindow(QtGui.QMainWindow): def quit(self): """ - Cleanup and tidely close the main window before quitting. + Start the quit sequence and wait for services to finish. + Cleanup and close the main window before quitting. """ # TODO separate the shutting down of services from the # UI stuff. @@ -2074,22 +2044,45 @@ class MainWindow(QtGui.QMainWindow): self.tr('The app is quitting, please wait.')) # explicitly process events to display tooltip immediately - QtCore.QCoreApplication.processEvents() + QtCore.QCoreApplication.processEvents(0, 10) + + # Close other windows if any. + if self._wizard: + self._wizard.close() + + if self._logger_window: + self._logger_window.close() # Set this in case that the app is hidden QtGui.QApplication.setQuitOnLastWindowClosed(True) - self._cleanup_and_quit() + self._stop_services() - # 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: - self._wizard.close() + # call final_quit when imap is stopped + self._backend.signaler.imap_stopped.connect(self.final_quit) + # or if we reach the timeout + reactor.callLater(self.SERVICES_STOP_TIMEOUT, self._backend.stop) - if self._logger_window: - self._logger_window.close() + @QtCore.Slot() + def final_quit(self): + """ + Final steps to quit the app, starting from here we don't care about + running services or user interaction, just quitting. + """ + logger.debug('Final quit...') + + try: + # disconnect signal if we get here due a timeout. + self._backend.signaler.imap_stopped.disconnect(self.final_quit) + except RuntimeError: + pass # Signal was not connected + + # Remove lockfiles on a clean shutdown. + logger.debug('Cleaning pidfiles') + if IS_WIN: + WindowsLock.release_all_locks() + self._backend.stop() self.close() diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 67bc007e..87c4621e 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -66,18 +66,14 @@ class IMAPControl(object): """ self._backend.start_imap_service(self.userid, flags.OFFLINE) - def stop_imap_service(self, cv): + def stop_imap_service(self): """ Stop imap service. - - :param cv: A condition variable to which we can signal when imap - indeed stops. - :type cv: threading.Condition """ self.imap_connection.qtsigs.disconnecting_signal.emit() logger.debug('Stopping imap service.') - self._backend.stop_imap_service(cv) + self._backend.stop_imap_service() def _handle_imap_events(self, req): """ @@ -249,6 +245,13 @@ class MailConductor(IMAPControl, SMTPControl): self._smtp_machine = smtp self._smtp_machine.start() + def stop_mail_services(self): + """ + Stop the IMAP and SMTP services. + """ + self.stop_imap_service() + self.stop_smtp_service() + def connect_mail_signals(self, widget): """ Connects the mail signals to the mail_status widget slots. -- cgit v1.2.3 From baeb605e53c2e8a7dceee86035f6d4cc6b49e131 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 19 May 2014 17:51:58 -0300 Subject: Improve wait and quit process. Refactor logic from backend to the vpnprocess. --- src/leap/bitmask/app.py | 2 + src/leap/bitmask/backend.py | 29 ++++++++---- src/leap/bitmask/gui/mainwindow.py | 69 ++++++++++++++++++----------- src/leap/bitmask/services/eip/vpnprocess.py | 12 +++++ 4 files changed, 77 insertions(+), 35 deletions(-) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index e413ab4c..05f81f0b 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -76,6 +76,7 @@ def sigint_handler(*args, **kwargs): mainwindow = args[0] mainwindow.quit() + def sigterm_handler(*args, **kwargs): """ Signal handler for SIGTERM. @@ -87,6 +88,7 @@ def sigterm_handler(*args, **kwargs): mainwindow = args[0] mainwindow.quit() + def add_logger_handlers(debug=False, logfile=None, replace_stdout=True): """ Create the logger and attach the handlers. diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 9661bda0..b138fd8d 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -17,7 +17,6 @@ """ Backend for everything """ -import commands import logging import os import time @@ -391,14 +390,20 @@ class EIP(object): # TODO: are we connected here? signaler.signal(signaler.EIP_CONNECTED) - def stop(self, shutdown=False): + def _do_stop(self, shutdown=False): """ - Stop the service. + Stop the service. This is run in a thread to avoid blocking. """ self._vpn.terminate(shutdown) if IS_LINUX: self._wait_for_firewall_down() + def stop(self, shutdown=False): + """ + Stop the service. + """ + return threads.deferToThread(self._do_stop, shutdown) + def _wait_for_firewall_down(self): """ Wait for the firewall to come down. @@ -411,15 +416,16 @@ class EIP(object): MAX_FW_WAIT_RETRIES = 25 FW_WAIT_STEP = 0.5 - retry = 0 - - fw_up_cmd = "pkexec /usr/sbin/bitmask-root firewall isup" - fw_is_down = lambda: commands.getstatusoutput(fw_up_cmd)[0] == 256 + retry = 1 - while retry < MAX_FW_WAIT_RETRIES: - if fw_is_down(): + while retry <= MAX_FW_WAIT_RETRIES: + if self._vpn.is_fw_down(): + self._signaler.signal(self._signaler.EIP_STOPPED) return else: + msg = "Firewall is not down yet, waiting... {0} of {1}" + msg = msg.format(retry, MAX_FW_WAIT_RETRIES) + logger.debug(msg) time.sleep(FW_WAIT_STEP) retry += 1 logger.warning("After waiting, firewall is not down... " @@ -953,6 +959,7 @@ class Signaler(QtCore.QObject): eip_disconnected = QtCore.Signal(object) eip_connection_died = QtCore.Signal(object) eip_connection_aborted = QtCore.Signal(object) + eip_stopped = QtCore.Signal(object) # EIP problems eip_no_polkit_agent_error = QtCore.Signal(object) @@ -1040,6 +1047,8 @@ class Signaler(QtCore.QObject): EIP_DISCONNECTED = "eip_disconnected" EIP_CONNECTION_DIED = "eip_connection_died" EIP_CONNECTION_ABORTED = "eip_connection_aborted" + EIP_STOPPED = "eip_stopped" + 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" @@ -1110,6 +1119,8 @@ class Signaler(QtCore.QObject): self.EIP_DISCONNECTED, self.EIP_CONNECTION_DIED, self.EIP_CONNECTION_ABORTED, + self.EIP_STOPPED, + self.EIP_NO_POLKIT_AGENT_ERROR, self.EIP_NO_TUN_KEXT_ERROR, self.EIP_NO_PKEXEC_ERROR, diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 557f3828..7cc88c1f 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -19,7 +19,6 @@ Main window for Bitmask. """ import logging import socket -import time from datetime import datetime @@ -87,6 +86,7 @@ class MainWindow(QtGui.QMainWindow): raise_window = QtCore.Signal([]) soledad_ready = QtCore.Signal([]) logout = QtCore.Signal([]) + all_services_stopped = QtCore.Signal() # We use this flag to detect abnormal terminations user_stopped_eip = False @@ -195,6 +195,12 @@ class MainWindow(QtGui.QMainWindow): self._logged_user = None self._logged_in_offline = False + # Set used to track the services being stopped and need wait. + self._services_being_stopped = {} + + # timeout object used to trigger quit + self._quit_timeout_callater = None + self._backend_connected_signals = {} self._backend_connect() @@ -1996,6 +2002,14 @@ class MainWindow(QtGui.QMainWindow): self._cancel_ongoing_defers() + self._services_being_stopped = {'imap', 'eip'} + + imap_stopped = lambda: self._remove_service('imap') + self._backend.signaler.imap_stopped.connect(imap_stopped) + + eip_stopped = lambda: self._remove_service('eip') + self._backend.signaler.eip_stopped.connect(eip_stopped) + logger.debug('Stopping mail services') self._backend.stop_imap_service() self._backend.stop_smtp_service() @@ -2007,27 +2021,6 @@ class MainWindow(QtGui.QMainWindow): logger.debug('Terminating vpn') self._backend.stop_eip(shutdown=True) - # We need to give some time to the ongoing signals for shutdown - # to come into action. This needs to be solved using - # back-communication from backend. - # TODO: handle this, I commented this fix to merge 'cleanly' - # QtCore.QTimer.singleShot(3000, self._shutdown) - - def _shutdown(self): - """ - Actually shutdown. - """ - self._cancel_ongoing_defers() - - # TODO missing any more cancels? - - logger.debug('Cleaning pidfiles') - self._cleanup_pidfiles() - if self._quit_callback: - self._quit_callback() - - logger.debug('Bye.') - def quit(self): """ Start the quit sequence and wait for services to finish. @@ -2060,10 +2053,27 @@ class MainWindow(QtGui.QMainWindow): self._really_quit = True - # call final_quit when imap is stopped - self._backend.signaler.imap_stopped.connect(self.final_quit) + # call final quit when all the services are stopped + self.all_services_stopped.connect(self.final_quit) # or if we reach the timeout - reactor.callLater(self.SERVICES_STOP_TIMEOUT, self._backend.stop) + self._quit_timeout_callater = reactor.callLater( + self.SERVICES_STOP_TIMEOUT, self.final_quit) + + @QtCore.Slot() + def _remove_service(self, service): + """ + Remove the given service from the waiting list and check if we have + running services that we need to wait until we quit. + Emit self.all_services_stopped signal if we don't need to keep waiting. + + :param service: the service that we want to remove + :type service: str + """ + self._services_being_stopped.discard(service) + + if not self._services_being_stopped: + logger.debug("All services stopped.") + self.all_services_stopped.emit() @QtCore.Slot() def final_quit(self): @@ -2075,10 +2085,15 @@ class MainWindow(QtGui.QMainWindow): try: # disconnect signal if we get here due a timeout. - self._backend.signaler.imap_stopped.disconnect(self.final_quit) + self.all_services_stopped.disconnect(self.final_quit) except RuntimeError: pass # Signal was not connected + # Cancel timeout to avoid being called if we reached here through the + # signal + if self._quit_timeout_callater.active(): + self._quit_timeout_callater.cancel() + # Remove lockfiles on a clean shutdown. logger.debug('Cleaning pidfiles') if IS_WIN: @@ -2086,3 +2101,5 @@ class MainWindow(QtGui.QMainWindow): self._backend.stop() self.close() + + reactor.callLater(1, self._quit_callback) diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 734b88df..81eac6d9 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -17,6 +17,7 @@ """ VPN Manager, spawned in a custom processProtocol. """ +import commands import logging import os import shutil @@ -232,6 +233,17 @@ class VPN(object): BM_ROOT, "firewall", "start"] + gateways) return True if exitCode is 0 else False + def is_fw_down(self): + """ + Return whether the firewall is down or not. + + :rtype: bool + """ + BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT + fw_up_cmd = "pkexec {0} firewall isup".format(BM_ROOT) + fw_is_down = lambda: commands.getstatusoutput(fw_up_cmd)[0] == 256 + return fw_is_down() + def _tear_down_firewall(self): """ Tear the firewall down using the privileged wrapper. -- cgit v1.2.3 From 4a055fdfd26ff8d3aaaaeeb60924a9ba5fc973f6 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 20 May 2014 16:11:38 -0300 Subject: Replace twisted logs with logging logs. --- src/leap/bitmask/backend.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index b138fd8d..96d0b0db 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -1262,7 +1262,7 @@ class Backend(object): """ Starts the looping call """ - log.msg("Starting worker...") + logger.debug("Starting worker...") self._lc.start(0.01) def stop(self): @@ -1275,14 +1275,17 @@ class Backend(object): """ Delayed stopping of worker. Called from `stop`. """ - log.msg("Stopping worker...") + logger.debug("Stopping worker...") if self._lc.running: self._lc.stop() else: logger.warning("Looping call is not running, cannot stop") + + logger.debug("Cancelling ongoing defers...") while len(self._ongoing_defers) > 0: d = self._ongoing_defers.pop() d.cancel() + logger.debug("Defers cancelled.") def _register(self, component): """ @@ -1296,8 +1299,7 @@ class Backend(object): try: self._components[component.key] = component except Exception: - log.msg("There was a problem registering %s" % (component,)) - log.err() + logger.error("There was a problem registering %s" % (component,)) def _signal_back(self, _, signal): """ @@ -1325,19 +1327,19 @@ class Backend(object): # A call might not have a callback signal, but if it does, # we add it to the chain if cmd[2] is not None: - d.addCallbacks(self._signal_back, log.err, cmd[2]) - d.addCallbacks(self._done_action, log.err, + d.addCallbacks(self._signal_back, logger.error, cmd[2]) + d.addCallbacks(self._done_action, logger.error, callbackKeywords={"d": d}) - d.addErrback(log.err) + d.addErrback(logger.error) self._ongoing_defers.append(d) except Empty: # If it's just empty we don't have anything to do. pass except defer.CancelledError: logger.debug("defer cancelled somewhere (CancelledError).") - except Exception: + except Exception as e: # But we log the rest - log.err() + logger.exception("Unexpected exception: {0!r}".format(e)) def _done_action(self, _, d): """ -- cgit v1.2.3 From 0e34d622bf133a78132cf58e60d52b0c2511e408 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 20 May 2014 16:15:01 -0300 Subject: Add changes files for #5481. --- changes/refactor-mail-soledad | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changes/refactor-mail-soledad diff --git a/changes/refactor-mail-soledad b/changes/refactor-mail-soledad new file mode 100644 index 00000000..32b1ab5b --- /dev/null +++ b/changes/refactor-mail-soledad @@ -0,0 +1,5 @@ +- Improve wait and quit process. +- Move soledad password change to backend. +- Move Mail logic to backend. +- Separate imap/smtp logic from conductor. +- Refactor SoledadBootstrapper to backend. Closes #5481. -- cgit v1.2.3 From a61889110118d04703b023936048b44517947516 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 21 May 2014 13:58:26 -0300 Subject: Rename backend methods for consistency. --- src/leap/bitmask/backend.py | 40 ++++++++++++++--------------- src/leap/bitmask/gui/mainwindow.py | 36 +++++++++++++------------- src/leap/bitmask/gui/preferenceswindow.py | 4 +-- src/leap/bitmask/gui/wizard.py | 4 +-- src/leap/bitmask/services/mail/conductor.py | 12 ++++----- 5 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 96d0b0db..0ab7040b 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -1356,7 +1356,7 @@ class Backend(object): # this in two processes, the methods bellow can be changed to # send_multipart and this backend class will be really simple. - def setup_provider(self, provider): + def provider_setup(self, provider): """ Initiate the setup for a provider. @@ -1372,7 +1372,7 @@ class Backend(object): """ self._call_queue.put(("provider", "setup_provider", None, provider)) - def cancel_setup_provider(self): + def provider_cancel_setup(self): """ Cancel the ongoing setup provider (if any). """ @@ -1393,7 +1393,7 @@ class Backend(object): """ self._call_queue.put(("provider", "bootstrap", None, provider)) - def register_user(self, provider, username, password): + def user_register(self, provider, username, password): """ Register a user using the domain and password given as parameters. @@ -1412,7 +1412,7 @@ class Backend(object): self._call_queue.put(("register", "register_user", None, provider, username, password)) - def setup_eip(self, provider, skip_network=False): + def eip_setup(self, provider, skip_network=False): """ Initiate the setup for a provider @@ -1430,13 +1430,13 @@ class Backend(object): self._call_queue.put(("eip", "setup_eip", None, provider, skip_network)) - def cancel_setup_eip(self): + def eip_cancel_setup(self): """ Cancel the ongoing setup EIP (if any). """ self._call_queue.put(("eip", "cancel_setup_eip", None)) - def start_eip(self): + def eip_start(self): """ Start the EIP service. @@ -1460,7 +1460,7 @@ class Backend(object): """ self._call_queue.put(("eip", "start", None)) - def stop_eip(self, shutdown=False): + def eip_stop(self, shutdown=False): """ Stop the EIP service. @@ -1469,7 +1469,7 @@ class Backend(object): """ self._call_queue.put(("eip", "stop", None, shutdown)) - def terminate_eip(self): + def eip_terminate(self): """ Terminate the EIP service, not necessarily in a nice way. """ @@ -1521,7 +1521,7 @@ class Backend(object): self._call_queue.put(("eip", "can_start", None, domain)) - def login(self, provider, username, password): + def user_login(self, provider, username, password): """ Execute the whole authentication process for a user @@ -1543,7 +1543,7 @@ class Backend(object): self._call_queue.put(("authenticate", "login", None, provider, username, password)) - def logout(self): + def user_logout(self): """ Log out the current session. @@ -1554,13 +1554,13 @@ class Backend(object): """ self._call_queue.put(("authenticate", "logout", None)) - def cancel_login(self): + def user_cancel_login(self): """ Cancel the ongoing login (if any). """ self._call_queue.put(("authenticate", "cancel_login", None)) - def auth_change_password(self, current_password, new_password): + def user_change_password(self, current_password, new_password): """ Change the user's password. @@ -1594,7 +1594,7 @@ class Backend(object): self._call_queue.put(("soledad", "change_password", None, new_password)) - def get_logged_in_status(self): + def user_get_logged_in_status(self): """ Signal if the user is currently logged in or not. @@ -1623,7 +1623,7 @@ class Backend(object): self._call_queue.put(("soledad", "bootstrap", None, username, domain, password)) - def load_offline_soledad(self, username, password, uuid): + def soledad_load_offline(self, username, password, uuid): """ Load the soledad database in offline mode. @@ -1639,19 +1639,19 @@ class Backend(object): self._call_queue.put(("soledad", "load_offline", None, username, password, uuid)) - def cancel_soledad_bootstrap(self): + def soledad_cancel_bootstrap(self): """ Cancel the ongoing soledad bootstrapping process (if any). """ self._call_queue.put(("soledad", "cancel_bootstrap", None)) - def close_soledad(self): + def soledad_close(self): """ Close soledad database. """ self._call_queue.put(("soledad", "close", None)) - def start_smtp_service(self, full_user_id, download_if_needed=False): + def smtp_start_service(self, full_user_id, download_if_needed=False): """ Start the SMTP service. @@ -1664,7 +1664,7 @@ class Backend(object): self._call_queue.put(("mail", "start_smtp_service", None, full_user_id, download_if_needed)) - def start_imap_service(self, full_user_id, offline=False): + def imap_start_service(self, full_user_id, offline=False): """ Start the IMAP service. @@ -1676,13 +1676,13 @@ class Backend(object): self._call_queue.put(("mail", "start_imap_service", None, full_user_id, offline)) - def stop_smtp_service(self): + def smtp_stop_service(self): """ Stop the SMTP service. """ self._call_queue.put(("mail", "stop_smtp_service", None)) - def stop_imap_service(self): + def imap_stop_service(self): """ Stop imap service. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 7cc88c1f..fc4b4d75 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -653,7 +653,7 @@ class MainWindow(QtGui.QMainWindow): # If we don't want to start eip, we leave everything # initialized to quickly start it if not self._trying_to_start_eip: - self._backend.setup_eip(default_provider, skip_network=True) + self._backend.eip_setup(default_provider, skip_network=True) def _backend_can_start_eip(self): """ @@ -1167,7 +1167,7 @@ class MainWindow(QtGui.QMainWindow): """ # XXX should rename this provider, name clash. provider = self._login_widget.get_selected_provider() - self._backend.setup_provider(provider) + self._backend.provider_setup(provider) @QtCore.Slot(dict) def _load_provider_config(self, data): @@ -1261,10 +1261,10 @@ class MainWindow(QtGui.QMainWindow): Cancel the running defers to avoid app blocking. """ # XXX: Should we stop all the backend defers? - self._backend.cancel_setup_provider() - self._backend.cancel_login() - self._backend.cancel_soledad_bootstrap() - self._backend.close_soledad() + self._backend.provider_cancel_setup() + self._backend.user_cancel_login() + self._backend.soledad_cancel_bootstrap() + self._backend.soledad_close() self._soledad_started = False @@ -1273,7 +1273,7 @@ class MainWindow(QtGui.QMainWindow): """ TRIGGERS: Signaler.prov_cancelled_setup fired by - self._backend.cancel_setup_provider() + self._backend.provider_cancel_setup() This method re-enables the login widget and display a message for the cancelled operation. @@ -1299,7 +1299,7 @@ class MainWindow(QtGui.QMainWindow): self._show_hide_unsupported_services() domain = self._provider_config.get_domain() - self._backend.login(domain, username, password) + self._backend.user_login(domain, username, password) else: logger.error(data[self._backend.ERROR_KEY]) self._login_problem_provider() @@ -1402,7 +1402,7 @@ class MainWindow(QtGui.QMainWindow): # this is mostly for internal use/debug for now. logger.warning("Sorry! Log-in at least one time.") return - self._backend.load_offline_soledad(full_user_id, password, uuid) + self._backend.soledad_load_offline(full_user_id, password, uuid) else: if self._logged_user is not None: domain = self._provider_config.get_domain() @@ -1609,7 +1609,7 @@ class MainWindow(QtGui.QMainWindow): # won't try the next time. self._settings.set_autostart_eip(True) - self._backend.start_eip() + self._backend.eip_start() @QtCore.Slot() def _on_eip_connection_aborted(self): @@ -1692,7 +1692,7 @@ class MainWindow(QtGui.QMainWindow): :type abnormal: bool """ self.user_stopped_eip = True - self._backend.stop_eip() + self._backend.eip_stop() self._set_eipstatus_off(False) self._already_started_eip = False @@ -1783,7 +1783,7 @@ class MainWindow(QtGui.QMainWindow): eip_status_label = eip_status_label.format(self._eip_name) self._eip_status.set_eip_status(eip_status_label, error=True) signal = qtsigs.connection_aborted_signal - self._backend.terminate_eip() + self._backend.eip_terminate() elif exitCode != 0 or not self.user_stopped_eip: eip_status_label = self.tr("{0} finished in an unexpected manner!") @@ -1813,7 +1813,7 @@ class MainWindow(QtGui.QMainWindow): self.tr("Starting...")) domain = self._login_widget.get_selected_provider() - self._backend.setup_eip(domain) + self._backend.eip_setup(domain) self._already_started_eip = True # we want to start soledad anyway after a certain timeout if eip @@ -1910,7 +1910,7 @@ class MainWindow(QtGui.QMainWindow): # XXX: If other defers are doing authenticated stuff, this # might conflict with those. CHECK! - self._backend.logout() + self._backend.user_logout() self.logout.emit() @QtCore.Slot() @@ -2011,15 +2011,15 @@ class MainWindow(QtGui.QMainWindow): self._backend.signaler.eip_stopped.connect(eip_stopped) logger.debug('Stopping mail services') - self._backend.stop_imap_service() - self._backend.stop_smtp_service() + self._backend.imap_stop_service() + self._backend.smtp_stop_service() if self._logged_user is not None: logger.debug("Doing logout") - self._backend.logout() + self._backend.user_logout() logger.debug('Terminating vpn') - self._backend.stop_eip(shutdown=True) + self._backend.eip_stop(shutdown=True) def quit(self): """ diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 77a3994f..47011a85 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -87,7 +87,7 @@ class PreferencesWindow(QtGui.QDialog): else: self._add_configured_providers() - self._backend.get_logged_in_status() + self._backend.user_get_logged_in_status() self._select_provider_by_name(domain) @@ -207,7 +207,7 @@ class PreferencesWindow(QtGui.QDialog): return self._set_changing_password(True) - self._backend.auth_change_password(current_password, new_password) + self._backend.user_change_password(current_password, new_password) @QtCore.Slot() def _srp_change_password_ok(self): diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 020a58e2..316b2bff 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -257,7 +257,7 @@ class Wizard(QtGui.QWizard): if ok: self._set_register_status(self.tr("Starting registration...")) - self._backend.register_user(self._domain, username, password) + self._backend.user_register(self._domain, username, password) self._username = username self._password = password else: @@ -406,7 +406,7 @@ class Wizard(QtGui.QWizard): self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) self._provider_select_defer = self._backend.\ - setup_provider(self._domain) + provider_setup(self._domain) @QtCore.Slot(bool) def _skip_provider_checks(self, skip): diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 87c4621e..7fc19f1c 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -64,7 +64,7 @@ class IMAPControl(object): """ Start imap service. """ - self._backend.start_imap_service(self.userid, flags.OFFLINE) + self._backend.imap_start_service(self.userid, flags.OFFLINE) def stop_imap_service(self): """ @@ -73,7 +73,7 @@ class IMAPControl(object): self.imap_connection.qtsigs.disconnecting_signal.emit() logger.debug('Stopping imap service.') - self._backend.stop_imap_service() + self._backend.imap_stop_service() def _handle_imap_events(self, req): """ @@ -146,14 +146,14 @@ class SMTPControl(object): :type download_if_needed: bool """ self.smtp_connection.qtsigs.connecting_signal.emit() - self._backend.start_smtp_service(self.userid, download_if_needed) + self._backend.smtp_start_service(self.userid, download_if_needed) def stop_smtp_service(self): """ Stops the SMTP service. """ self.smtp_connection.qtsigs.disconnecting_signal.emit() - self._backend.stop_smtp_service() + self._backend.smtp_stop_service() def _handle_smtp_events(self, req): """ @@ -249,8 +249,8 @@ class MailConductor(IMAPControl, SMTPControl): """ Stop the IMAP and SMTP services. """ - self.stop_imap_service() - self.stop_smtp_service() + self.imap_stop_service() + self.smtp_stop_service() def connect_mail_signals(self, widget): """ -- cgit v1.2.3 From d0a9ecf0cba998b44a216c5cdf1800eea152f379 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 21 May 2014 18:18:58 -0300 Subject: Update username regex to support the same as webapp. Rename password util to credentials and add a username check helper. Move the username regexp to the credentials module. Closes #5695. --- changes/bug-5695_fix-username-regex-support | 3 ++ src/leap/bitmask/gui/login.py | 5 +- src/leap/bitmask/gui/preferenceswindow.py | 4 +- src/leap/bitmask/gui/wizard.py | 25 ++++++--- src/leap/bitmask/util/credentials.py | 81 +++++++++++++++++++++++++++++ src/leap/bitmask/util/password.py | 58 --------------------- 6 files changed, 106 insertions(+), 70 deletions(-) create mode 100644 changes/bug-5695_fix-username-regex-support create mode 100644 src/leap/bitmask/util/credentials.py delete mode 100644 src/leap/bitmask/util/password.py diff --git a/changes/bug-5695_fix-username-regex-support b/changes/bug-5695_fix-username-regex-support new file mode 100644 index 00000000..855ec149 --- /dev/null +++ b/changes/bug-5695_fix-username-regex-support @@ -0,0 +1,3 @@ +- Update username regex to support the same as webapp. Closes #5965. +- Wrong error message for username too short. (Bug #5697) +- Cleanup and refactor username/password validators. diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index ac7ad878..f66e71d9 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -24,6 +24,7 @@ from ui_login import Ui_LoginWidget from leap.bitmask.config import flags from leap.bitmask.util import make_address +from leap.bitmask.util.credentials import USERNAME_REGEX from leap.bitmask.util.keyring_helpers import has_keyring from leap.bitmask.util.keyring_helpers import get_keyring from leap.common.check import leap_assert_type @@ -48,8 +49,6 @@ class LoginWidget(QtGui.QWidget): MAX_STATUS_WIDTH = 40 - BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$" - # Keyring KEYRING_KEY = "bitmask" @@ -87,7 +86,7 @@ class LoginWidget(QtGui.QWidget): self.ui.btnLogout.clicked.connect( self.logout) - username_re = QtCore.QRegExp(self.BARE_USERNAME_REGEX) + username_re = QtCore.QRegExp(USERNAME_REGEX) self.ui.lnUser.setValidator( QtGui.QRegExpValidator(username_re, self)) diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 47011a85..0a4c7f56 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -27,7 +27,7 @@ from PySide import QtCore, QtGui from leap.bitmask.provider import get_provider_path from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.gui.ui_preferences import Ui_Preferences -from leap.bitmask.util.password import basic_password_checks +from leap.bitmask.util.credentials import password_checks from leap.bitmask.services import get_supported from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.services import get_service_display_name, MX_SERVICE @@ -198,7 +198,7 @@ class PreferencesWindow(QtGui.QDialog): new_password = self.ui.leNewPassword.text() new_password2 = self.ui.leNewPassword2.text() - ok, msg = basic_password_checks(username, new_password, new_password2) + ok, msg = password_checks(username, new_password, new_password2) if not ok: self._set_changing_password(False) diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 316b2bff..ce45b431 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -29,8 +29,9 @@ from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.provider import get_provider_path from leap.bitmask.services import get_service_display_name, get_supported +from leap.bitmask.util.credentials import password_checks, username_checks +from leap.bitmask.util.credentials import USERNAME_REGEX from leap.bitmask.util.keyring_helpers import has_keyring -from leap.bitmask.util.password import basic_password_checks from ui_wizard import Ui_Wizard @@ -49,8 +50,6 @@ class Wizard(QtGui.QWizard): REGISTER_USER_PAGE = 4 SERVICES_PAGE = 5 - BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$" - def __init__(self, backend, bypass_checks=False): """ Constructor for the main Wizard. @@ -118,7 +117,7 @@ class Wizard(QtGui.QWizard): self.ui.rbExistingProvider.toggled.connect(self._skip_provider_checks) - usernameRe = QtCore.QRegExp(self.BARE_USERNAME_REGEX) + usernameRe = QtCore.QRegExp(USERNAME_REGEX) self.ui.lblUser.setValidator( QtGui.QRegExpValidator(usernameRe, self)) @@ -231,6 +230,12 @@ class Wizard(QtGui.QWizard): if reset: self._reset_provider_check() + def _focus_username(self): + """ + Focus at the username lineedit for the registration page + """ + self.ui.lblUser.setFocus() + def _focus_password(self): """ Focuses at the password lineedit for the registration page @@ -253,16 +258,22 @@ class Wizard(QtGui.QWizard): password = self.ui.lblPassword.text() password2 = self.ui.lblPassword2.text() - ok, msg = basic_password_checks(username, password, password2) - if ok: + user_ok, msg = username_checks(username) + if user_ok: + pass_ok, msg = password_checks(username, password, password2) + + if user_ok and pass_ok: self._set_register_status(self.tr("Starting registration...")) self._backend.user_register(self._domain, username, password) self._username = username self._password = password else: + if user_ok: + self._focus_password() + else: + self._focus_username() self._set_register_status(msg, error=True) - self._focus_password() self.ui.btnRegister.setEnabled(True) def _set_registration_fields_visibility(self, visible): diff --git a/src/leap/bitmask/util/credentials.py b/src/leap/bitmask/util/credentials.py new file mode 100644 index 00000000..a661bfb0 --- /dev/null +++ b/src/leap/bitmask/util/credentials.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# credentials.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 . + +""" +Credentials utilities +""" +from PySide import QtCore, QtGui + +WEAK_PASSWORDS = ("123456", "qweasd", "qwerty", "password") +USERNAME_REGEX = r"^[A-Za-z][A-Za-z\d_\-\.]+[A-Za-z]$" + +USERNAME_VALIDATOR = QtGui.QRegExpValidator(QtCore.QRegExp(USERNAME_REGEX)) + + +def username_checks(username): + # translation helper + _tr = QtCore.QObject().tr + + message = None + + if message is None and len(username) < 2: + message = _tr("Username must have at least 2 characters") + + valid = USERNAME_VALIDATOR.validate(username, 0) + valid_username = valid[0] == QtGui.QValidator.State.Acceptable + if message is None and not valid_username: + message = _tr("Invalid username") + + return message is None, message + + +def password_checks(username, password, password2): + """ + Performs basic password checks to avoid really easy passwords. + + :param username: username provided at the registrarion form + :type username: str + :param password: password from the registration form + :type password: str + :param password2: second password from the registration form + :type password: str + + :returns: True and empty message if all the checks pass, + False and an error message otherwise + :rtype: tuple(bool, str) + """ + # translation helper + _tr = QtCore.QObject().tr + + message = None + + if message is None and password != password2: + message = _tr("Passwords don't match") + + if message is None and not password: + message = _tr("You can't use an empty password") + + if message is None and len(password) < 6: + message = _tr("Password too short") + + if message is None and password in WEAK_PASSWORDS: + message = _tr("Password too easy") + + if message is None and username == password: + message = _tr("Password equal to username") + + return message is None, message diff --git a/src/leap/bitmask/util/password.py b/src/leap/bitmask/util/password.py deleted file mode 100644 index 73659f0d..00000000 --- a/src/leap/bitmask/util/password.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -# password.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 . - -""" -Password utilities -""" -from PySide import QtCore - -WEAK_PASSWORDS = ("123456", "qweasd", "qwerty", "password") - - -def basic_password_checks(username, password, password2): - """ - Performs basic password checks to avoid really easy passwords. - - :param username: username provided at the registrarion form - :type username: str - :param password: password from the registration form - :type password: str - :param password2: second password from the registration form - :type password: str - - :returns: True and empty message if all the checks pass, - False and an error message otherwise - :rtype: tuple(bool, str) - """ - # translation helper - _tr = QtCore.QObject().tr - - message = None - - if message is None and password != password2: - message = _tr("Passwords don't match") - - if message is None and len(password) < 6: - message = _tr("Password too short") - - if message is None and password in WEAK_PASSWORDS: - message = _tr("Password too easy") - - if message is None and username == password: - message = _tr("Password equal to username") - - return message is None, message -- cgit v1.2.3 From 100848992f96f8edd32433dd8f5efc7dc1230079 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 19 May 2014 11:11:31 -0500 Subject: do not tear fw down during restarts --- src/leap/bitmask/backend.py | 17 ++++++++++------- src/leap/bitmask/gui/mainwindow.py | 8 ++++---- src/leap/bitmask/services/eip/vpnprocess.py | 26 +++++++++++++++----------- 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 0ab7040b..d6d5004f 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -390,19 +390,19 @@ class EIP(object): # TODO: are we connected here? signaler.signal(signaler.EIP_CONNECTED) - def _do_stop(self, shutdown=False): + def _do_stop(self, shutdown=False, restart=False): """ Stop the service. This is run in a thread to avoid blocking. """ - self._vpn.terminate(shutdown) + self._vpn.terminate(shutdown, restart) if IS_LINUX: self._wait_for_firewall_down() - def stop(self, shutdown=False): + def stop(self, shutdown=False, restart=False): """ Stop the service. """ - return threads.deferToThread(self._do_stop, shutdown) + return threads.deferToThread(self._do_stop, shutdown, restart) def _wait_for_firewall_down(self): """ @@ -1460,14 +1460,17 @@ class Backend(object): """ self._call_queue.put(("eip", "start", None)) - def eip_stop(self, shutdown=False): + def eip_stop(self, shutdown=False, restart=False): """ Stop the EIP service. - :param shutdown: + :param shutdown: whether this is the final shutdown. :type shutdown: bool + + :param restart: whether this is part of a restart. + :type restart: bool """ - self._call_queue.put(("eip", "stop", None, shutdown)) + self._call_queue.put(("eip", "stop", None, shutdown, restart)) def eip_terminate(self): """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index fc4b4d75..201a24ec 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1680,7 +1680,7 @@ class MainWindow(QtGui.QMainWindow): self._set_eipstatus_off() @QtCore.Slot() - def _stop_eip(self): + def _stop_eip(self, restart=False): """ TRIGGERS: self._eip_connection.qtsigs.do_disconnect_signal (via state machine) @@ -1691,8 +1691,8 @@ class MainWindow(QtGui.QMainWindow): :param abnormal: whether this was an abnormal termination. :type abnormal: bool """ - self.user_stopped_eip = True - self._backend.eip_stop() + self.user_stopped_eip = not restart + self._backend.eip_stop(restart=restart) self._set_eipstatus_off(False) self._already_started_eip = False @@ -1731,7 +1731,7 @@ class MainWindow(QtGui.QMainWindow): """ # for some reason, emitting the do_disconnect/do_connect # signals hangs the UI. - self._stop_eip() + self._stop_eip(restart=True) QtCore.QTimer.singleShot(2000, self._start_EIP) def _set_eipstatus_off(self, error=True): diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 81eac6d9..b068066f 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -300,19 +300,24 @@ class VPN(object): self._vpnproc.aborted = True self._vpnproc.killProcess() - def terminate(self, shutdown=False): + def terminate(self, shutdown=False, restart=False): """ Stops the openvpn subprocess. Attempts to send a SIGTERM first, and after a timeout it sends a SIGKILL. + + :param shutdown: whether this is the final shutdown + :type shutdown: bool + :param restart: whether this stop is part of a hard restart. + :type restart: bool """ from twisted.internet import reactor self._stop_pollers() - # We assume that the only valid shutodowns are initiated - # by an user action. - self._user_stopped = shutdown + # We assume that the only valid stops are initiated + # by an user action, not hard restarts + self._user_stopped = not restart # First we try to be polite and send a SIGTERM... if self._vpnproc: @@ -324,13 +329,12 @@ class VPN(object): reactor.callLater( self.TERMINATE_WAIT, self._kill_if_left_alive) - if shutdown: - if IS_LINUX and self._user_stopped: - firewall_down = self._tear_down_firewall() - if firewall_down: - logger.debug("Firewall down") - else: - logger.warning("Could not tear firewall down") + if IS_LINUX and self._user_stopped: + firewall_down = self._tear_down_firewall() + if firewall_down: + logger.debug("Firewall down") + else: + logger.warning("Could not tear firewall down") def _start_pollers(self): """ -- cgit v1.2.3 From f4ed7d8ab4401f6ef310118b037bc982ae73b75c Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 21 May 2014 18:38:12 -0500 Subject: changes file --- changes/bug_5687_switch-eip-off | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/bug_5687_switch-eip-off diff --git a/changes/bug_5687_switch-eip-off b/changes/bug_5687_switch-eip-off new file mode 100644 index 00000000..4cf0d24a --- /dev/null +++ b/changes/bug_5687_switch-eip-off @@ -0,0 +1 @@ +- Bring firewall down when switching EIP off. Closes: #5687 -- cgit v1.2.3 From 8b2a0b62bab158417babd0182d4dc00adf8ec521 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 23 May 2014 14:37:26 -0300 Subject: Allow usernames to end in a digit. --- changes/bug_allow-usernames-ending-with-digit | 1 + src/leap/bitmask/util/credentials.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changes/bug_allow-usernames-ending-with-digit diff --git a/changes/bug_allow-usernames-ending-with-digit b/changes/bug_allow-usernames-ending-with-digit new file mode 100644 index 00000000..de47e7ac --- /dev/null +++ b/changes/bug_allow-usernames-ending-with-digit @@ -0,0 +1 @@ +- Allow usernames to end in a digit. diff --git a/src/leap/bitmask/util/credentials.py b/src/leap/bitmask/util/credentials.py index a661bfb0..07ded17b 100644 --- a/src/leap/bitmask/util/credentials.py +++ b/src/leap/bitmask/util/credentials.py @@ -21,7 +21,7 @@ Credentials utilities from PySide import QtCore, QtGui WEAK_PASSWORDS = ("123456", "qweasd", "qwerty", "password") -USERNAME_REGEX = r"^[A-Za-z][A-Za-z\d_\-\.]+[A-Za-z]$" +USERNAME_REGEX = r"^[A-Za-z][A-Za-z\d_\-\.]+[A-Za-z\d]$" USERNAME_VALIDATOR = QtGui.QRegExpValidator(QtCore.QRegExp(USERNAME_REGEX)) -- cgit v1.2.3 From b77d7c5d2a27ee45b88e6c06152c9c06847e868f Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 22 May 2014 16:04:53 -0300 Subject: Fix method names. --- src/leap/bitmask/services/mail/conductor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 7fc19f1c..98b40929 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -249,8 +249,8 @@ class MailConductor(IMAPControl, SMTPControl): """ Stop the IMAP and SMTP services. """ - self.imap_stop_service() - self.smtp_stop_service() + self.stop_imap_service() + self.stop_smtp_service() def connect_mail_signals(self, widget): """ -- cgit v1.2.3 From 5aeb8cbfb85022dcf74a46d1881edee552e967be Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 27 May 2014 09:50:47 -0300 Subject: Remove unused code. --- src/leap/bitmask/config/providerconfig.py | 36 ------------------------------- 1 file changed, 36 deletions(-) diff --git a/src/leap/bitmask/config/providerconfig.py b/src/leap/bitmask/config/providerconfig.py index 2ebe05ce..b411c6f3 100644 --- a/src/leap/bitmask/config/providerconfig.py +++ b/src/leap/bitmask/config/providerconfig.py @@ -199,39 +199,3 @@ class ProviderConfig(BaseConfig): :rtype: bool """ return "mx" in self.get_services() - - -if __name__ == "__main__": - logger = logging.getLogger(name='leap') - logger.setLevel(logging.DEBUG) - console = logging.StreamHandler() - console.setLevel(logging.DEBUG) - formatter = logging.Formatter( - '%(asctime)s ' - '- %(name)s - %(levelname)s - %(message)s') - console.setFormatter(formatter) - logger.addHandler(console) - - provider = ProviderConfig() - - try: - provider.get_api_version() - except Exception as e: - assert isinstance(e, AssertionError), "Expected an assert" - print "Safe value getting is working" - - # standalone minitest - #if provider.load("provider_bad.json"): - if provider.load("leap/providers/bitmask.net/provider.json"): - print provider.get_api_version() - print provider.get_ca_cert_fingerprint() - print provider.get_ca_cert_uri() - print provider.get_default_language() - print provider.get_description() - print provider.get_description(lang="asd") - print provider.get_domain() - print provider.get_enrollment_policy() - print provider.get_languages() - print provider.get_name() - print provider.get_services() - print provider.get_services_string() -- cgit v1.2.3 From 37353a2c57a759e160a7060c412b15d30ebde4bb Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 22 May 2014 17:34:24 -0300 Subject: Refactor cleanup, ProviderConfig to backend. --- src/leap/bitmask/backend.py | 161 ++++++++++++---- src/leap/bitmask/config/providerconfig.py | 64 +++++-- src/leap/bitmask/gui/mainwindow.py | 207 +++++++++------------ src/leap/bitmask/gui/preferenceswindow.py | 114 ++++++------ src/leap/bitmask/gui/wizard.py | 73 ++++---- .../services/soledad/soledadbootstrapper.py | 3 +- 6 files changed, 360 insertions(+), 262 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index d6d5004f..e8bf0482 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -37,8 +37,8 @@ from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.crypto.srpregister import SRPRegister from leap.bitmask.platform_init import IS_LINUX -from leap.bitmask.provider import get_provider_path from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper +from leap.bitmask.services import get_supported from leap.bitmask.services.eip import eipconfig from leap.bitmask.services.eip import get_openvpn_management from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper @@ -64,26 +64,6 @@ from PySide import QtCore logger = logging.getLogger(__name__) -def get_provider_config(config, domain): - """ - Return the ProviderConfig object for the given domain. - If it is already loaded in `config`, then don't reload. - - :param config: a ProviderConfig object - :type conig: ProviderConfig - :param domain: the domain which config is required. - :type domain: unicode - - :returns: True if the config was loaded successfully, False otherwise. - :rtype: bool - """ - # TODO: see ProviderConfig.get_provider_config - if (not config.loaded() or config.get_domain() != domain): - config.load(get_provider_path(domain)) - - return config.loaded() - - class ILEAPComponent(zope.interface.Interface): """ Interface that every component for the backend should comply to @@ -167,6 +147,7 @@ class Provider(object): :type bypass_checks: bool """ self.key = "provider" + self._signaler = signaler self._provider_bootstrapper = ProviderBootstrapper(signaler, bypass_checks) self._download_provider_defer = None @@ -208,13 +189,9 @@ class Provider(object): """ d = None - # TODO: use this commented code when we don't need the provider config - # in the maiwindow. - # config = ProviderConfig.get_provider_config(provider) - # self._provider_config = config - # if config is not None: - config = self._provider_config - if get_provider_config(config, provider): + config = ProviderConfig.get_provider_config(provider) + self._provider_config = config + if config is not None: d = self._provider_bootstrapper.run_provider_setup_checks( config, download_if_needed=True) else: @@ -228,6 +205,73 @@ class Provider(object): d = defer.Deferred() return d + def _get_services(self, domain): + """ + Returns a list of services provided by the given provider. + + :param domain: the provider to get the services from. + :type domain: str + + :rtype: list of str + """ + services = [] + provider_config = ProviderConfig.get_provider_config(domain) + if provider_config is not None: + services = provider_config.get_services() + + return services + + def get_supported_services(self, domain): + """ + Signal a list of supported services provided by the given provider. + + :param domain: the provider to get the services from. + :type domain: str + + Signals: + prov_get_supported_services -> list of unicode + """ + services = get_supported(self._get_services(domain)) + + self._signaler.signal( + self._signaler.PROV_GET_SUPPORTED_SERVICES, services) + + def get_all_services(self, providers): + """ + Signal a list of services provided by all the configured providers. + + :param providers: the list of providers to get the services. + :type providers: list + + Signals: + prov_get_all_services -> list of unicode + """ + services_all = set() + + for domain in providers: + services = self._get_services(domain) + services_all = services_all.union(set(services)) + + self._signaler.signal( + self._signaler.PROV_GET_ALL_SERVICES, services_all) + + def get_details(self, domain, lang=None): + """ + Signal a ProviderConfigLight object with the current ProviderConfig + settings. + + :param domain: the domain name of the provider. + :type domain: str + :param lang: the language to use for localized strings. + :type lang: str + + Signals: + prov_get_details -> ProviderConfigLight + """ + self._signaler.signal( + self._signaler.PROV_GET_DETAILS, + self._provider_config.get_light_config(domain, lang)) + class Register(object): """ @@ -926,6 +970,10 @@ class Signaler(QtCore.QObject): prov_unsupported_client = QtCore.Signal(object) prov_unsupported_api = QtCore.Signal(object) + prov_get_all_services = QtCore.Signal(object) + prov_get_supported_services = QtCore.Signal(object) + prov_get_details = QtCore.Signal(object) + prov_cancelled_setup = QtCore.Signal(object) # Signals for SRPRegister @@ -1021,6 +1069,9 @@ class Signaler(QtCore.QObject): PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client" PROV_UNSUPPORTED_API = "prov_unsupported_api" PROV_CANCELLED_SETUP = "prov_cancelled_setup" + PROV_GET_ALL_SERVICES = "prov_get_all_services" + PROV_GET_SUPPORTED_SERVICES = "prov_get_supported_services" + PROV_GET_DETAILS = "prov_get_details" SRP_REGISTRATION_FINISHED = "srp_registration_finished" SRP_REGISTRATION_FAILED = "srp_registration_failed" @@ -1106,6 +1157,9 @@ class Signaler(QtCore.QObject): self.PROV_UNSUPPORTED_CLIENT, self.PROV_UNSUPPORTED_API, self.PROV_CANCELLED_SETUP, + self.PROV_GET_ALL_SERVICES, + self.PROV_GET_SUPPORTED_SERVICES, + self.PROV_GET_DETAILS, self.SRP_REGISTRATION_FINISHED, self.SRP_REGISTRATION_FAILED, @@ -1393,6 +1447,47 @@ class Backend(object): """ self._call_queue.put(("provider", "bootstrap", None, provider)) + def provider_get_supported_services(self, domain): + """ + Signal a list of supported services provided by the given provider. + + :param domain: the provider to get the services from. + :type domain: str + + Signals: + prov_get_supported_services -> list of unicode + """ + self._call_queue.put(("provider", "get_supported_services", None, + domain)) + + def provider_get_all_services(self, providers): + """ + Signal a list of services provided by all the configured providers. + + :param providers: the list of providers to get the services. + :type providers: list + + Signals: + prov_get_all_services -> list of unicode + """ + self._call_queue.put(("provider", "get_all_services", None, + providers)) + + def provider_get_details(self, domain, lang): + """ + Signal a ProviderConfigLight object with the current ProviderConfig + settings. + + :param domain: the domain name of the provider. + :type domain: str + :param lang: the language to use for localized strings. + :type lang: str + + Signals: + prov_get_details -> ProviderConfigLight + """ + self._call_queue.put(("provider", "get_details", None, domain, lang)) + def user_register(self, provider, username, password): """ Register a user using the domain and password given as parameters. @@ -1698,16 +1793,6 @@ class Backend(object): # XXX HACK: this section is meant to be a place to hold methods and # variables needed in the meantime while we migrate all to the backend. - def get_provider_config(self): - # TODO: refactor the provider config into a singleton/global loading it - # every time from the file. - provider_config = self._components["provider"]._provider_config - return provider_config - - def get_soledad(self): - soledad = self._components["soledad"]._soledad_bootstrapper._soledad - return soledad - def get_keymanager(self): km = self._components["soledad"]._soledad_bootstrapper._keymanager return km diff --git a/src/leap/bitmask/config/providerconfig.py b/src/leap/bitmask/config/providerconfig.py index b411c6f3..cf31b3b2 100644 --- a/src/leap/bitmask/config/providerconfig.py +++ b/src/leap/bitmask/config/providerconfig.py @@ -38,6 +38,35 @@ class MissingCACert(Exception): pass +class ProviderConfigLight(object): + """ + A light config object to hold some provider settings needed by the GUI. + """ + def __init__(self): + """ + Define the public attributes. + """ + self.domain = "" + self.name = "" + self.description = "" + self.enrollment_policy = "" + self.services = [] + + @property + def services_string(self): + """ + Return a comma separated list of serices provided by this provider. + + :rtype: str + """ + services = [] + for service in self.services: + services.append(get_service_display_name(service)) + + services_str = ", ".join(services) + return services_str + + class ProviderConfig(BaseConfig): """ Provider configuration abstraction class @@ -45,6 +74,29 @@ class ProviderConfig(BaseConfig): def __init__(self): BaseConfig.__init__(self) + def get_light_config(self, domain, lang=None): + """ + Return a ProviderConfigLight object with the data for the loaded + object. + + :param domain: the domain name of the provider. + :type domain: str + :param lang: the language to use for localized strings. + :type lang: str + + :rtype: ProviderConfigLight or None if the ProviderConfig isn't loaded. + """ + config = self.get_provider_config(domain) + details = ProviderConfigLight() + + details.domain = config.get_domain() + details.name = config.get_name(lang=lang) + details.description = config.get_description(lang=lang) + details.enrollment_policy = config.get_enrollment_policy() + details.services = config.get_services() + + return details + @classmethod def get_provider_config(self, domain): """ @@ -144,18 +196,6 @@ class ProviderConfig(BaseConfig): services = self._safe_get_value("services") return services - def get_services_string(self): - """ - Returns a string with the available services in the current - provider, ready to be shown to the user. - """ - services = [] - for service in self.get_services(): - services.append(get_service_display_name(service)) - - services_str = ", ".join(services) - return services_str - def get_ca_cert_path(self, about_to_download=False): """ Returns the path to the certificate for the current provider. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 201a24ec..4d79305e 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -30,7 +30,6 @@ from leap.bitmask import __version__ as VERSION from leap.bitmask import __version_hash__ as VERSION_HASH from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings -from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.gui import statemachines from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement @@ -43,7 +42,6 @@ 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 from leap.bitmask.platform_init.initializers import init_platform @@ -64,7 +62,6 @@ if IS_WIN: from leap.bitmask.platform_init.locks import WindowsLock from leap.bitmask.platform_init.locks import raise_window_ack -from leap.common.check import leap_assert from leap.common.events import register from leap.common.events import events_pb2 as proto @@ -178,15 +175,6 @@ class MainWindow(QtGui.QMainWindow): self._trying_to_start_eip = False - # This is loaded only once, there's a bug when doing that more - # than once - # XXX HACK!! But we need it as long as we are using - # provider_config in here - self._provider_config = self._backend.get_provider_config() - - # Used for automatic start of EIP - self._provisional_provider_config = ProviderConfig() - self._already_started_eip = False self._soledad_started = False @@ -247,6 +235,8 @@ class MainWindow(QtGui.QMainWindow): self._ui_mx_visible = True self._ui_eip_visible = True + self._provider_details = None + # last minute UI manipulations self._center_window() @@ -383,6 +373,9 @@ class MainWindow(QtGui.QMainWindow): self._connect_and_track(sig.prov_cancelled_setup, self._set_login_cancelled) + self._connect_and_track(sig.prov_get_details, + self._provider_get_details) + # Login signals self._connect_and_track(sig.srp_auth_ok, self._authentication_finished) @@ -430,9 +423,14 @@ class MainWindow(QtGui.QMainWindow): sig.backend_bad_call.connect(self._backend_bad_call) + sig.prov_check_api_certificate.connect(self._get_provider_details) + sig.prov_unsupported_client.connect(self._needs_update) sig.prov_unsupported_api.connect(self._incompatible_api) + sig.prov_get_all_services.connect( + self._provider_get_all_services) + # EIP start signals sig.eip_openvpn_already_running.connect( self._on_eip_openvpn_already_running) @@ -597,12 +595,13 @@ class MainWindow(QtGui.QMainWindow): domain = self._login_widget.get_selected_provider() logged_user = "{0}@{1}".format(self._logged_user, domain) - has_mx = True - if self._logged_user is not None: - provider_config = self._get_best_provider_config() - has_mx = provider_config.provides_mx() + details = self._provider_details + mx_provided = False + if details is not None: + mx_provided = MX_SERVICE in details - akm = AdvancedKeyManagement(self, has_mx, logged_user, + # XXX: handle differently not logged in user? + akm = AdvancedKeyManagement(self, mx_provided, logged_user, self._keymanager, self._soledad_started) akm.show() @@ -618,8 +617,7 @@ class MainWindow(QtGui.QMainWindow): user = self._login_widget.get_user() prov = self._login_widget.get_selected_provider() preferences = PreferencesWindow( - self, self._backend, self._provider_config, - self._soledad_started, user, prov) + self, self._backend, self._soledad_started, user, prov) self.soledad_ready.connect(preferences.set_soledad_ready) preferences.show() @@ -857,16 +855,9 @@ class MainWindow(QtGui.QMainWindow): """ providers = self._settings.get_configured_providers() - services = set() - - for prov in providers: - provider_config = ProviderConfig() - loaded = provider_config.load( - provider.get_provider_path(prov)) - if loaded: - for service in provider_config.get_services(): - services.add(service) + self._backend.provider_get_all_services(providers) + def _provider_get_all_services(self, services): self._set_eip_visible(EIP_SERVICE in services) self._set_mx_visible(MX_SERVICE in services) @@ -904,14 +895,11 @@ class MainWindow(QtGui.QMainWindow): """ Set the login label to reflect offline status. """ - if self._logged_in_offline: - provider = "" - else: + provider = "" + if not self._logged_in_offline: provider = self.ui.lblLoginProvider.text() - self.ui.lblLoginProvider.setText( - provider + - self.tr(" (offline mode)")) + self.ui.lblLoginProvider.setText(provider + self.tr(" (offline mode)")) # # systray @@ -1165,9 +1153,8 @@ class MainWindow(QtGui.QMainWindow): provider configuration if it's not present, otherwise will emit the corresponding signals inmediately """ - # XXX should rename this provider, name clash. - provider = self._login_widget.get_selected_provider() - self._backend.provider_setup(provider) + domain = self._login_widget.get_selected_provider() + self._backend.provider_setup(domain) @QtCore.Slot(dict) def _load_provider_config(self, data): @@ -1175,12 +1162,11 @@ class MainWindow(QtGui.QMainWindow): TRIGGERS: self._backend.signaler.prov_download_provider_info - Once the provider config has been downloaded, this loads the - self._provider_config instance with it and starts the second - part of the bootstrapping sequence + Once the provider config has been downloaded, start the second + part of the bootstrapping sequence. :param data: result from the last stage of the - run_provider_select_checks + backend.provider_setup() :type data: dict """ if data[self._backend.PASSED_KEY]: @@ -1222,7 +1208,6 @@ class MainWindow(QtGui.QMainWindow): self._set_label_offline() self.offline_mode_bypass_login.emit() else: - leap_assert(self._provider_config, "We need a provider config") self.ui.action_create_new_account.setEnabled(False) if self._login_widget.start_login(): self._download_provider_config() @@ -1290,15 +1275,13 @@ class MainWindow(QtGui.QMainWindow): Once the provider configuration is loaded, this starts the SRP authentication """ - leap_assert(self._provider_config, "We need a provider config!") - if data[self._backend.PASSED_KEY]: username = self._login_widget.get_user() password = self._login_widget.get_password() self._show_hide_unsupported_services() - domain = self._provider_config.get_domain() + domain = self._login_widget.get_selected_provider() self._backend.user_login(domain, username, password) else: logger.error(data[self._backend.ERROR_KEY]) @@ -1317,7 +1300,7 @@ class MainWindow(QtGui.QMainWindow): self._logged_user = self._login_widget.get_user() user = self._logged_user - domain = self._provider_config.get_domain() + domain = self._login_widget.get_selected_provider() full_user_id = make_address(user, domain) self._mail_conductor.userid = full_user_id self._start_eip_bootstrap() @@ -1331,7 +1314,7 @@ class MainWindow(QtGui.QMainWindow): sig.soledad_bootstrap_failed.connect(lambda: btn_enabled(True)) sig.soledad_bootstrap_finished.connect(lambda: btn_enabled(True)) - if not self._get_best_provider_config().provides_mx(): + if not MX_SERVICE in self._provider_details.services: self._set_mx_visible(False) def _start_eip_bootstrap(self): @@ -1341,11 +1324,10 @@ class MainWindow(QtGui.QMainWindow): """ self._login_widget.logged_in() - provider = self._provider_config.get_domain() - self.ui.lblLoginProvider.setText(provider) + domain = self._login_widget.get_selected_provider() + self.ui.lblLoginProvider.setText(domain) - self._enabled_services = self._settings.get_enabled_services( - self._provider_config.get_domain()) + self._enabled_services = self._settings.get_enabled_services(domain) # TODO separate UI from logic. if self._provides_mx_and_enabled(): @@ -1355,6 +1337,30 @@ class MainWindow(QtGui.QMainWindow): self._maybe_start_eip() + @QtCore.Slot() + def _get_provider_details(self): + """ + TRIGGERS: + prov_check_api_certificate + + Set the attributes to know if the EIP and MX services are supported + and enabled. + This is triggered right after the provider has been set up. + """ + domain = self._login_widget.get_selected_provider() + lang = QtCore.QLocale.system().name() + self._backend.provider_get_details(domain, lang) + + @QtCore.Slot() + def _provider_get_details(self, details): + """ + Set the details for the just downloaded provider. + + :param details: the details of the provider. + :type details: ProviderConfigLight + """ + self._provider_details = details + def _provides_mx_and_enabled(self): """ Defines if the current provider provides mx and if we have it enabled. @@ -1362,9 +1368,15 @@ class MainWindow(QtGui.QMainWindow): :returns: True if provides and is enabled, False otherwise :rtype: bool """ - provider_config = self._get_best_provider_config() - return (provider_config.provides_mx() and - MX_SERVICE in self._enabled_services) + domain = self._login_widget.get_selected_provider() + enabled_services = self._settings.get_enabled_services(domain) + + mx_enabled = MX_SERVICE in enabled_services + mx_provided = False + if self._provider_details is not None: + mx_provided = MX_SERVICE in self._provider_details.services + + return mx_enabled and mx_provided def _provides_eip_and_enabled(self): """ @@ -1373,16 +1385,23 @@ class MainWindow(QtGui.QMainWindow): :returns: True if provides and is enabled, False otherwise :rtype: bool """ - provider_config = self._get_best_provider_config() - return (provider_config.provides_eip() and - EIP_SERVICE in self._enabled_services) + domain = self._login_widget.get_selected_provider() + enabled_services = self._settings.get_enabled_services(domain) + + eip_enabled = EIP_SERVICE in enabled_services + eip_provided = False + if self._provider_details is not None: + eip_provided = EIP_SERVICE in self._provider_details.services + + return eip_enabled and eip_provided def _maybe_run_soledad_setup_checks(self): """ Conditionally start Soledad. """ # TODO split. - if not self._provides_mx_and_enabled(): + if not self._provides_mx_and_enabled() and not flags.OFFLINE: + logger.debug("Does not provides and enabled MX") return username = self._login_widget.get_user() @@ -1390,9 +1409,6 @@ class MainWindow(QtGui.QMainWindow): provider_domain = self._login_widget.get_selected_provider() if flags.OFFLINE: - self._provisional_provider_config.load( - provider.get_provider_path(provider_domain)) - full_user_id = make_address(username, provider_domain) uuid = self._settings.get_uuid(full_user_id) self._mail_conductor.userid = full_user_id @@ -1405,7 +1421,7 @@ class MainWindow(QtGui.QMainWindow): self._backend.soledad_load_offline(full_user_id, password, uuid) else: if self._logged_user is not None: - domain = self._provider_config.get_domain() + domain = self._login_widget.get_selected_provider() self._backend.soledad_bootstrap(username, domain, password) ################################################################### @@ -1457,19 +1473,8 @@ class MainWindow(QtGui.QMainWindow): # TODO in the OFFLINE mode we should also modify the rules # in the mail state machine so it shows that imap is active # (but not smtp since it's not yet ready for offline use) - start_fun = self._mail_conductor.start_imap_service - if flags.OFFLINE: - provider_domain = self._login_widget.get_selected_provider() - self._provider_config.load( - provider.get_provider_path(provider_domain)) - provides_mx = self._provider_config.provides_mx() - - if flags.OFFLINE and provides_mx: - start_fun() - return - - if self._provides_mx_and_enabled(): - start_fun() + if self._provides_mx_and_enabled() or flags.OFFLINE: + self._mail_conductor.start_imap_service() # end service control methods (imap) @@ -1519,9 +1524,7 @@ class MainWindow(QtGui.QMainWindow): """ self._eip_connection.qtsigs.connected_signal.emit() - provider_config = self._get_best_provider_config() - domain = provider_config.get_domain() - + domain = self._login_widget.get_selected_provider() self._eip_status.set_provider(domain) self._settings.set_defaultprovider(domain) self._already_started_eip = True @@ -1582,19 +1585,8 @@ class MainWindow(QtGui.QMainWindow): self._enabled_services = settings.get_enabled_services( default_provider) - loaded = self._provisional_provider_config.load( - provider.get_provider_path(default_provider)) - if loaded and settings.get_autostart_eip(): - # 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. + if settings.get_autostart_eip(): self._maybe_start_eip() - elif settings.get_autostart_eip(): - # 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): @@ -1700,11 +1692,12 @@ class MainWindow(QtGui.QMainWindow): logger.debug('Setting autostart to: False') self._settings.set_autostart_eip(False) - if self._logged_user: - self._eip_status.set_provider( - make_address( - self._logged_user, - self._get_best_provider_config().get_domain())) + user = self._logged_user + if user: + domain = self._login_widget.get_selected_provider() + full_user_id = make_address(user, domain) + self._eip_status.set_provider(full_user_id) + self._eip_status.eip_stopped() @QtCore.Slot() @@ -1874,30 +1867,6 @@ class MainWindow(QtGui.QMainWindow): # end of EIP methods --------------------------------------------- - def _get_best_provider_config(self): - """ - Returns the best ProviderConfig to use at a moment. We may - have to use self._provider_config or - self._provisional_provider_config depending on the start - status. - - :rtype: ProviderConfig - """ - # TODO move this out of gui. - leap_assert(self._provider_config is not None or - self._provisional_provider_config is not None, - "We need a provider config") - - provider_config = None - if self._provider_config.loaded(): - provider_config = self._provider_config - elif self._provisional_provider_config.loaded(): - provider_config = self._provisional_provider_config - else: - leap_assert(False, "We could not find any usable ProviderConfig.") - - return provider_config - @QtCore.Slot() def _logout(self): """ diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 0a4c7f56..c67052f3 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -24,12 +24,9 @@ from functools import partial from PySide import QtCore, QtGui -from leap.bitmask.provider import get_provider_path from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.gui.ui_preferences import Ui_Preferences from leap.bitmask.util.credentials import password_checks -from leap.bitmask.services import get_supported -from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.services import get_service_display_name, MX_SERVICE logger = logging.getLogger(__name__) @@ -41,15 +38,12 @@ class PreferencesWindow(QtGui.QDialog): """ preferences_saved = QtCore.Signal() - def __init__(self, parent, backend, provider_config, soledad_started, - username, domain): + def __init__(self, parent, backend, soledad_started, username, domain): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget :param backend: Backend being used :type backend: Backend - :param provider_config: ProviderConfig object. - :type provider_config: ProviderConfig :param soledad_started: whether soledad has started or not :type soledad_started: bool :param username: the user set in the login widget @@ -62,7 +56,6 @@ class PreferencesWindow(QtGui.QDialog): self._backend = backend self._settings = LeapSettings() - self._provider_config = provider_config self._soledad_started = soledad_started self._username = username self._domain = domain @@ -99,31 +92,7 @@ class PreferencesWindow(QtGui.QDialog): Actions to perform is the user is logged in. """ - settings = self._settings - pw_enabled = True - - # check if provider has 'mx' ... - # TODO: we should move this to the backend. - if self._provider_config.provides_mx(): - enabled_services = settings.get_enabled_services(self._domain) - mx_name = get_service_display_name(MX_SERVICE) - - # ... and if the user have it enabled - if MX_SERVICE not in enabled_services: - msg = self.tr("You need to enable {0} in order to change " - "the password.".format(mx_name)) - self._set_password_change_status(msg, error=True) - pw_enabled = False - else: - # check if Soledad is bootstrapped - if not self._soledad_started: - msg = self.tr( - "You need to wait until {0} is ready in " - "order to change the password.".format(mx_name)) - self._set_password_change_status(msg) - pw_enabled = False - - self.ui.gbPasswordChange.setEnabled(pw_enabled) + self._backend.provider_provides_mx() @QtCore.Slot() def _not_logged_in(self): @@ -138,6 +107,44 @@ class PreferencesWindow(QtGui.QDialog): self._set_password_change_status(msg) self.ui.gbPasswordChange.setEnabled(False) + @QtCore.Slot() + def _provides_mx(self): + """ + TRIGGERS: + Signaler.prov_provides_mx + + Actions to perform if the provider provides MX. + """ + pw_enabled = True + enabled_services = self._settings.get_enabled_services(self._domain) + mx_name = get_service_display_name(MX_SERVICE) + + if MX_SERVICE not in enabled_services: + msg = self.tr("You need to enable {0} in order to change " + "the password.".format(mx_name)) + self._set_password_change_status(msg, error=True) + pw_enabled = False + else: + # check if Soledad is bootstrapped + if not self._soledad_started: + msg = self.tr( + "You need to wait until {0} is ready in " + "order to change the password.".format(mx_name)) + self._set_password_change_status(msg) + pw_enabled = False + + self.ui.gbPasswordChange.setEnabled(pw_enabled) + + @QtCore.Slot() + def _not_provides_mx(self): + """ + TRIGGERS: + Signaler.prov_not_provides_mx + + Actions to perform if the provider does not provides MX. + """ + self.ui.gbPasswordChange.setEnabled(False) + @QtCore.Slot() def set_soledad_ready(self): """ @@ -339,8 +346,7 @@ class PreferencesWindow(QtGui.QDialog): TRIGGERS: self.ui.cbProvidersServices.currentIndexChanged[unicode] - Loads the services that the provider provides into the UI for - the user to enable or disable. + Fill the services list with the selected provider's services. :param domain: the domain of the provider to load services from. :type domain: str @@ -351,10 +357,6 @@ class PreferencesWindow(QtGui.QDialog): if not domain: return - provider_config = self._get_provider_config(domain) - if provider_config is None: - return - # set the proper connection for the 'save' button try: self.ui.pbSaveServices.clicked.disconnect() @@ -364,7 +366,21 @@ class PreferencesWindow(QtGui.QDialog): save_services = partial(self._save_enabled_services, domain) self.ui.pbSaveServices.clicked.connect(save_services) - services = get_supported(provider_config.get_services()) + self._backend.provider_get_supported_services(domain) + + @QtCore.Slot(str) + def _load_services(self, services): + """ + TRIGGERS: + self.ui.cbProvidersServices.currentIndexChanged[unicode] + + Loads the services that the provider provides into the UI for + the user to enable or disable. + + :param domain: the domain of the provider to load services from. + :type domain: str + """ + domain = self.ui.cbProvidersServices.currentText() services_conf = self._settings.get_enabled_services(domain) # discard changes if other provider is selected @@ -412,27 +428,15 @@ class PreferencesWindow(QtGui.QDialog): self._set_providers_services_status(msg, success=True) self.preferences_saved.emit() - def _get_provider_config(self, domain): - """ - Helper to return a valid Provider Config from the domain name. - - :param domain: the domain name of the provider. - :type domain: str - - :rtype: ProviderConfig or None if there is a problem loading the config - """ - provider_config = ProviderConfig() - if not provider_config.load(get_provider_path(domain)): - provider_config = None - - return provider_config - def _backend_connect(self): """ Helper to connect to backend signals """ sig = self._backend.signaler + sig.prov_provides_mx.connect(self._provides_mx) + sig.prov_get_supported_services.connect(self._load_services) + sig.srp_status_logged_in.connect(self._is_logged_in) sig.srp_status_not_logged_in.connect(self._not_logged_in) diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index ce45b431..4d774907 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -26,8 +26,6 @@ from PySide import QtCore, QtGui from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings -from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.provider import get_provider_path from leap.bitmask.services import get_service_display_name, get_supported from leap.bitmask.util.credentials import password_checks, username_checks from leap.bitmask.util.credentials import USERNAME_REGEX @@ -88,10 +86,9 @@ class Wizard(QtGui.QWizard): self._backend_connect() self._domain = None - # HACK!! We need provider_config for the time being, it'll be - # removed - self._provider_config = ( - self._backend._components["provider"]._provider_config) + + # this details are set when the provider download is complete. + self._provider_details = None # We will store a reference to the defers for eventual use # (eg, to cancel them) but not doing anything with them right now. @@ -513,10 +510,12 @@ class Wizard(QtGui.QWizard): check. Since this check is the last of this set, it also completes the page if passed """ - if self._provider_config.load(get_provider_path(self._domain)): + if data[self._backend.PASSED_KEY]: self._complete_task(data, self.ui.lblProviderInfo, True, self.SELECT_PROVIDER_PAGE) self._provider_checks_ok = True + lang = QtCore.QLocale.system().name() + self._backend.provider_get_details(self._domain, lang) else: new_data = { self._backend.PASSED_KEY: False, @@ -538,6 +537,16 @@ class Wizard(QtGui.QWizard): else: self.ui.cbProviders.setEnabled(True) + @QtCore.Slot() + def _provider_get_details(self, details): + """ + Set the details for the just downloaded provider. + + :param details: the details of the provider. + :type details: ProviderConfigLight + """ + self._provider_details = details + @QtCore.Slot(dict) def _download_ca_cert(self, data): """ @@ -605,11 +614,9 @@ class Wizard(QtGui.QWizard): the user to enable or disable. """ self.ui.grpServices.setTitle( - self.tr("Services by %s") % - (self._provider_config.get_name(),)) + self.tr("Services by {0}").format(self._provider_details.name)) - services = get_supported( - self._provider_config.get_services()) + services = get_supported(self._provider_details.services) for service in services: try: @@ -652,38 +659,31 @@ class Wizard(QtGui.QWizard): if not self._provider_setup_ok: self._reset_provider_setup() sub_title = self.tr("Gathering configuration options for {0}") - sub_title = sub_title.format(self._provider_config.get_name()) + sub_title = sub_title.format(self._provider_details.name) self.page(pageId).setSubTitle(sub_title) self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON) self._provider_setup_defer = self._backend.\ provider_bootstrap(self._domain) if pageId == self.PRESENT_PROVIDER_PAGE: - self.page(pageId).setSubTitle(self.tr("Description of services " - "offered by %s") % - (self._provider_config - .get_name(),)) - - lang = QtCore.QLocale.system().name() - self.ui.lblProviderName.setText( - "%s" % - (self._provider_config.get_name(lang=lang),)) - self.ui.lblProviderURL.setText( - "https://%s" % (self._provider_config.get_domain(),)) - self.ui.lblProviderDesc.setText( - "%s" % - (self._provider_config.get_description(lang=lang),)) - - self.ui.lblServicesOffered.setText(self._provider_config - .get_services_string()) - self.ui.lblProviderPolicy.setText(self._provider_config - .get_enrollment_policy()) + sub_title = self.tr("Description of services offered by {0}") + sub_title = sub_title.format(self._provider_details.name) + self.page(pageId).setSubTitle(sub_title) + + details = self._provider_details + name = "{0}".format(details.name) + domain = "https://{0}".format(details.domain) + description = "{0}".format(details.description) + self.ui.lblProviderName.setText(name) + self.ui.lblProviderURL.setText(domain) + self.ui.lblProviderDesc.setText(description) + self.ui.lblServicesOffered.setText(details.services_string) + self.ui.lblProviderPolicy.setText(details.enrollment_policy) if pageId == self.REGISTER_USER_PAGE: - self.page(pageId).setSubTitle(self.tr("Register a new user with " - "%s") % - (self._provider_config - .get_name(),)) + sub_title = self.tr("Register a new user with {0}") + sub_title = sub_title.format(self._provider_details.name) + self.page(pageId).setSubTitle(sub_title) self.ui.chkRemember.setVisible(False) if pageId == self.SERVICES_PAGE: @@ -706,8 +706,6 @@ class Wizard(QtGui.QWizard): if self.currentPage() == self.page(self.SELECT_PROVIDER_PAGE): if self._use_existing_provider: self._domain = self.ui.cbProviders.currentText() - self._provider_config = ProviderConfig.get_provider_config( - self._domain) if self._show_register: return self.REGISTER_USER_PAGE else: @@ -732,6 +730,7 @@ class Wizard(QtGui.QWizard): sig.prov_name_resolution.connect(self._name_resolution) sig.prov_https_connection.connect(self._https_connection) sig.prov_download_provider_info.connect(self._download_provider_info) + sig.prov_get_details.connect(self._provider_get_details) sig.prov_download_ca_cert.connect(self._download_ca_cert) sig.prov_check_ca_fingerprint.connect(self._check_ca_fingerprint) diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 2bdad7e2..db12fd80 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -188,8 +188,9 @@ class SoledadBootstrapper(AbstractBootstrapper): try: self.load_and_sync_soledad(uuid, offline=True) self._signaler.signal(self._signaler.SOLEDAD_OFFLINE_FINISHED) - except Exception: + except Exception as e: # TODO: we should handle more specific exceptions in here + logger.exception(e) self._signaler.signal(self._signaler.SOLEDAD_OFFLINE_FAILED) def _get_soledad_local_params(self, uuid, offline=False): -- cgit v1.2.3 From f330589af3e8752dd9e948b1bbd171f503780a91 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 27 May 2014 13:28:56 -0300 Subject: Refactor cleanup and move Keymanager to backend. --- src/leap/bitmask/backend.py | 204 +++++++++++++++++++- src/leap/bitmask/gui/advanced_key_management.py | 237 ++++++++++++++---------- src/leap/bitmask/gui/mainwindow.py | 12 +- 3 files changed, 342 insertions(+), 111 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index e8bf0482..5e22a8c4 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -56,6 +56,9 @@ from leap.bitmask.services.soledad.soledadbootstrapper import \ from leap.common import certs as leap_certs +from leap.keymanager import openpgp +from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch + from leap.soledad.client import NoStorageSecret, PassphraseTooShort # Frontend side @@ -745,6 +748,125 @@ class Soledad(object): d.addErrback(self._change_password_error) +class Keymanager(object): + """ + Interfaces with KeyManager. + """ + zope.interface.implements(ILEAPComponent) + + def __init__(self, keymanager_proxy, signaler=None): + """ + Constructor for the Keymanager component. + + :param keymanager_proxy: proxy to pass around a Keymanager object. + :type keymanager_proxy: zope.ProxyBase + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + self.key = "keymanager" + self._keymanager_proxy = keymanager_proxy + self._signaler = signaler + + def import_keys(self, username, filename): + """ + Imports the username's key pair. + Those keys need to be ascii armored. + + :param username: the user that will have the imported pair of keys. + :type username: str + :param filename: the name of the file where the key pair is stored. + :type filename: str + """ + # NOTE: This feature is disabled right now since is dangerous + return + + new_key = '' + signal = None + try: + with open(filename, 'r') as keys_file: + new_key = keys_file.read() + except IOError as e: + logger.error("IOError importing key. {0!r}".format(e)) + signal = self._signaler.KEYMANAGER_IMPORT_IOERROR + self._signaler.signal(signal) + return + + keymanager = self._keymanager_proxy + try: + public_key, private_key = keymanager.parse_openpgp_ascii_key( + new_key) + except (KeyAddressMismatch, KeyFingerprintMismatch) as e: + logger.error(repr(e)) + signal = self._signaler.KEYMANAGER_IMPORT_DATAMISMATCH + self._signaler.signal(signal) + return + + if public_key is None or private_key is None: + signal = self._signaler.KEYMANAGER_IMPORT_MISSINGKEY + self._signaler.signal(signal) + return + + current_public_key = keymanager.get_key(username, openpgp.OpenPGPKey) + if public_key.address != current_public_key.address: + logger.error("The key does not match the ID") + signal = self._signaler.KEYMANAGER_IMPORT_ADDRESSMISMATCH + self._signaler.signal(signal) + return + + keymanager.delete_key(self._key) + keymanager.delete_key(self._key_priv) + keymanager.put_key(public_key) + keymanager.put_key(private_key) + keymanager.send_key(openpgp.OpenPGPKey) + + logger.debug('Import ok') + signal = self._signaler.KEYMANAGER_IMPORT_OK + + self._signaler.signal(signal) + + def export_keys(self, username, filename): + """ + Export the given username's keys to a file. + + :param username: the username whos keys we need to export. + :type username: str + :param filename: the name of the file where we want to save the keys. + :type filename: str + """ + keymanager = self._keymanager_proxy + + public_key = keymanager.get_key(username, openpgp.OpenPGPKey) + private_key = keymanager.get_key(username, openpgp.OpenPGPKey, + private=True) + try: + with open(filename, 'w') as keys_file: + keys_file.write(public_key.key_data) + keys_file.write(private_key.key_data) + + logger.debug('Export ok') + self._signaler.signal(self._signaler.KEYMANAGER_EXPORT_OK) + except IOError as e: + logger.error("IOError exporting key. {0!r}".format(e)) + self._signaler.signal(self._signaler.KEYMANAGER_EXPORT_ERROR) + + def list_keys(self): + """ + List all the keys stored in the local DB. + """ + keys = self._keymanager_proxy.get_all_keys_in_local_db() + self._signaler.signal(self._signaler.KEYMANAGER_KEYS_LIST, keys) + + def get_key_details(self, username): + """ + List all the keys stored in the local DB. + """ + public_key = self._keymanager_proxy.get_key(username, + openpgp.OpenPGPKey) + details = (public_key.key_id, public_key.fingerprint) + self._signaler.signal(self._signaler.KEYMANAGER_KEY_DETAILS, details) + + class Mail(object): """ Interfaces with setup and launch of Mail. @@ -1047,6 +1169,19 @@ class Signaler(QtCore.QObject): soledad_password_change_ok = QtCore.Signal(object) soledad_password_change_error = QtCore.Signal(object) + # Keymanager signals + keymanager_export_ok = QtCore.Signal(object) + keymanager_export_error = QtCore.Signal(object) + keymanager_keys_list = QtCore.Signal(object) + + keymanager_import_ioerror = QtCore.Signal(object) + keymanager_import_datamismatch = QtCore.Signal(object) + keymanager_import_missingkey = QtCore.Signal(object) + keymanager_import_addressmismatch = QtCore.Signal(object) + keymanager_import_ok = QtCore.Signal(object) + + keymanager_key_details = QtCore.Signal(object) + # mail related signals imap_stopped = QtCore.Signal(object) @@ -1135,6 +1270,17 @@ class Signaler(QtCore.QObject): SOLEDAD_CANCELLED_BOOTSTRAP = "soledad_cancelled_bootstrap" + KEYMANAGER_EXPORT_OK = "keymanager_export_ok" + KEYMANAGER_EXPORT_ERROR = "keymanager_export_error" + KEYMANAGER_KEYS_LIST = "keymanager_keys_list" + + KEYMANAGER_IMPORT_IOERROR = "keymanager_import_ioerror" + KEYMANAGER_IMPORT_DATAMISMATCH = "keymanager_import_datamismatch" + KEYMANAGER_IMPORT_MISSINGKEY = "keymanager_import_missingkey" + KEYMANAGER_IMPORT_ADDRESSMISMATCH = "keymanager_import_addressmismatch" + KEYMANAGER_IMPORT_OK = "keymanager_import_ok" + KEYMANAGER_KEY_DETAILS = "keymanager_key_details" + IMAP_STOPPED = "imap_stopped" BACKEND_BAD_CALL = "backend_bad_call" @@ -1223,6 +1369,17 @@ class Signaler(QtCore.QObject): self.SOLEDAD_PASSWORD_CHANGE_OK, self.SOLEDAD_PASSWORD_CHANGE_ERROR, + self.KEYMANAGER_EXPORT_OK, + self.KEYMANAGER_EXPORT_ERROR, + self.KEYMANAGER_KEYS_LIST, + + self.KEYMANAGER_IMPORT_IOERROR, + self.KEYMANAGER_IMPORT_DATAMISMATCH, + self.KEYMANAGER_IMPORT_MISSINGKEY, + self.KEYMANAGER_IMPORT_ADDRESSMISMATCH, + self.KEYMANAGER_IMPORT_OK, + self.KEYMANAGER_KEY_DETAILS, + self.IMAP_STOPPED, self.BACKEND_BAD_CALL, @@ -1292,6 +1449,8 @@ class Backend(object): self._register(Soledad(self._soledad_proxy, self._keymanager_proxy, self._signaler)) + self._register(Keymanager(self._keymanager_proxy, + self._signaler)) self._register(Mail(self._soledad_proxy, self._keymanager_proxy, self._signaler)) @@ -1749,6 +1908,43 @@ class Backend(object): """ self._call_queue.put(("soledad", "close", None)) + def keymanager_list_keys(self): + """ + Signal a list of public keys locally stored. + + Signals: + keymanager_keys_list -> list + """ + self._call_queue.put(("keymanager", "list_keys", None)) + + def keymanager_export_keys(self, username, filename): + """ + Export the given username's keys to a file. + + :param username: the username whos keys we need to export. + :type username: str + :param filename: the name of the file where we want to save the keys. + :type filename: str + + Signals: + keymanager_export_ok + keymanager_export_error + """ + self._call_queue.put(("keymanager", "export_keys", None, + username, filename)) + + def keymanager_get_key_details(self, username): + """ + Signal the given username's key details. + + :param username: the username whos keys we need to get details. + :type username: str + + Signals: + keymanager_key_details + """ + self._call_queue.put(("keymanager", "get_key_details", None, username)) + def smtp_start_service(self, full_user_id, download_if_needed=False): """ Start the SMTP service. @@ -1788,11 +1984,3 @@ class Backend(object): imap_stopped """ self._call_queue.put(("mail", "stop_imap_service", None)) - - ########################################################################### - # XXX HACK: this section is meant to be a place to hold methods and - # variables needed in the meantime while we migrate all to the backend. - - def get_keymanager(self): - km = self._components["soledad"]._soledad_bootstrapper._keymanager - return km diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py index 1681caca..b3a4ed8e 100644 --- a/src/leap/bitmask/gui/advanced_key_management.py +++ b/src/leap/bitmask/gui/advanced_key_management.py @@ -19,10 +19,8 @@ Advanced Key Management """ import logging -from PySide import QtGui +from PySide import QtCore, QtGui -from leap.keymanager import openpgp -from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch from leap.bitmask.services import get_service_display_name, MX_SERVICE from ui_advanced_key_management import Ui_AdvancedKeyManagement @@ -33,7 +31,7 @@ class AdvancedKeyManagement(QtGui.QDialog): """ Advanced Key Management """ - def __init__(self, parent, has_mx, user, keymanager, soledad_started): + def __init__(self, parent, has_mx, user, backend, soledad_started): """ :param parent: parent object of AdvancedKeyManagement. :parent type: QWidget @@ -42,8 +40,8 @@ class AdvancedKeyManagement(QtGui.QDialog): :type has_mx: bool :param user: the current logged in user. :type user: unicode - :param keymanager: the existing keymanager instance - :type keymanager: KeyManager + :param backend: Backend being used + :type backend: Backend :param soledad_started: whether soledad has started or not :type soledad_started: bool """ @@ -75,16 +73,12 @@ class AdvancedKeyManagement(QtGui.QDialog): # "existing e-mails.") # self.ui.lblStatus.setText(msg) - self._keymanager = keymanager - - self._key = keymanager.get_key(user, openpgp.OpenPGPKey) - self._key_priv = keymanager.get_key( - user, openpgp.OpenPGPKey, private=True) + self._user = user + self._backend = backend + self._backend_connect() # show current key information self.ui.leUser.setText(user) - self.ui.leKeyID.setText(self._key.key_id) - self.ui.leFingerprint.setText(self._key.fingerprint) # set up connections self.ui.pbImportKeys.clicked.connect(self._import_keys) @@ -94,7 +88,15 @@ class AdvancedKeyManagement(QtGui.QDialog): self.ui.twPublicKeys.horizontalHeader().setResizeMode( 0, QtGui.QHeaderView.Stretch) - self._list_keys() + self._backend.keymanager_get_key_details(user) + self._backend.keymanager_list_keys() + + def _keymanager_key_details(self, details): + """ + Set the current user's key details into the gui. + """ + self.ui.leKeyID.setText(details[0]) + self.ui.leFingerprint.setText(details[1]) def _disable_ui(self, msg): """ @@ -113,53 +115,11 @@ class AdvancedKeyManagement(QtGui.QDialog): Imports the user's key pair. Those keys need to be ascii armored. """ - fileName, filtr = QtGui.QFileDialog.getOpenFileName( + file_name, filtr = QtGui.QFileDialog.getOpenFileName( self, self.tr("Open keys file"), options=QtGui.QFileDialog.DontUseNativeDialog) - if fileName: - new_key = '' - try: - with open(fileName, 'r') as keys_file: - new_key = keys_file.read() - except IOError as e: - logger.error("IOError importing key. {0!r}".format(e)) - QtGui.QMessageBox.critical( - self, self.tr("Input/Output error"), - self.tr("There was an error accessing the file.\n" - "Import canceled.")) - return - - keymanager = self._keymanager - try: - public_key, private_key = keymanager.parse_openpgp_ascii_key( - new_key) - except (KeyAddressMismatch, KeyFingerprintMismatch) as e: - logger.error(repr(e)) - QtGui.QMessageBox.warning( - self, self.tr("Data mismatch"), - self.tr("The public and private key should have the " - "same address and fingerprint.\n" - "Import canceled.")) - return - - if public_key is None or private_key is None: - QtGui.QMessageBox.warning( - self, self.tr("Missing key"), - self.tr("You need to provide the public AND private " - "key in the same file.\n" - "Import canceled.")) - return - - if public_key.address != self._key.address: - logger.error("The key does not match the ID") - QtGui.QMessageBox.warning( - self, self.tr("Address mismatch"), - self.tr("The identity for the key needs to be the same " - "as your user address.\n" - "Import canceled.")) - return - + if file_name: question = self.tr("Are you sure that you want to replace " "the current key pair whith the imported?") res = QtGui.QMessageBox.question( @@ -167,61 +127,152 @@ class AdvancedKeyManagement(QtGui.QDialog): QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) # default No - if res == QtGui.QMessageBox.No: - return + if res == QtGui.QMessageBox.Yes: + self._backend.keymanager_import_keys(self._user, file_name) + else: + logger.debug('Import canceled by the user.') - keymanager.delete_key(self._key) - keymanager.delete_key(self._key_priv) - keymanager.put_key(public_key) - keymanager.put_key(private_key) - keymanager.send_key(openpgp.OpenPGPKey) + @QtCore.Slot() + def _keymanager_import_ok(self): + """ + TRIGGERS: + Signaler.keymanager_import_ok - logger.debug('Import ok') + Notify the user that the key import went OK. + """ + QtGui.QMessageBox.information( + self, self.tr("Import Successful"), + self.tr("The key pair was imported successfully.")) - QtGui.QMessageBox.information( - self, self.tr("Import Successful"), - self.tr("The key pair was imported successfully.")) - else: - logger.debug('Import canceled by the user.') + @QtCore.Slot() + def _import_ioerror(self): + """ + TRIGGERS: + Signaler.keymanager_import_ioerror + + Notify the user that the key import had an IOError problem. + """ + QtGui.QMessageBox.critical( + self, self.tr("Input/Output error"), + self.tr("There was an error accessing the file.\n" + "Import canceled.")) + + @QtCore.Slot() + def _import_datamismatch(self): + """ + TRIGGERS: + Signaler.keymanager_import_datamismatch + + Notify the user that the key import had an data mismatch problem. + """ + QtGui.QMessageBox.warning( + self, self.tr("Data mismatch"), + self.tr("The public and private key should have the " + "same address and fingerprint.\n" + "Import canceled.")) + + @QtCore.Slot() + def _import_missingkey(self): + """ + TRIGGERS: + Signaler.keymanager_import_missingkey + + Notify the user that the key import failed due a missing key. + """ + QtGui.QMessageBox.warning( + self, self.tr("Missing key"), + self.tr("You need to provide the public AND private " + "key in the same file.\n" + "Import canceled.")) + + @QtCore.Slot() + def _import_addressmismatch(self): + """ + TRIGGERS: + Signaler.keymanager_import_addressmismatch + + Notify the user that the key import failed due an address mismatch. + """ + QtGui.QMessageBox.warning( + self, self.tr("Address mismatch"), + self.tr("The identity for the key needs to be the same " + "as your user address.\n" + "Import canceled.")) def _export_keys(self): """ Exports the user's key pair. """ - fileName, filtr = QtGui.QFileDialog.getSaveFileName( + file_name, filtr = QtGui.QFileDialog.getSaveFileName( self, self.tr("Save keys file"), options=QtGui.QFileDialog.DontUseNativeDialog) - if fileName: - try: - with open(fileName, 'w') as keys_file: - keys_file.write(self._key.key_data) - keys_file.write(self._key_priv.key_data) - - logger.debug('Export ok') - QtGui.QMessageBox.information( - self, self.tr("Export Successful"), - self.tr("The key pair was exported successfully.\n" - "Please, store your private key in a safe place.")) - except IOError as e: - logger.error("IOError exporting key. {0!r}".format(e)) - QtGui.QMessageBox.critical( - self, self.tr("Input/Output error"), - self.tr("There was an error accessing the file.\n" - "Export canceled.")) - return + if file_name: + self._backend.keymanager_export_keys(self._user, file_name) else: logger.debug('Export canceled by the user.') - def _list_keys(self): + @QtCore.Slot() + def _keymanager_export_ok(self): + """ + TRIGGERS: + Signaler.keymanager_export_ok + + Notify the user that the key export went OK. """ - Loads all the public keys stored in the local db to the keys table. + QtGui.QMessageBox.information( + self, self.tr("Export Successful"), + self.tr("The key pair was exported successfully.\n" + "Please, store your private key in a safe place.")) + + @QtCore.Slot() + def _keymanager_export_error(self): + """ + TRIGGERS: + Signaler.keymanager_export_error + + Notify the user that the key export didn't go well. + """ + QtGui.QMessageBox.critical( + self, self.tr("Input/Output error"), + self.tr("There was an error accessing the file.\n" + "Export canceled.")) + + @QtCore.Slot() + def _keymanager_keys_list(self, keys): """ - keys = self._keymanager.get_all_keys_in_local_db() + TRIGGERS: + Signaler.keymanager_keys_list + Load the keys given as parameter in the table. + + :param keys: the list of keys to load. + :type keys: list + """ keys_table = self.ui.twPublicKeys + for key in keys: row = keys_table.rowCount() keys_table.insertRow(row) keys_table.setItem(row, 0, QtGui.QTableWidgetItem(key.address)) keys_table.setItem(row, 1, QtGui.QTableWidgetItem(key.key_id)) + + def _backend_connect(self): + """ + Connect to backend signals. + """ + sig = self._backend.signaler + + sig.keymanager_export_ok.connect(self._keymanager_export_ok) + sig.keymanager_export_error.connect(self._keymanager_export_error) + sig.keymanager_keys_list.connect(self._keymanager_keys_list) + + sig.keymanager_key_details.connect(self._keymanager_key_details) + + sig.keymanager_import_ok.connect(self._keymanager_import_ok) + + sig.keymanager_import_ioerror.connect(self._import_ioerror) + sig.keymanager_import_datamismatch.connect(self._import_datamismatch) + sig.keymanager_import_missingkey.connect(self._import_missingkey) + sig.keymanager_import_addressmismatch.connect( + self._import_addressmismatch) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 4d79305e..cf6614be 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -23,7 +23,6 @@ import socket from datetime import datetime from PySide import QtCore, QtGui -from zope.proxy import ProxyBase, setProxiedObject from twisted.internet import reactor, threads from leap.bitmask import __version__ as VERSION @@ -269,8 +268,6 @@ class MainWindow(QtGui.QMainWindow): self._bypass_checks = bypass_checks self._start_hidden = start_hidden - self._keymanager = ProxyBase(None) - self._mail_conductor = mail_conductor.MailConductor(self._backend) self._mail_conductor.connect_mail_signals(self._mail_status) @@ -598,11 +595,11 @@ class MainWindow(QtGui.QMainWindow): details = self._provider_details mx_provided = False if details is not None: - mx_provided = MX_SERVICE in details + mx_provided = MX_SERVICE in details.services # XXX: handle differently not logged in user? akm = AdvancedKeyManagement(self, mx_provided, logged_user, - self._keymanager, self._soledad_started) + self._backend, self._soledad_started) akm.show() @QtCore.Slot() @@ -1437,12 +1434,7 @@ class MainWindow(QtGui.QMainWindow): """ logger.debug("Done bootstrapping Soledad") - # Update the proxy objects to point to the initialized instances. - # setProxiedObject(self._soledad, self._backend.get_soledad()) - setProxiedObject(self._keymanager, self._backend.get_keymanager()) - self._soledad_started = True - self.soledad_ready.emit() ################################################################### -- cgit v1.2.3 From 027aff14f3ea9c6965100a625071505f63271ffd Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 28 May 2014 09:33:25 -0300 Subject: Add changes file for #5698 and #5711. --- changes/refactor-and-cleanup | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changes/refactor-and-cleanup diff --git a/changes/refactor-and-cleanup b/changes/refactor-and-cleanup new file mode 100644 index 00000000..18331ed4 --- /dev/null +++ b/changes/refactor-and-cleanup @@ -0,0 +1,3 @@ +- Refactor Keymanager to backend. Closes #5711. +- Cleanup backend from hacks. Closes #5698. + -- cgit v1.2.3 From 8d5fba55ee399aa7cf0d6e000f806cce7279a1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 28 May 2014 11:19:59 -0300 Subject: Properly set userid for SMTP --- changes/bug_set_userid | 1 + src/leap/bitmask/services/mail/smtpbootstrapper.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changes/bug_set_userid diff --git a/changes/bug_set_userid b/changes/bug_set_userid new file mode 100644 index 00000000..050a8808 --- /dev/null +++ b/changes/bug_set_userid @@ -0,0 +1 @@ +- Properly set the userid for SMTP. \ No newline at end of file diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py index 785fe404..3ef755e8 100644 --- a/src/leap/bitmask/services/mail/smtpbootstrapper.py +++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py @@ -152,7 +152,7 @@ class SMTPBootstrapper(AbstractBootstrapper): self._provider_config = ProviderConfig.get_provider_config(domain) self._keymanager = keymanager self._smtp_config = SMTPConfig() - self._useid = userid + self._userid = userid self._download_if_needed = download_if_needed try: -- cgit v1.2.3 From 1b467b53ec99352280d098bea78cf1dceffef2d5 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 29 May 2014 10:40:34 -0300 Subject: Avoid 'provides eip' check during eip autostart. --- changes/bug-5721_eip-autostart-is-broken | 1 + src/leap/bitmask/gui/mainwindow.py | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 changes/bug-5721_eip-autostart-is-broken diff --git a/changes/bug-5721_eip-autostart-is-broken b/changes/bug-5721_eip-autostart-is-broken new file mode 100644 index 00000000..ab1ea512 --- /dev/null +++ b/changes/bug-5721_eip-autostart-is-broken @@ -0,0 +1 @@ +- Fix EIP autostart failing. Closes #5721. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index cf6614be..fd9a70ac 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1578,7 +1578,7 @@ class MainWindow(QtGui.QMainWindow): default_provider) if settings.get_autostart_eip(): - self._maybe_start_eip() + self._maybe_start_eip(autostart=True) @QtCore.Slot() def _start_EIP(self): @@ -1787,12 +1787,21 @@ class MainWindow(QtGui.QMainWindow): # eip boostrapping, config etc... - def _maybe_start_eip(self): + def _maybe_start_eip(self, autostart=False): """ Start the EIP bootstrapping sequence if the client is configured to do so. + + :param autostart: we are autostarting EIP when this is True + :type autostart: bool """ - if self._provides_eip_and_enabled() and not self._already_started_eip: + # during autostart we assume that the provider provides EIP + if autostart: + should_start = EIP_SERVICE in self._enabled_services + else: + should_start = self._provides_eip_and_enabled() + + if should_start and not self._already_started_eip: # XXX this should be handled by the state machine. self._eip_status.set_eip_status( self.tr("Starting...")) -- cgit v1.2.3 From ccc8973bdc5cc0858906751eada622de74fd5d37 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 21 May 2014 18:42:05 -0500 Subject: refactor eip start/stop control to conductor and cleanup a little bit of the signal mess. --- src/leap/bitmask/backend.py | 12 +- src/leap/bitmask/gui/eip_status.py | 161 +++++++++-- src/leap/bitmask/gui/mainwindow.py | 403 +++++----------------------- src/leap/bitmask/gui/statemachines.py | 13 +- src/leap/bitmask/services/eip/conductor.py | 269 +++++++++++++++++++ src/leap/bitmask/services/eip/vpnprocess.py | 15 +- 6 files changed, 512 insertions(+), 361 deletions(-) create mode 100644 src/leap/bitmask/services/eip/conductor.py diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 5e22a8c4..f0fe50f9 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -434,8 +434,12 @@ class EIP(object): except Exception as e: logger.error("Unexpected problem: {0!r}".format(e)) else: + logger.debug('EIP: no errors') # TODO: are we connected here? - signaler.signal(signaler.EIP_CONNECTED) + # kali -- no, we are not! CONNECTED should be passed only + # by the vpn observer. Currently handled by the state updater + # in eip_status + #signaler.signal(signaler.EIP_CONNECTED) def _do_stop(self, shutdown=False, restart=False): """ @@ -470,9 +474,9 @@ class EIP(object): self._signaler.signal(self._signaler.EIP_STOPPED) return else: - msg = "Firewall is not down yet, waiting... {0} of {1}" - msg = msg.format(retry, MAX_FW_WAIT_RETRIES) - logger.debug(msg) + #msg = "Firewall is not down yet, waiting... {0} of {1}" + #msg = msg.format(retry, MAX_FW_WAIT_RETRIES) + #logger.debug(msg) time.sleep(FW_WAIT_STEP) retry += 1 logger.warning("After waiting, firewall is not down... " diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index ca28b8bf..9da231f2 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -24,7 +24,6 @@ from functools import partial from PySide import QtCore, QtGui -from leap.bitmask.services.eip.connection import EIPConnection from leap.bitmask.services import get_service_display_name, EIP_SERVICE from leap.bitmask.platform_init import IS_LINUX from leap.bitmask.util.averages import RateMovingAverage @@ -43,9 +42,14 @@ class EIPStatusWidget(QtGui.QWidget): RATE_STR = "%1.2f KB/s" TOTAL_STR = "%1.2f Kb" - eip_connection_connected = QtCore.Signal() + def __init__(self, parent=None, eip_conductor=None): + """ + :param parent: the parent of the widget. + :type parent: QObject - def __init__(self, parent=None): + :param eip_conductor: an EIPConductor object. + :type eip_conductor: EIPConductor + """ QtGui.QWidget.__init__(self, parent) self._systray = None @@ -54,7 +58,8 @@ class EIPStatusWidget(QtGui.QWidget): self.ui = Ui_EIPStatus() self.ui.setupUi(self) - self.eipconnection = EIPConnection() + self.eip_conductor = eip_conductor + self.eipconnection = eip_conductor.eip_connection # set systray tooltip status self._eip_status = "" @@ -75,11 +80,36 @@ class EIPStatusWidget(QtGui.QWidget): self._make_status_clickable() self._provider = "" + self.is_restart = False # Action for the systray self._eip_disabled_action = QtGui.QAction( "{0} is {1}".format(self._service_name, self.tr("disabled")), self) + def connect_backend_signals(self): + """ + Connect backend signals. + """ + signaler = self.eip_conductor._backend.signaler + + signaler.eip_openvpn_already_running.connect( + self._on_eip_openvpn_already_running) + signaler.eip_alien_openvpn_already_running.connect( + self._on_eip_alien_openvpn_already_running) + signaler.eip_openvpn_not_found_error.connect( + self._on_eip_openvpn_not_found_error) + signaler.eip_vpn_launcher_exception.connect( + self._on_eip_vpn_launcher_exception) + signaler.eip_no_polkit_agent_error.connect( + self._on_eip_no_polkit_agent_error) + signaler.eip_no_pkexec_error.connect(self._on_eip_no_pkexec_error) + signaler.eip_no_tun_kext_error.connect(self._on_eip_no_tun_kext_error) + + signaler.eip_state_changed.connect(self.update_vpn_state) + signaler.eip_status_changed.connect(self.update_vpn_status) + signaler.eip_network_unreachable.connect( + self._on_eip_network_unreachable) + def _make_status_clickable(self): """ Makes upload and download figures clickable. @@ -238,7 +268,7 @@ class EIPStatusWidget(QtGui.QWidget): def eip_pre_up(self): """ Triggered when the app activates eip. - Hides the status box and disables the start/stop button. + Disables the start/stop button. """ self.set_startstop_enabled(False) @@ -248,7 +278,7 @@ class EIPStatusWidget(QtGui.QWidget): Triggered when a default provider_config has not been found. Disables the start button and adds instructions to the user. """ - #logger.debug('Hiding EIP start button') + logger.debug('Hiding EIP start button') # you might be tempted to change this for a .setEnabled(False). # it won't work. it's under the claws of the state machine. # probably the best thing would be to make a conditional @@ -326,26 +356,26 @@ class EIPStatusWidget(QtGui.QWidget): Sets the state of the widget to how it should look after EIP has started """ - self.ui.btnEipStartStop.setText(self.tr("Turn OFF")) self.ui.btnEipStartStop.disconnect(self) self.ui.btnEipStartStop.clicked.connect( self.eipconnection.qtsigs.do_connect_signal) - # XXX disable ----------------------------- - def eip_stopped(self): + @QtCore.Slot(dict) + def eip_stopped(self, restart=False): """ + TRIGGERS: + EIPConductor.qtsigs.disconnected_signal + Sets the state of the widget to how it should look after EIP has stopped """ - # XXX should connect this to EIPConnection.disconnected_signal self._reset_traffic_rates() - # XXX disable ----------------------------- - self.ui.btnEipStartStop.setText(self.tr("Turn ON")) - self.ui.btnEipStartStop.disconnect(self) - self.ui.btnEipStartStop.clicked.connect( - self.eipconnection.qtsigs.do_disconnect_signal) - self.ui.eip_bandwidth.hide() + + # XXX FIXME ! ----------------------- this needs to + # accomodate the messages about firewall up or not. + # Or better call it from the conductor... + self.ui.lblEIPMessage.setText( self.tr("Traffic is being routed in the clear")) self.ui.lblEIPStatus.show() @@ -407,7 +437,8 @@ class EIPStatusWidget(QtGui.QWidget): self.ui.lblEIPStatus.hide() # XXX should be handled by the state machine too. - self.eip_connection_connected.emit() + # --- is this currently being sent? + self.eipconnection.qtsigs.connected_signal.emit() # XXX should lookup vpn_state map in EIPConnection elif vpn_state == "AUTH": @@ -423,8 +454,9 @@ class EIPStatusWidget(QtGui.QWidget): elif vpn_state == "ALREADYRUNNING": # Put the following calls in Qt's event queue, otherwise # the UI won't update properly + #self.send_disconnect_signal() QtCore.QTimer.singleShot( - 0, self.eipconnection.qtsigs.do_disconnect_signal) + 0, self.eipconnection.qtsigns.do_disconnect_signal.emit) msg = self.tr("Unable to start VPN, it's already running.") QtCore.QTimer.singleShot(0, partial(self.set_eip_status, msg)) else: @@ -470,3 +502,96 @@ class EIPStatusWidget(QtGui.QWidget): self._provider = provider self.ui.lblEIPMessage.setText( self.tr("Route traffic through: {0}").format(self._provider)) + + # + # Slots for signals + # + + @QtCore.Slot() + def _on_eip_connection_aborted(self): + """ + TRIGGERS: + Signaler.eip_connection_aborted + """ + logger.error("Tried to start EIP but cannot find any " + "available provider!") + + eip_status_label = self.tr("Could not load {0} configuration.") + eip_status_label = eip_status_label.format( + self._eip_conductor.eip_name) + self.set_eip_status(eip_status_label, error=True) + + # signal connection_aborted to state machine: + qtsigs = self._eip_connection.qtsigs + qtsigs.connection_aborted_signal.emit() + + def _on_eip_openvpn_already_running(self): + self.set_eip_status( + self.tr("Another openvpn instance is already running, and " + "could not be stopped."), + error=True) + self.set_eipstatus_off() + + def _on_eip_alien_openvpn_already_running(self): + self.set_eip_status( + self.tr("Another openvpn instance is already running, and " + "could not be stopped because it was not launched by " + "Bitmask. Please stop it and try again."), + error=True) + self.set_eipstatus_off() + + def _on_eip_openvpn_not_found_error(self): + self.set_eip_status( + self.tr("We could not find openvpn binary."), + error=True) + self.set_eipstatus_off() + + def _on_eip_vpn_launcher_exception(self): + # XXX We should implement again translatable exceptions so + # we can pass a translatable string to the panel (usermessage attr) + self.set_eip_status("VPN Launcher error.", error=True) + self.set_eipstatus_off() + + def _on_eip_no_polkit_agent_error(self): + self.set_eip_status( + # XXX this should change to polkit-kde where + # applicable. + self.tr("We could not find any authentication agent in your " + "system.
Make sure you have" + "polkit-gnome-authentication-agent-1 running and" + "try again."), + error=True) + self.set_eipstatus_off() + + def _on_eip_no_pkexec_error(self): + self.set_eip_status( + self.tr("We could not find pkexec in your system."), + error=True) + self.set_eipstatus_off() + + def _on_eip_no_tun_kext_error(self): + self.set_eip_status( + self.tr("{0} cannot be started because the tuntap extension is " + "not installed properly in your " + "system.").format(self._eip_conductor.eip_name)) + self.set_eipstatus_off() + + @QtCore.Slot() + def _on_eip_network_unreachable(self): + """ + TRIGGERS: + self._eip_connection.qtsigs.network_unreachable + + Displays a "network unreachable" error in the EIP status panel. + """ + self.set_eip_status(self.tr("Network is unreachable"), + error=True) + self.set_eip_status_icon("error") + + def set_eipstatus_off(self, error=True): + # XXX this should be handled by the state machine. + """ + Sets eip status to off + """ + self.set_eip_status("", error=error) + self.set_eip_status_icon("error") diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index fd9a70ac..27ef6109 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -30,7 +30,6 @@ from leap.bitmask import __version_hash__ as VERSION_HASH from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings -from leap.bitmask.gui import statemachines from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement from leap.bitmask.gui.eip_preferenceswindow import EIPPreferencesWindow from leap.bitmask.gui.eip_status import EIPStatusWidget @@ -46,12 +45,10 @@ from leap.bitmask.platform_init.initializers import init_platform from leap.bitmask import backend -from leap.bitmask.services import get_service_display_name - +from leap.bitmask.services.eip import conductor as eip_conductor from leap.bitmask.services.mail import conductor as mail_conductor from leap.bitmask.services import EIP_SERVICE, MX_SERVICE -from leap.bitmask.services.eip.connection import EIPConnection from leap.bitmask.util import make_address from leap.bitmask.util.keyring_helpers import has_keyring @@ -68,6 +65,7 @@ from leap.mail.imap.service.imap import IMAP_PORT from ui_mainwindow import Ui_MainWindow +QtDelayedCall = QtCore.QTimer.singleShot logger = logging.getLogger(__name__) @@ -131,11 +129,16 @@ class MainWindow(QtGui.QMainWindow): self._settings = LeapSettings() + # Login Widget self._login_widget = LoginWidget( self._settings, self) self.ui.loginLayout.addWidget(self._login_widget) + # Mail Widget + self._mail_status = MailStatusWidget(self) + self.ui.mailLayout.addWidget(self._mail_status) + # Qt Signal Connections ##################################### # TODO separate logic from ui signals. @@ -144,34 +147,27 @@ class MainWindow(QtGui.QMainWindow): self._login_widget.show_wizard.connect(self._launch_wizard) self._login_widget.logout.connect(self._logout) - self._eip_status = EIPStatusWidget(self) - self.ui.eipLayout.addWidget(self._eip_status) - self._login_widget.logged_in_signal.connect( - self._eip_status.enable_eip_start) - self._login_widget.logged_in_signal.connect( - self._enable_eip_start_action) - - self._mail_status = MailStatusWidget(self) - self.ui.mailLayout.addWidget(self._mail_status) - - self._eip_connection = EIPConnection() + # EIP Control redux ######################################### + self._eip_conductor = eip_conductor.EIPConductor( + self._settings, self._backend) + self._eip_status = EIPStatusWidget(self, self._eip_conductor) - # XXX this should be handled by EIP Conductor - self._eip_connection.qtsigs.connecting_signal.connect( - self._start_EIP) - self._eip_connection.qtsigs.disconnecting_signal.connect( - self._stop_eip) + self.ui.eipLayout.addWidget(self._eip_status) + self._eip_conductor.add_eip_widget(self._eip_status) - self._eip_status.eip_connection_connected.connect( + self._eip_conductor.connect_signals() + self._eip_conductor.qtsigs.connected_signal.connect( self._on_eip_connection_connected) - self._eip_status.eip_connection_connected.connect( + self._eip_conductor.qtsigs.connected_signal.connect( self._maybe_run_soledad_setup_checks) + self.offline_mode_bypass_login.connect( self._maybe_run_soledad_setup_checks) self.eip_needs_login.connect(self._eip_status.disable_eip_start) self.eip_needs_login.connect(self._disable_eip_start_action) + self._already_started_eip = False self._trying_to_start_eip = False self._already_started_eip = False @@ -215,8 +211,7 @@ class MainWindow(QtGui.QMainWindow): self._systray = None - # XXX separate actions into a different - # module. + # XXX separate actions into a different module. self._action_mail_status = QtGui.QAction(self.tr("Mail is OFF"), self) self._mail_status.set_action_mail_status(self._action_mail_status) @@ -280,20 +275,19 @@ class MainWindow(QtGui.QMainWindow): # the EIPConductor or some other clever component that we will # instantiate from here. - self.eip_machine = None # start event machines - self.start_eip_machine() + # TODO should encapsulate all actions into one object + self._eip_conductor.start_eip_machine( + action=self._action_eip_startstop) self._mail_conductor.start_mail_machine() - self._eip_name = get_service_display_name(EIP_SERVICE) - if self._first_run(): self._wizard_firstrun = True self._disconnect_and_untrack() self._wizard = Wizard(backend=self._backend, bypass_checks=bypass_checks) # Give this window time to finish init and then show the wizard - QtCore.QTimer.singleShot(1, self._launch_wizard) + QtDelayedCall(1, self._launch_wizard) self._wizard.accepted.connect(self._finish_init) self._wizard.rejected.connect(self._rejected_wizard) else: @@ -351,67 +345,50 @@ class MainWindow(QtGui.QMainWindow): :type only_tracked: bool """ sig = self._backend.signaler + conntrack = self._connect_and_track + auth_err = self._authentication_error - self._connect_and_track(sig.prov_name_resolution, - self._intermediate_stage) - self._connect_and_track(sig.prov_https_connection, - self._intermediate_stage) - self._connect_and_track(sig.prov_download_ca_cert, - self._intermediate_stage) + conntrack(sig.prov_name_resolution, self._intermediate_stage) + conntrack(sig.prov_https_connection, self._intermediate_stage) + conntrack(sig.prov_download_ca_cert, self._intermediate_stage) + conntrack(sig.prov_download_provider_info, self._load_provider_config) + conntrack(sig.prov_check_api_certificate, self._provider_config_loaded) - self._connect_and_track(sig.prov_download_provider_info, - self._load_provider_config) - self._connect_and_track(sig.prov_check_api_certificate, - self._provider_config_loaded) - - self._connect_and_track(sig.prov_problem_with_provider, - self._login_problem_provider) - - self._connect_and_track(sig.prov_cancelled_setup, - self._set_login_cancelled) + conntrack(sig.prov_problem_with_provider, self._login_problem_provider) + conntrack(sig.prov_cancelled_setup, self._set_login_cancelled) self._connect_and_track(sig.prov_get_details, self._provider_get_details) # Login signals - self._connect_and_track(sig.srp_auth_ok, self._authentication_finished) + conntrack(sig.srp_auth_ok, self._authentication_finished) - auth_error = ( - lambda: self._authentication_error(self.tr("Unknown error."))) - self._connect_and_track(sig.srp_auth_error, auth_error) + auth_error = lambda: auth_err(self.tr("Unknown error.")) + conntrack(sig.srp_auth_error, auth_error) - auth_server_error = ( - lambda: self._authentication_error( - self.tr("There was a server problem with authentication."))) - self._connect_and_track(sig.srp_auth_server_error, auth_server_error) + auth_server_error = lambda: auth_err(self.tr( + "There was a server problem with authentication.")) + conntrack(sig.srp_auth_server_error, auth_server_error) - auth_connection_error = ( - lambda: self._authentication_error( - self.tr("Could not establish a connection."))) - self._connect_and_track(sig.srp_auth_connection_error, - auth_connection_error) + auth_connection_error = lambda: auth_err(self.tr( + "Could not establish a connection.")) + conntrack(sig.srp_auth_connection_error, auth_connection_error) - auth_bad_user_or_password = ( - lambda: self._authentication_error( - self.tr("Invalid username or password."))) - self._connect_and_track(sig.srp_auth_bad_user_or_password, - auth_bad_user_or_password) + auth_bad_user_or_password = lambda: auth_err(self.tr( + "Invalid username or password.")) + conntrack(sig.srp_auth_bad_user_or_password, auth_bad_user_or_password) # Logout signals - self._connect_and_track(sig.srp_logout_ok, self._logout_ok) - self._connect_and_track(sig.srp_logout_error, self._logout_error) - - self._connect_and_track(sig.srp_not_logged_in_error, - self._not_logged_in_error) + conntrack(sig.srp_logout_ok, self._logout_ok) + conntrack(sig.srp_logout_error, self._logout_error) + conntrack(sig.srp_not_logged_in_error, self._not_logged_in_error) # EIP bootstrap signals - self._connect_and_track(sig.eip_config_ready, - self._eip_intermediate_stage) - self._connect_and_track(sig.eip_client_certificate_ready, - self._finish_eip_bootstrap) + conntrack(sig.eip_config_ready, self._eip_intermediate_stage) + conntrack(sig.eip_client_certificate_ready, self._finish_eip_bootstrap) ################################################### - # Add tracked signals above this, untracked bellow! + # Add tracked signals above this, untracked below! ################################################### if only_tracked: return @@ -421,38 +398,20 @@ class MainWindow(QtGui.QMainWindow): sig.backend_bad_call.connect(self._backend_bad_call) sig.prov_check_api_certificate.connect(self._get_provider_details) - sig.prov_unsupported_client.connect(self._needs_update) sig.prov_unsupported_api.connect(self._incompatible_api) + sig.prov_get_all_services.connect(self._provider_get_all_services) - sig.prov_get_all_services.connect( - self._provider_get_all_services) - - # EIP start signals - sig.eip_openvpn_already_running.connect( - self._on_eip_openvpn_already_running) - sig.eip_alien_openvpn_already_running.connect( - self._on_eip_alien_openvpn_already_running) - sig.eip_openvpn_not_found_error.connect( - self._on_eip_openvpn_not_found_error) - sig.eip_vpn_launcher_exception.connect( - self._on_eip_vpn_launcher_exception) - sig.eip_no_polkit_agent_error.connect( - self._on_eip_no_polkit_agent_error) - sig.eip_no_pkexec_error.connect(self._on_eip_no_pkexec_error) - sig.eip_no_tun_kext_error.connect(self._on_eip_no_tun_kext_error) - - sig.eip_state_changed.connect(self._eip_status.update_vpn_state) - sig.eip_status_changed.connect(self._eip_status.update_vpn_status) - sig.eip_process_finished.connect(self._eip_finished) - sig.eip_network_unreachable.connect(self._on_eip_network_unreachable) - sig.eip_process_restart_tls.connect(self._do_eip_restart) - sig.eip_process_restart_ping.connect(self._do_eip_restart) + # EIP start signals ============================================== + self._eip_conductor.connect_backend_signals() sig.eip_can_start.connect(self._backend_can_start_eip) sig.eip_cannot_start.connect(self._backend_cannot_start_eip) + # ================================================================== + # Soledad signals + # TODO delegate connection to soledad bootstrapper sig.soledad_bootstrap_failed.connect( self._mail_status.set_soledad_failed) sig.soledad_bootstrap_finished.connect(self._on_soledad_ready) @@ -919,7 +878,8 @@ class MainWindow(QtGui.QMainWindow): systrayMenu.addAction(self._action_visible) systrayMenu.addSeparator() - eip_status_label = "{0}: {1}".format(self._eip_name, self.tr("OFF")) + eip_status_label = "{0}: {1}".format( + self._eip_conductor.eip_name, self.tr("OFF")) self._eip_menu = eip_menu = systrayMenu.addMenu(eip_status_label) eip_menu.addAction(self._action_eip_startstop) self._eip_status.set_eip_status_menu(eip_menu) @@ -1001,7 +961,7 @@ class MainWindow(QtGui.QMainWindow): # Wait a bit until the window visibility has changed so # the menu is set with the correct value. - QtCore.QTimer.singleShot(500, self._update_hideshow_menu) + QtDelayedCall(500, self._update_hideshow_menu) def _center_window(self): """ @@ -1473,21 +1433,6 @@ class MainWindow(QtGui.QMainWindow): ################################################################### # Service control methods: eip - def start_eip_machine(self): - """ - Initializes and starts the EIP state machine - """ - button = self._eip_status.eip_button - action = self._action_eip_startstop - label = self._eip_status.eip_label - builder = statemachines.ConnectionMachineBuilder(self._eip_connection) - eip_machine = builder.make_machine(button=button, - action=action, - label=label) - self.eip_machine = eip_machine - self.eip_machine.start() - logger.debug('eip machine started') - @QtCore.Slot() def _disable_eip_start_action(self): """ @@ -1506,17 +1451,14 @@ class MainWindow(QtGui.QMainWindow): def _on_eip_connection_connected(self): """ TRIGGERS: - self._eip_status.eip_connection_connected - - Emits the EIPConnection.qtsigs.connected_signal + self._eip_conductor.qtsigs.connected_signal This is a little workaround for connecting the vpn-connected signal that currently is beeing processed under status_panel. After the refactor to EIPConductor this should not be necessary. """ - self._eip_connection.qtsigs.connected_signal.emit() - domain = self._login_widget.get_selected_provider() + self._eip_status.set_provider(domain) self._settings.set_defaultprovider(domain) self._already_started_eip = True @@ -1525,6 +1467,9 @@ class MainWindow(QtGui.QMainWindow): self._check_name_resolution(domain) def _check_name_resolution(self, domain): + # FIXME this has to be moved to backend !!! + # Should move to netchecks module. + # and separate qt from reactor... """ Check if we can resolve the given domain name. @@ -1555,7 +1500,7 @@ class MainWindow(QtGui.QMainWindow): "missing some helper files that are needed to securely use " "DNS while {1} is active. To install these helper files, quit " "this application and start it again." - ).format(domain, self._eip_name) + ).format(domain, self._eip_conductor.eip_name) show_err = lambda: QtGui.QMessageBox.critical( self, self.tr("Connection Error"), msg) @@ -1580,211 +1525,6 @@ class MainWindow(QtGui.QMainWindow): if settings.get_autostart_eip(): self._maybe_start_eip(autostart=True) - @QtCore.Slot() - def _start_EIP(self): - """ - Starts EIP - """ - self._eip_status.eip_pre_up() - self.user_stopped_eip = False - - # Until we set an option in the preferences window, we'll assume that - # by default we try to autostart. If we switch it off manually, it - # won't try the next time. - self._settings.set_autostart_eip(True) - - self._backend.eip_start() - - @QtCore.Slot() - def _on_eip_connection_aborted(self): - """ - TRIGGERS: - Signaler.eip_connection_aborted - """ - logger.error("Tried to start EIP but cannot find any " - "available provider!") - - eip_status_label = self.tr("Could not load {0} configuration.") - eip_status_label = eip_status_label.format(self._eip_name) - self._eip_status.set_eip_status(eip_status_label, error=True) - - # signal connection_aborted to state machine: - qtsigs = self._eip_connection.qtsigs - qtsigs.connection_aborted_signal.emit() - - def _on_eip_openvpn_already_running(self): - self._eip_status.set_eip_status( - self.tr("Another openvpn instance is already running, and " - "could not be stopped."), - error=True) - self._set_eipstatus_off() - - def _on_eip_alien_openvpn_already_running(self): - self._eip_status.set_eip_status( - self.tr("Another openvpn instance is already running, and " - "could not be stopped because it was not launched by " - "Bitmask. Please stop it and try again."), - error=True) - self._set_eipstatus_off() - - def _on_eip_openvpn_not_found_error(self): - self._eip_status.set_eip_status( - self.tr("We could not find openvpn binary."), - error=True) - self._set_eipstatus_off() - - def _on_eip_vpn_launcher_exception(self): - # XXX We should implement again translatable exceptions so - # we can pass a translatable string to the panel (usermessage attr) - self._eip_status.set_eip_status("VPN Launcher error.", error=True) - self._set_eipstatus_off() - - def _on_eip_no_polkit_agent_error(self): - self._eip_status.set_eip_status( - # XXX this should change to polkit-kde where - # applicable. - self.tr("We could not find any authentication agent in your " - "system.
Make sure you have" - "polkit-gnome-authentication-agent-1 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 pkexec in your system."), - error=True) - self._set_eipstatus_off() - - def _on_eip_no_tun_kext_error(self): - self._eip_status.set_eip_status( - self.tr("{0} cannot be started because the tuntap extension is " - "not installed properly in your " - "system.").format(self._eip_name)) - self._set_eipstatus_off() - - @QtCore.Slot() - def _stop_eip(self, restart=False): - """ - TRIGGERS: - self._eip_connection.qtsigs.do_disconnect_signal (via state machine) - - Stops vpn process and makes gui adjustments to reflect - the change of state. - - :param abnormal: whether this was an abnormal termination. - :type abnormal: bool - """ - self.user_stopped_eip = not restart - self._backend.eip_stop(restart=restart) - - self._set_eipstatus_off(False) - self._already_started_eip = False - - logger.debug('Setting autostart to: False') - self._settings.set_autostart_eip(False) - - user = self._logged_user - if user: - domain = self._login_widget.get_selected_provider() - full_user_id = make_address(user, domain) - self._eip_status.set_provider(full_user_id) - - self._eip_status.eip_stopped() - - @QtCore.Slot() - def _on_eip_network_unreachable(self): - # XXX Should move to EIP Conductor - """ - TRIGGERS: - self._eip_connection.qtsigs.network_unreachable - - Displays a "network unreachable" error in the EIP status panel. - """ - self._eip_status.set_eip_status(self.tr("Network is unreachable"), - error=True) - self._eip_status.set_eip_status_icon("error") - - @QtCore.Slot() - def _do_eip_restart(self): - # XXX Should move to EIP Conductor - """ - TRIGGERS: - self._eip_connection.qtsigs.process_restart - - Restart the connection. - """ - # for some reason, emitting the do_disconnect/do_connect - # signals hangs the UI. - self._stop_eip(restart=True) - QtCore.QTimer.singleShot(2000, self._start_EIP) - - def _set_eipstatus_off(self, error=True): - """ - Sets eip status to off - """ - # XXX this should be handled by the state machine. - self._eip_status.set_eip_status("", error=error) - self._eip_status.set_eip_status_icon("error") - - @QtCore.Slot(int) - def _eip_finished(self, exitCode): - """ - TRIGGERS: - Signaler.eip_process_finished - - Triggered when the EIP/VPN process finishes to set the UI - accordingly. - - Ideally we would have the right exit code here, - but the use of different wrappers (pkexec, cocoasudo) swallows - the openvpn exit code so we get zero exit in some cases where we - shouldn't. As a workaround we just use a flag to indicate - a purposeful switch off, and mark everything else as unexpected. - - In the near future we should trigger a native notification from here, - since the user really really wants to know she is unprotected asap. - And the right thing to do will be to fail-close. - - :param exitCode: the exit code of the eip process. - :type exitCode: int - """ - # TODO move to EIPConductor. - # TODO Add error catching to the openvpn log observer - # so we can have a more precise idea of which type - # of error did we have (server side, local problem, etc) - - logger.info("VPN process finished with exitCode %s..." - % (exitCode,)) - - qtsigs = self._eip_connection.qtsigs - signal = qtsigs.disconnected_signal - - # XXX check if these exitCodes are pkexec/cocoasudo specific - if exitCode in (126, 127): - eip_status_label = self.tr( - "{0} could not be launched " - "because you did not authenticate properly.") - eip_status_label = eip_status_label.format(self._eip_name) - self._eip_status.set_eip_status(eip_status_label, error=True) - signal = qtsigs.connection_aborted_signal - self._backend.eip_terminate() - - elif exitCode != 0 or not self.user_stopped_eip: - eip_status_label = self.tr("{0} finished in an unexpected manner!") - eip_status_label = eip_status_label.format(self._eip_name) - self._eip_status.eip_stopped() - self._eip_status.set_eip_status_icon("error") - self._eip_status.set_eip_status(eip_status_label, error=True) - signal = qtsigs.connection_died_signal - - if exitCode == 0 and IS_MAC: - # XXX remove this warning after I fix cocoasudo. - logger.warning("The above exit code MIGHT BE WRONG.") - - # We emit signals to trigger transitions in the state machine: - signal.emit() - # eip boostrapping, config etc... def _maybe_start_eip(self, autostart=False): @@ -1812,9 +1552,8 @@ class MainWindow(QtGui.QMainWindow): self._already_started_eip = True # we want to start soledad anyway after a certain timeout if eip # fails to come up - QtCore.QTimer.singleShot( - self.EIP_START_TIMEOUT, - self._maybe_run_soledad_setup_checks) + QtDelayedCall(self.EIP_START_TIMEOUT, + self._maybe_run_soledad_setup_checks) else: if not self._already_started_eip: if EIP_SERVICE in self._enabled_services: @@ -1833,8 +1572,8 @@ class MainWindow(QtGui.QMainWindow): TRIGGERS: self._backend.signaler.eip_client_certificate_ready - Starts the VPN thread if the eip configuration is properly - loaded + Start the VPN thread if the eip configuration is properly + loaded. """ passed = data[self._backend.PASSED_KEY] @@ -1846,11 +1585,11 @@ class MainWindow(QtGui.QMainWindow): return # DO START EIP Connection! - self._eip_connection.qtsigs.do_connect_signal.emit() + self._eip_conductor.do_connect() @QtCore.Slot(dict) def _eip_intermediate_stage(self, data): - # TODO missing param + # TODO missing param documentation """ TRIGGERS: self._backend.signaler.eip_config_ready diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py index 31938a70..f8e5479d 100644 --- a/src/leap/bitmask/gui/statemachines.py +++ b/src/leap/bitmask/gui/statemachines.py @@ -504,6 +504,11 @@ class ConnectionMachineBuilder(object): conn.qtsigs.connection_died_signal, states[_OFF]) + # XXX adding this--------------------- + states[_ON].addTransition( + conn.qtsigs.do_disconnect_signal, + states[_DIS]) + # * If we receive the connection_aborted, we transition # from connecting to the off state states[_CON].addTransition( @@ -551,7 +556,8 @@ class ConnectionMachineBuilder(object): # TODO add tooltip # OFF State ---------------------- - off = QState() + off = SignallingState( + None, name=conn.name) off_label = _tr("Turn {0}").format( conn.Connected.short_label) if button: @@ -587,7 +593,10 @@ class ConnectionMachineBuilder(object): states[_CON] = connecting # ON State ------------------------ - on = QState() + on = SignallingState( + None, name=conn.name) + on_label = _tr("Turn {0}").format( + conn.Disconnected.short_label) if button: on.assignProperty( button, 'text', on_label) diff --git a/src/leap/bitmask/services/eip/conductor.py b/src/leap/bitmask/services/eip/conductor.py new file mode 100644 index 00000000..253f76c0 --- /dev/null +++ b/src/leap/bitmask/services/eip/conductor.py @@ -0,0 +1,269 @@ +# -*- coding: utf-8 -*- +# conductor.py +# Copyright (C) 2014 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +EIP Conductor module. +""" +import logging + +from PySide import QtCore + +from leap.bitmask.gui import statemachines +from leap.bitmask.services import EIP_SERVICE +from leap.bitmask.services import get_service_display_name +from leap.bitmask.services.eip.connection import EIPConnection +from leap.bitmask.platform_init import IS_MAC +from leap.bitmask.util import make_address + +QtDelayedCall = QtCore.QTimer.singleShot +logger = logging.getLogger(__name__) + + +class EIPConductor(object): + + def __init__(self, settings, backend, **kwargs): + """ + Initializes EIP Conductor. + + :param settings: + :type settings: + + :param backend: + :type backend: + """ + self.eip_connection = EIPConnection() + self.eip_name = get_service_display_name(EIP_SERVICE) + self._settings = settings + self._backend = backend + + self._eip_status = None + + @property + def qtsigs(self): + return self.eip_connection.qtsigs + + def add_eip_widget(self, widget): + """ + Keep a reference to the passed eip status widget. + + :param widget: the EIP Status widget. + :type widget: QWidget + """ + self._eip_status = widget + + def connect_signals(self): + """ + Connect signals. + """ + self.qtsigs.connecting_signal.connect(self._start_eip) + self.qtsigs.disconnecting_signal.connect(self._stop_eip) + self.qtsigs.disconnected_signal.connect(self._eip_status.eip_stopped) + + def connect_backend_signals(self): + """ + Connect to backend signals. + """ + signaler = self._backend.signaler + + # for conductor + signaler.eip_process_restart_tls.connect(self._do_eip_restart) + signaler.eip_process_restart_ping.connect(self._do_eip_restart) + signaler.eip_process_finished.connect(self._eip_finished) + + # for widget + self._eip_status.connect_backend_signals() + + def start_eip_machine(self, action): + """ + Initializes and starts the EIP state machine. + Needs the reference to the eip_status widget not to be empty. + + :action: QtAction + """ + action = action + button = self._eip_status.eip_button + label = self._eip_status.eip_label + + builder = statemachines.ConnectionMachineBuilder(self.eip_connection) + eip_machine = builder.make_machine(button=button, + action=action, + label=label) + self.eip_machine = eip_machine + self.eip_machine.start() + logger.debug('eip machine started') + + def do_connect(self): + """ + Start the connection procedure. + Emits a signal that triggers the OFF -> Connecting sequence. + This will call _start_eip via the state machine. + """ + self.qtsigs.do_connect_signal.emit() + + @QtCore.Slot() + def _start_eip(self): + """ + Starts EIP. + """ + # FIXME --- pass is_restart parameter to here ??? + is_restart = self._eip_status and self._eip_status.is_restart + + def reconnect(): + self.qtsigs.disconnecting_signal.connect(self._stop_eip) + + if is_restart: + QtDelayedCall(0, reconnect) + else: + self._eip_status.eip_pre_up() + self.user_stopped_eip = False + + # Until we set an option in the preferences window, we'll assume that + # by default we try to autostart. If we switch it off manually, it + # won't try the next time. + self._settings.set_autostart_eip(True) + self._eip_status.is_restart = False + + # DO the backend call! + self._backend.eip_start() + + @QtCore.Slot() + def _stop_eip(self, restart=False): + """ + TRIGGERS: + self.qsigs.do_disconnect_signal (via state machine) + + Stops vpn process and makes gui adjustments to reflect + the change of state. + + :param restart: whether this is part of a eip restart. + :type restart: bool + """ + self._eip_status.is_restart = restart + self.user_stopped_eip = not restart + + def reconnect_stop_signal(): + self.qtsigs.disconnecting_signal.connect(self._stop_eip) + + def on_disconnected_do_restart(): + # hard restarts + eip_status_label = self._eip_status.tr("{0} is restarting") + eip_status_label = eip_status_label.format(self.eip_name) + self._eip_status.eip_stopped(restart=True) + self._eip_status.set_eip_status(eip_status_label, error=False) + + QtDelayedCall(1000, self.qtsigs.do_connect_signal.emit) + + if restart: + # we bypass the on_eip_disconnected here + #qtsigs.disconnected_signal.disconnect() + self.qtsigs.disconnected_signal.connect(on_disconnected_do_restart) + QtDelayedCall(0, self.qtsigs.disconnected_signal.emit) + + # Call to the backend. + self._backend.eip_stop(restart=restart) + + self._eip_status.set_eipstatus_off(False) + self._already_started_eip = False + + logger.debug('Setting autostart to: False') + self._settings.set_autostart_eip(False) + + if self._logged_user: + self._eip_status.set_provider( + make_address( + self._logged_user, + self._get_best_provider_config().get_domain())) + + self._eip_status.eip_stopped(restart=restart) + + if restart: + QtDelayedCall(50000, reconnect_stop_signal) + + @QtCore.Slot() + def _do_eip_restart(self): + """ + TRIGGERS: + self._eip_connection.qtsigs.process_restart + + Restart the connection. + """ + if self._eip_status is not None: + self._eip_status.is_restart = True + try: + self.qtsigs.disconnecting_signal.disconnect() + except Exception: + logger.error("cannot disconnect signals") + + def do_stop(*args): + self._stop_eip(restart=True) + + self.qtsigs.disconnecting_signal.connect(do_stop) + self.qtsigs.do_disconnect_signal.emit() + + @QtCore.Slot(int) + def _eip_finished(self, exitCode): + """ + TRIGGERS: + Signaler.eip_process_finished + + Triggered when the EIP/VPN process finishes to set the UI + accordingly. + + Ideally we would have the right exit code here, + but the use of different wrappers (pkexec, cocoasudo) swallows + the openvpn exit code so we get zero exit in some cases where we + shouldn't. As a workaround we just use a flag to indicate + a purposeful switch off, and mark everything else as unexpected. + + :param exitCode: the exit code of the eip process. + :type exitCode: int + """ + # TODO Add error catching to the openvpn log observer + # so we can have a more precise idea of which type + # of error did we have (server side, local problem, etc) + + logger.info("VPN process finished with exitCode %s..." + % (exitCode,)) + + signal = self.qtsigs.disconnected_signal + + # XXX check if these exitCodes are pkexec/cocoasudo specific + if exitCode in (126, 127): + eip_status_label = self._eip_status.tr( + "{0} could not be launched " + "because you did not authenticate properly.") + eip_status_label = eip_status_label.format(self.eip_name) + self._eip_status.set_eip_status(eip_status_label, error=True) + signal = self.qtsigs.connection_aborted_signal + self._backend.eip_terminate() + + # XXX FIXME --- check exitcode is != 0 really + if exitCode != 0 and not self.user_stopped_eip: + eip_status_label = self._eip_status.tr( + "{0} finished in an unexpected manner!") + eip_status_label = eip_status_label.format(self.eip_name) + self._eip_status.eip_stopped() + self._eip_status.set_eip_status_icon("error") + self._eip_status.set_eip_status(eip_status_label, + error=True) + signal = self.qtsigs.connection_died_signal + + if exitCode == 0 and IS_MAC: + # XXX remove this warning after I fix cocoasudo. + logger.warning("The above exit code MIGHT BE WRONG.") + + # We emit signals to trigger transitions in the state machine: + signal.emit() diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index b068066f..6eef05af 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -116,10 +116,12 @@ class VPNObserver(object): :returns: a Signaler signal or None :rtype: str or None """ + sig = self._signaler signals = { - "network_unreachable": self._signaler.EIP_NETWORK_UNREACHABLE, - "process_restart_tls": self._signaler.EIP_PROCESS_RESTART_TLS, - "process_restart_ping": self._signaler.EIP_PROCESS_RESTART_PING, + "network_unreachable": sig.EIP_NETWORK_UNREACHABLE, + "process_restart_tls": sig.EIP_PROCESS_RESTART_TLS, + "process_restart_ping": sig.EIP_PROCESS_RESTART_PING, + "initialization_completed": sig.EIP_CONNECTED } return signals.get(event.lower()) @@ -318,6 +320,7 @@ class VPN(object): # We assume that the only valid stops are initiated # by an user action, not hard restarts self._user_stopped = not restart + self._vpnproc.is_restart = restart # First we try to be polite and send a SIGTERM... if self._vpnproc: @@ -755,7 +758,7 @@ class VPNManager(object): # However, that should be a rare case right now. self._send_command("signal SIGTERM") self._close_management_socket(announce=True) - except Exception as e: + except (Exception, AssertionError) as e: logger.warning("Problem trying to terminate OpenVPN: %r" % (e,)) else: @@ -824,6 +827,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): self._openvpn_verb = openvpn_verb self._vpn_observer = VPNObserver(signaler) + self.is_restart = False # processProtocol methods @@ -859,7 +863,8 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): exit_code = reason.value.exitCode if isinstance(exit_code, int): logger.debug("processExited, status %d" % (exit_code,)) - self._signaler.signal(self._signaler.EIP_PROCESS_FINISHED, exit_code) + self._signaler.signal( + self._signaler.EIP_PROCESS_FINISHED, exit_code) self._alive = False def processEnded(self, reason): -- cgit v1.2.3 From b85d6ed72460f15b6029f36ccfef3bccd57e530c Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 28 May 2014 16:29:42 -0500 Subject: fix non-existing logged_user caused by a bad rebase. --- src/leap/bitmask/gui/mainwindow.py | 21 +++++++++++ src/leap/bitmask/gui/statemachines.py | 4 +++ src/leap/bitmask/services/eip/conductor.py | 58 ++++++++++++++++++------------ 3 files changed, 61 insertions(+), 22 deletions(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 27ef6109..aa317d96 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -158,6 +158,8 @@ class MainWindow(QtGui.QMainWindow): self._eip_conductor.connect_signals() self._eip_conductor.qtsigs.connected_signal.connect( self._on_eip_connection_connected) + self._eip_conductor.qtsigs.disconnected_signal.connect( + self._on_eip_connection_disconnected) self._eip_conductor.qtsigs.connected_signal.connect( self._maybe_run_soledad_setup_checks) @@ -1466,6 +1468,25 @@ class MainWindow(QtGui.QMainWindow): # check for connectivity self._check_name_resolution(domain) + @QtCore.Slot() + def _on_eip_connection_disconnected(self): + """ + TRIGGERS: + self._eip_conductor.qtsigs.disconnected_signal + + Workaround for updating the eip_status widget with + the provider when the eip connection disconnects. + """ + # TODO + # We should move this to the conductor<->widget interface. + # To do that, we need to subscribe to logged_user, + # for example by using the observer pattern or a proxy object. + user = self._logged_user + if user: + domain = self._login_widget.get_selected_provider() + full_user_id = make_address(user, domain) + self._eip_status.set_provider(full_user_id) + def _check_name_resolution(self, domain): # FIXME this has to be moved to backend !!! # Should move to netchecks module. diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py index f8e5479d..2a876b83 100644 --- a/src/leap/bitmask/gui/statemachines.py +++ b/src/leap/bitmask/gui/statemachines.py @@ -55,6 +55,10 @@ class SignallingState(QState): """ Emits the signal on entry. """ + print "IIIIIIIIIIIIIIIIIII" + print "TO STATE", self._name, self.objectName() + print "IIIIIIIIIIIIIIIIIII" + logger.debug('State %s::%s entered. Emitting signal ...' % (self._name, self.objectName())) if self._signal is not None: diff --git a/src/leap/bitmask/services/eip/conductor.py b/src/leap/bitmask/services/eip/conductor.py index 253f76c0..7cc80014 100644 --- a/src/leap/bitmask/services/eip/conductor.py +++ b/src/leap/bitmask/services/eip/conductor.py @@ -69,6 +69,7 @@ class EIPConductor(object): Connect signals. """ self.qtsigs.connecting_signal.connect(self._start_eip) + self.qtsigs.disconnecting_signal.connect(self._stop_eip) self.qtsigs.disconnected_signal.connect(self._eip_status.eip_stopped) @@ -154,43 +155,55 @@ class EIPConductor(object): self._eip_status.is_restart = restart self.user_stopped_eip = not restart - def reconnect_stop_signal(): - self.qtsigs.disconnecting_signal.connect(self._stop_eip) - def on_disconnected_do_restart(): # hard restarts + logger.debug("HARD RESTART") eip_status_label = self._eip_status.tr("{0} is restarting") eip_status_label = eip_status_label.format(self.eip_name) self._eip_status.eip_stopped(restart=True) self._eip_status.set_eip_status(eip_status_label, error=False) - QtDelayedCall(1000, self.qtsigs.do_connect_signal.emit) + QtDelayedCall(2000, self.do_connect) + + def plug_restart_on_disconnected(): + #print "PLUGGING RESTART ON DISCONNECTED" + self.qtsigs.disconnected_signal.connect(on_disconnected_do_restart) + + def reconnect_disconnected_signal(): + #print "RECONNECTING DISCONNECTED SIGNAL" + self.qtsigs.disconnected_signal.disconnect( + on_disconnected_do_restart) + + def do_stop(*args): + self._stop_eip(restart=False) + + def reconnect_stop_signal(): + #print "RECONNECTING StOP SIGNAL" + self.qtsigs.disconnecting_signal.disconnect() + self.qtsigs.disconnecting_signal.connect(do_stop) if restart: # we bypass the on_eip_disconnected here - #qtsigs.disconnected_signal.disconnect() - self.qtsigs.disconnected_signal.connect(on_disconnected_do_restart) - QtDelayedCall(0, self.qtsigs.disconnected_signal.emit) + plug_restart_on_disconnected() + self.qtsigs.disconnected_signal.emit() + #QtDelayedCall(0, self.qtsigs.disconnected_signal.emit) + # ...and reconnect the original signal again, after having used the + # diversion + QtDelayedCall(500, reconnect_disconnected_signal) + else: + logger.debug('Setting autostart to: False') + self._settings.set_autostart_eip(False) # Call to the backend. self._backend.eip_stop(restart=restart) - self._eip_status.set_eipstatus_off(False) self._already_started_eip = False - - logger.debug('Setting autostart to: False') - self._settings.set_autostart_eip(False) - - if self._logged_user: - self._eip_status.set_provider( - make_address( - self._logged_user, - self._get_best_provider_config().get_domain())) - + self._eip_status.set_eipstatus_off(False) self._eip_status.eip_stopped(restart=restart) + # XXX needed? if restart: - QtDelayedCall(50000, reconnect_stop_signal) + QtDelayedCall(3000, reconnect_stop_signal) @QtCore.Slot() def _do_eip_restart(self): @@ -202,14 +215,15 @@ class EIPConductor(object): """ if self._eip_status is not None: self._eip_status.is_restart = True + + def do_stop(*args): + self._stop_eip(restart=True) + try: self.qtsigs.disconnecting_signal.disconnect() except Exception: logger.error("cannot disconnect signals") - def do_stop(*args): - self._stop_eip(restart=True) - self.qtsigs.disconnecting_signal.connect(do_stop) self.qtsigs.do_disconnect_signal.emit() -- cgit v1.2.3 From 2174acd89500ebad2c9b3df034d276e0e857b6f8 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 29 May 2014 12:44:56 -0500 Subject: change eip status during restarts --- src/leap/bitmask/gui/eip_status.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 9da231f2..ca209d6a 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -373,11 +373,19 @@ class EIPStatusWidget(QtGui.QWidget): self.ui.eip_bandwidth.hide() # XXX FIXME ! ----------------------- this needs to - # accomodate the messages about firewall up or not. + # accomodate the messages about firewall status. Right now + # we're assuming it works correctly, but we should test fw + # status positively. # Or better call it from the conductor... - self.ui.lblEIPMessage.setText( - self.tr("Traffic is being routed in the clear")) + clear_traffic = self.tr("Traffic is being routed in the clear") + unreachable_net = self.tr("Network is unreachable") + + if restart: + msg = unreachable_net + else: + msg = clear_traffic + self.ui.lblEIPMessage.setText(msg) self.ui.lblEIPStatus.show() @QtCore.Slot(dict) -- cgit v1.2.3 From 5f6e44241b4858f1c407f0babfc41f40a06405f1 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 29 May 2014 12:45:46 -0500 Subject: display restart error after SIGTERM[soft,tls-error] - fix tls-error: is SIGTERM now - connect to connection-died signal - display error to user --- src/leap/bitmask/backend.py | 2 +- src/leap/bitmask/gui/eip_status.py | 21 ++++++++++++++---- src/leap/bitmask/gui/mainwindow.py | 2 +- src/leap/bitmask/gui/statemachines.py | 4 ---- src/leap/bitmask/services/eip/conductor.py | 33 +++++++++++++++++++++++------ src/leap/bitmask/services/eip/vpnprocess.py | 2 +- 6 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index f0fe50f9..1ab5b40d 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -1718,7 +1718,7 @@ class Backend(object): """ self._call_queue.put(("eip", "start", None)) - def eip_stop(self, shutdown=False, restart=False): + def eip_stop(self, shutdown=False, restart=False, failed=False): """ Stop the EIP service. diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index ca209d6a..04acc167 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -361,7 +361,7 @@ class EIPStatusWidget(QtGui.QWidget): self.eipconnection.qtsigs.do_connect_signal) @QtCore.Slot(dict) - def eip_stopped(self, restart=False): + def eip_stopped(self, restart=False, failed=False): """ TRIGGERS: EIPConductor.qtsigs.disconnected_signal @@ -378,16 +378,29 @@ class EIPStatusWidget(QtGui.QWidget): # status positively. # Or better call it from the conductor... - clear_traffic = self.tr("Traffic is being routed in the clear") - unreachable_net = self.tr("Network is unreachable") + clear_traffic = self.tr("Traffic is being routed in the clear.") + unreachable_net = self.tr("Network is unreachable.") + failed_msg = self.tr("Cannot start Encrypted Proxy.") if restart: msg = unreachable_net + elif failed: + msg = failed_msg else: msg = clear_traffic self.ui.lblEIPMessage.setText(msg) self.ui.lblEIPStatus.show() + def eip_failed_to_restart(self): + """ + Update EIP messages. + """ + msg = self.tr("Could not restart Encrypted Proxy") + self.ui.lblEIPMessage.setText(msg) + self.ui.lblEIPStatus.show() + + self.set_eip_status(self.tr("You can start the service manually.")) + @QtCore.Slot(dict) def update_vpn_status(self, data=None): """ @@ -530,7 +543,7 @@ class EIPStatusWidget(QtGui.QWidget): self.set_eip_status(eip_status_label, error=True) # signal connection_aborted to state machine: - qtsigs = self._eip_connection.qtsigs + qtsigs = self._eipconnection.qtsigs qtsigs.connection_aborted_signal.emit() def _on_eip_openvpn_already_running(self): diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index aa317d96..98d9fb8b 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1764,7 +1764,7 @@ class MainWindow(QtGui.QMainWindow): if self._systray is not None: self._systray.showMessage( self.tr('Quitting...'), - self.tr('The app is quitting, please wait.')) + self.tr('Bitmask is quitting, please wait.')) # explicitly process events to display tooltip immediately QtCore.QCoreApplication.processEvents(0, 10) diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py index 2a876b83..f8e5479d 100644 --- a/src/leap/bitmask/gui/statemachines.py +++ b/src/leap/bitmask/gui/statemachines.py @@ -55,10 +55,6 @@ class SignallingState(QState): """ Emits the signal on entry. """ - print "IIIIIIIIIIIIIIIIIII" - print "TO STATE", self._name, self.objectName() - print "IIIIIIIIIIIIIIIIIII" - logger.debug('State %s::%s entered. Emitting signal ...' % (self._name, self.objectName())) if self._signal is not None: diff --git a/src/leap/bitmask/services/eip/conductor.py b/src/leap/bitmask/services/eip/conductor.py index 7cc80014..0cd4c95c 100644 --- a/src/leap/bitmask/services/eip/conductor.py +++ b/src/leap/bitmask/services/eip/conductor.py @@ -81,6 +81,7 @@ class EIPConductor(object): # for conductor signaler.eip_process_restart_tls.connect(self._do_eip_restart) + signaler.eip_process_restart_tls.connect(self._do_eip_failed) signaler.eip_process_restart_ping.connect(self._do_eip_restart) signaler.eip_process_finished.connect(self._eip_finished) @@ -141,7 +142,7 @@ class EIPConductor(object): self._backend.eip_start() @QtCore.Slot() - def _stop_eip(self, restart=False): + def _stop_eip(self, restart=False, failed=False): """ TRIGGERS: self.qsigs.do_disconnect_signal (via state machine) @@ -151,9 +152,12 @@ class EIPConductor(object): :param restart: whether this is part of a eip restart. :type restart: bool + + :param failed: whether this is the final step of a retry sequence + :type failed: bool """ self._eip_status.is_restart = restart - self.user_stopped_eip = not restart + self.user_stopped_eip = not restart and not failed def on_disconnected_do_restart(): # hard restarts @@ -166,11 +170,9 @@ class EIPConductor(object): QtDelayedCall(2000, self.do_connect) def plug_restart_on_disconnected(): - #print "PLUGGING RESTART ON DISCONNECTED" self.qtsigs.disconnected_signal.connect(on_disconnected_do_restart) def reconnect_disconnected_signal(): - #print "RECONNECTING DISCONNECTED SIGNAL" self.qtsigs.disconnected_signal.disconnect( on_disconnected_do_restart) @@ -178,7 +180,6 @@ class EIPConductor(object): self._stop_eip(restart=False) def reconnect_stop_signal(): - #print "RECONNECTING StOP SIGNAL" self.qtsigs.disconnecting_signal.disconnect() self.qtsigs.disconnecting_signal.connect(do_stop) @@ -190,6 +191,10 @@ class EIPConductor(object): # ...and reconnect the original signal again, after having used the # diversion QtDelayedCall(500, reconnect_disconnected_signal) + + elif failed: + self.qtsigs.disconnected_signal.emit() + else: logger.debug('Setting autostart to: False') self._settings.set_autostart_eip(False) @@ -197,9 +202,11 @@ class EIPConductor(object): # Call to the backend. self._backend.eip_stop(restart=restart) - self._already_started_eip = False + # ... and inform the status widget self._eip_status.set_eipstatus_off(False) - self._eip_status.eip_stopped(restart=restart) + self._eip_status.eip_stopped(restart=restart, failed=failed) + + self._already_started_eip = False # XXX needed? if restart: @@ -227,6 +234,18 @@ class EIPConductor(object): self.qtsigs.disconnecting_signal.connect(do_stop) self.qtsigs.do_disconnect_signal.emit() + @QtCore.Slot() + def _do_eip_failed(self): + """ + Stop EIP after a failure to start. + + TRIGGERS + signaler.eip_process_restart_tls + """ + logger.debug("TLS Error: eip_stop (failed)") + self.qtsigs.connection_died_signal.emit() + QtDelayedCall(1000, self._eip_status.eip_failed_to_restart) + @QtCore.Slot(int) def _eip_finished(self, exitCode): """ diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 6eef05af..098619be 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -70,7 +70,7 @@ class VPNObserver(object): 'NETWORK_UNREACHABLE': ( 'Network is unreachable (code=101)',), 'PROCESS_RESTART_TLS': ( - "SIGUSR1[soft,tls-error]",), + "SIGTERM[soft,tls-error]",), 'PROCESS_RESTART_PING': ( "SIGTERM[soft,ping-restart]",), 'INITIALIZATION_COMPLETED': ( -- cgit v1.2.3 From efea398e18f806ad5bb7cec4aa0dcef5f94319bc Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 29 May 2014 12:49:44 -0500 Subject: rephrase --- src/leap/bitmask/gui/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 98d9fb8b..1d467e60 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1360,7 +1360,7 @@ class MainWindow(QtGui.QMainWindow): """ # TODO split. if not self._provides_mx_and_enabled() and not flags.OFFLINE: - logger.debug("Does not provides and enabled MX") + logger.debug("Provider does not offer MX, but it is enabled.") return username = self._login_widget.get_user() -- cgit v1.2.3 From 1ef424fcd34d1f3800ffd200be72d775be5a9740 Mon Sep 17 00:00:00 2001 From: elijah Date: Thu, 29 May 2014 01:23:53 -0700 Subject: unblock local multicast IPs from linux firewall, to allow SSDP and Bonjour/mDNS to work. --- changes/allow-local-multicast-in-firewall | 1 + pkg/linux/bitmask-root | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 changes/allow-local-multicast-in-firewall diff --git a/changes/allow-local-multicast-in-firewall b/changes/allow-local-multicast-in-firewall new file mode 100644 index 00000000..160cabb3 --- /dev/null +++ b/changes/allow-local-multicast-in-firewall @@ -0,0 +1 @@ +- unblock local multicast IPs from linux firewall, to allow SSDP and Bonjour/mDNS to work. \ No newline at end of file diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 6d296ecf..f1c5c0c3 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -740,6 +740,11 @@ def firewall_start(args): iptables("--insert", BITMASK_CHAIN, "-o", default_device, "--jump", "REJECT") + # log rejected packets to syslog + if DEBUG: + iptables("--insert", BITMASK_CHAIN, "-o", default_device, + "--jump", "LOG", "--log-prefix", "iptables denied: ", "--log-level", "7") + # allow traffic to gateways for gateway in gateways: ip4tables("--insert", BITMASK_CHAIN, "--destination", gateway, @@ -750,10 +755,27 @@ def firewall_start(args): ip4tables("--insert", BITMASK_CHAIN, "--destination", local_network_ipv4, "-o", default_device, "--jump", "ACCEPT") + # allow multicast Simple Service Discovery Protocol + ip4tables("--insert", BITMASK_CHAIN, + "--protocol", "udp", "--destination", "239.255.255.250", "--dport", "1900", + "-o", default_device, "--jump", "ACCEPT") + # allow multicast Bonjour/mDNS + ip4tables("--insert", BITMASK_CHAIN, + "--protocol", "udp", "--destination", "224.0.0.251", "--dport", "5353", + "-o", default_device, "--jump", "ACCEPT") if local_network_ipv6: ip6tables("--insert", BITMASK_CHAIN, "--destination", local_network_ipv6, "-o", default_device, "--jump", "ACCEPT") + # allow multicast Simple Service Discovery Protocol + ip6tables("--insert", BITMASK_CHAIN, + "--protocol", "udp", "--destination", "FF05::C", "--dport", "1900", + "-o", default_device, "--jump", "ACCEPT") + # allow multicast Bonjour/mDNS + ip6tables("--insert", BITMASK_CHAIN, + "--protocol", "udp", "--destination", "FF02::FB", "--dport", "5353", + "-o", default_device, "--jump", "ACCEPT") + # block DNS requests to anyone but the service provider or localhost # when we actually route ipv6, we will need dns rules for it too -- cgit v1.2.3 From 1417c41e05a3afe79555950921c2bc6289bf02ea Mon Sep 17 00:00:00 2001 From: elijah Date: Thu, 29 May 2014 15:48:02 -0700 Subject: return instead of reject for multicast --- pkg/linux/bitmask-root | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index f1c5c0c3..83e85774 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -758,11 +758,11 @@ def firewall_start(args): # allow multicast Simple Service Discovery Protocol ip4tables("--insert", BITMASK_CHAIN, "--protocol", "udp", "--destination", "239.255.255.250", "--dport", "1900", - "-o", default_device, "--jump", "ACCEPT") + "-o", default_device, "--jump", "RETURN") # allow multicast Bonjour/mDNS ip4tables("--insert", BITMASK_CHAIN, "--protocol", "udp", "--destination", "224.0.0.251", "--dport", "5353", - "-o", default_device, "--jump", "ACCEPT") + "-o", default_device, "--jump", "RETURN") if local_network_ipv6: ip6tables("--insert", BITMASK_CHAIN, "--destination", local_network_ipv6, "-o", default_device, @@ -770,11 +770,11 @@ def firewall_start(args): # allow multicast Simple Service Discovery Protocol ip6tables("--insert", BITMASK_CHAIN, "--protocol", "udp", "--destination", "FF05::C", "--dport", "1900", - "-o", default_device, "--jump", "ACCEPT") + "-o", default_device, "--jump", "RETURN") # allow multicast Bonjour/mDNS ip6tables("--insert", BITMASK_CHAIN, "--protocol", "udp", "--destination", "FF02::FB", "--dport", "5353", - "-o", default_device, "--jump", "ACCEPT") + "-o", default_device, "--jump", "RETURN") # block DNS requests to anyone but the service provider or localhost -- cgit v1.2.3 From fbf615b941f195b2fe513b528da9aec5771e75ea Mon Sep 17 00:00:00 2001 From: elijah Date: Thu, 29 May 2014 15:57:06 -0700 Subject: linux firewall: s/insert/append, and switch order. it makes much more sense to insert custom chain at start of OUTPUT, then append to that chain. --- pkg/linux/bitmask-root | 54 +++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 83e85774..9bd5dfce 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -729,63 +729,63 @@ def firewall_start(args): local_network_ipv6 = get_local_network_ipv6(default_device) gateways = get_gateways(args) - # add custom chain "bitmask" + # add custom chain "bitmask" to front of OUTPUT chain if not ipv4_chain_exists(BITMASK_CHAIN): ip4tables("--new-chain", BITMASK_CHAIN) if not ipv6_chain_exists(BITMASK_CHAIN): ip6tables("--new-chain", BITMASK_CHAIN) iptables("--insert", "OUTPUT", "--jump", BITMASK_CHAIN) - # reject everything - iptables("--insert", BITMASK_CHAIN, "-o", default_device, - "--jump", "REJECT") - - # log rejected packets to syslog - if DEBUG: - iptables("--insert", BITMASK_CHAIN, "-o", default_device, - "--jump", "LOG", "--log-prefix", "iptables denied: ", "--log-level", "7") + # allow DNS over VPN + for allowed_dns in [NAMESERVER, "127.0.0.1", "127.0.1.1"]: + ip4tables("--append", BITMASK_CHAIN, "--protocol", "udp", + "--dport", "53", "--destination", allowed_dns, + "--jump", "ACCEPT") - # allow traffic to gateways - for gateway in gateways: - ip4tables("--insert", BITMASK_CHAIN, "--destination", gateway, - "-o", default_device, "--jump", "ACCEPT") + # block DNS requests to anyone but the service provider or localhost + # (when we actually route ipv6, we will need DNS rules for it too) + ip4tables("--append", BITMASK_CHAIN, "--protocol", "udp", "--dport", "53", + "--jump", "REJECT") # allow traffic to IPs on local network if local_network_ipv4: - ip4tables("--insert", BITMASK_CHAIN, + ip4tables("--append", BITMASK_CHAIN, "--destination", local_network_ipv4, "-o", default_device, "--jump", "ACCEPT") # allow multicast Simple Service Discovery Protocol - ip4tables("--insert", BITMASK_CHAIN, + ip4tables("--append", BITMASK_CHAIN, "--protocol", "udp", "--destination", "239.255.255.250", "--dport", "1900", "-o", default_device, "--jump", "RETURN") # allow multicast Bonjour/mDNS - ip4tables("--insert", BITMASK_CHAIN, + ip4tables("--append", BITMASK_CHAIN, "--protocol", "udp", "--destination", "224.0.0.251", "--dport", "5353", "-o", default_device, "--jump", "RETURN") if local_network_ipv6: - ip6tables("--insert", BITMASK_CHAIN, + ip6tables("--append", BITMASK_CHAIN, "--destination", local_network_ipv6, "-o", default_device, "--jump", "ACCEPT") # allow multicast Simple Service Discovery Protocol - ip6tables("--insert", BITMASK_CHAIN, + ip6tables("--append", BITMASK_CHAIN, "--protocol", "udp", "--destination", "FF05::C", "--dport", "1900", "-o", default_device, "--jump", "RETURN") # allow multicast Bonjour/mDNS - ip6tables("--insert", BITMASK_CHAIN, + ip6tables("--append", BITMASK_CHAIN, "--protocol", "udp", "--destination", "FF02::FB", "--dport", "5353", "-o", default_device, "--jump", "RETURN") + # allow traffic to gateways + for gateway in gateways: + ip4tables("--append", BITMASK_CHAIN, "--destination", gateway, + "-o", default_device, "--jump", "ACCEPT") - # block DNS requests to anyone but the service provider or localhost - # when we actually route ipv6, we will need dns rules for it too - ip4tables("--insert", BITMASK_CHAIN, "--protocol", "udp", "--dport", "53", - "--jump", "REJECT") + # log rejected packets to syslog + if DEBUG: + iptables("--append", BITMASK_CHAIN, "-o", default_device, + "--jump", "LOG", "--log-prefix", "iptables denied: ", "--log-level", "7") - for allowed_dns in [NAMESERVER, "127.0.0.1", "127.0.1.1"]: - ip4tables("--insert", BITMASK_CHAIN, "--protocol", "udp", - "--dport", "53", "--destination", allowed_dns, - "--jump", "ACCEPT") + # reject everything else + iptables("--append", BITMASK_CHAIN, "-o", default_device, + "--jump", "REJECT") # workaround for ipv6 servers being blocked and not falling back to ipv4. # See #5693 -- cgit v1.2.3 From a0eecf4d0dc8ac17c8f2d99d56c21d5b2fae4f30 Mon Sep 17 00:00:00 2001 From: elijah Date: Fri, 30 May 2014 01:51:53 -0700 Subject: fix bug with ipv6 blocking (added to wrong chain, so never removed and also it would keep getting added repeatedly) --- changes/allow-local-multicast-in-firewall | 3 ++- pkg/linux/bitmask-root | 28 +++++++++++++--------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/changes/allow-local-multicast-in-firewall b/changes/allow-local-multicast-in-firewall index 160cabb3..4c17b92e 100644 --- a/changes/allow-local-multicast-in-firewall +++ b/changes/allow-local-multicast-in-firewall @@ -1 +1,2 @@ -- unblock local multicast IPs from linux firewall, to allow SSDP and Bonjour/mDNS to work. \ No newline at end of file +- unblock local multicast IPs from linux firewall, to allow SSDP and Bonjour/mDNS to work. +- fix bug with ipv6 blocking that caused block to not get removed from firewall when Bitmask quit. \ No newline at end of file diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 9bd5dfce..82e8799f 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -653,6 +653,7 @@ def get_local_network_ipv6(device): def run_iptable_with_check(cmd, *args, **options): """ Run an iptables command checking to see if it should: + for --append: run only if rule does not already exist. for --insert: run only if rule does not already exist. for --delete: run only if rule does exist. other commands are run normally. @@ -662,6 +663,11 @@ def run_iptable_with_check(cmd, *args, **options): check_code = run(cmd, *check_args, exitcode=True) if check_code != 0: run(cmd, *args, **options) + elif "--append" in args: + check_args = [arg.replace("--append", "--check") for arg in args] + check_code = run(cmd, *check_args, exitcode=True) + if check_code != 0: + run(cmd, *args, **options) elif "--delete" in args: check_args = [arg.replace("--delete", "--check") for arg in args] check_code = run(cmd, *check_args, exitcode=True) @@ -773,7 +779,7 @@ def firewall_start(args): "--protocol", "udp", "--destination", "FF02::FB", "--dport", "5353", "-o", default_device, "--jump", "RETURN") - # allow traffic to gateways + # allow ipv4 traffic to gateways for gateway in gateways: ip4tables("--append", BITMASK_CHAIN, "--destination", gateway, "-o", default_device, "--jump", "ACCEPT") @@ -783,21 +789,13 @@ def firewall_start(args): iptables("--append", BITMASK_CHAIN, "-o", default_device, "--jump", "LOG", "--log-prefix", "iptables denied: ", "--log-level", "7") - # reject everything else - iptables("--append", BITMASK_CHAIN, "-o", default_device, - "--jump", "REJECT") - - # workaround for ipv6 servers being blocked and not falling back to ipv4. - # See #5693 - ip6tables("--append", "OUTPUT", "--jump", "REJECT", - "-s", "::/0", "-d", "::/0", - "-p", "tcp", - "--reject-with", "icmp6-port-unreachable") - ip6tables("--append", "OUTPUT", "--jump", "REJECT", - "-s", "::/0", "-d", "::/0", - "-p", "udp", - "--reject-with", "icmp6-port-unreachable") + # for now, ensure all other ipv6 packets get rejected (regardless of device) + # (not sure why, but "-p any" doesn't work) + ip6tables("--append", BITMASK_CHAIN, "-p", "tcp", "--jump", "REJECT") + ip6tables("--append", BITMASK_CHAIN, "-p", "udp", "--jump", "REJECT") + # reject all other ipv4 sent over the default device + ip4tables("--append", BITMASK_CHAIN, "-o", default_device, "--jump", "REJECT") def firewall_stop(): """ -- cgit v1.2.3 From d4d6b0c5476b45629d632debe4527f9d6cb3cb0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 29 May 2014 16:44:25 -0300 Subject: Check openvpn bin path before starting openvpn --- changes/bug_openvpn_not_found | 1 + src/leap/bitmask/backend.py | 74 +++++++++++++++++--------- src/leap/bitmask/gui/eip_status.py | 36 +++++++++++-- src/leap/bitmask/gui/mainwindow.py | 5 +- src/leap/bitmask/gui/statemachines.py | 4 ++ src/leap/bitmask/platform_init/initializers.py | 1 - src/leap/bitmask/services/eip/conductor.py | 1 - 7 files changed, 88 insertions(+), 34 deletions(-) create mode 100644 changes/bug_openvpn_not_found diff --git a/changes/bug_openvpn_not_found b/changes/bug_openvpn_not_found new file mode 100644 index 00000000..c2363122 --- /dev/null +++ b/changes/bug_openvpn_not_found @@ -0,0 +1 @@ +- Enable UI when OpenVPN bin is not found, plus check before starting EIP. Fixes #5619. \ No newline at end of file diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 1ab5b40d..e67bee1b 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -45,7 +45,7 @@ from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper from leap.bitmask.services.eip import vpnlauncher, vpnprocess from leap.bitmask.services.eip import linuxvpnlauncher, darwinvpnlauncher - +from leap.bitmask.services.eip import get_vpn_launcher from leap.bitmask.services.mail.imapcontroller import IMAPController from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper @@ -389,6 +389,11 @@ class EIP(object): loaded = eipconfig.load_eipconfig_if_needed( provider_config, eip_config, domain) + if not self._can_start(domain): + if self._signaler is not None: + self._signaler.signal(self._signaler.EIP_CONNECTION_ABORTED) + return + if not loaded: if self._signaler is not None: self._signaler.signal(self._signaler.EIP_CONNECTION_ABORTED) @@ -572,44 +577,61 @@ class EIP(object): self._signaler.signal( self._signaler.EIP_GET_GATEWAYS_LIST, gateways) - def can_start(self, domain): + def _can_start(self, domain): """ - Signal whether it has everything that is needed to run EIP or not + Returns True if it has everything that is needed to run EIP, + False otherwise :param domain: the domain for the provider to check :type domain: str - - Signals: - eip_can_start - eip_cannot_start """ - try: - eip_config = eipconfig.EIPConfig() - provider_config = ProviderConfig.get_provider_config(domain) + eip_config = eipconfig.EIPConfig() + provider_config = ProviderConfig.get_provider_config(domain) + + api_version = provider_config.get_api_version() + eip_config.set_api_version(api_version) + eip_loaded = eip_config.load(eipconfig.get_eipconfig_path(domain)) + + launcher = get_vpn_launcher() + if not os.path.isfile(launcher.OPENVPN_BIN_PATH): + logger.error("Cannot start OpenVPN, binary not found") + return False + + # check for other problems + if not eip_loaded or provider_config is None: + logger.error("Cannot load provider and eip config, cannot " + "autostart") + return False - api_version = provider_config.get_api_version() - eip_config.set_api_version(api_version) - eip_loaded = eip_config.load(eipconfig.get_eipconfig_path(domain)) + client_cert_path = eip_config.\ + get_client_cert_path(provider_config, about_to_download=False) - # check for other problems - if not eip_loaded or provider_config is None: - raise Exception("Cannot load provider and eip config, cannot " - "autostart") + if leap_certs.should_redownload(client_cert_path): + logger.error("The client should redownload the certificate," + " cannot autostart") + return False - client_cert_path = eip_config.\ - get_client_cert_path(provider_config, about_to_download=False) + if not os.path.isfile(client_cert_path): + logger.error("Can't find the certificate, cannot autostart") + return False - if leap_certs.should_redownload(client_cert_path): - raise Exception("The client should redownload the certificate," - " cannot autostart") + return True - if not os.path.isfile(client_cert_path): - raise Exception("Can't find the certificate, cannot autostart") + def can_start(self, domain): + """ + Signal whether it has everything that is needed to run EIP or not + :param domain: the domain for the provider to check + :type domain: str + + Signals: + eip_can_start + eip_cannot_start + """ + if self._can_start(domain): if self._signaler is not None: self._signaler.signal(self._signaler.EIP_CAN_START) - except Exception as e: - logger.exception(e) + else: if self._signaler is not None: self._signaler.signal(self._signaler.EIP_CANNOT_START) diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 04acc167..ba041968 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -102,6 +102,8 @@ class EIPStatusWidget(QtGui.QWidget): self._on_eip_vpn_launcher_exception) signaler.eip_no_polkit_agent_error.connect( self._on_eip_no_polkit_agent_error) + signaler.eip_connection_aborted.connect( + self._on_eip_connection_aborted) signaler.eip_no_pkexec_error.connect(self._on_eip_no_pkexec_error) signaler.eip_no_tun_kext_error.connect(self._on_eip_no_tun_kext_error) @@ -539,11 +541,11 @@ class EIPStatusWidget(QtGui.QWidget): eip_status_label = self.tr("Could not load {0} configuration.") eip_status_label = eip_status_label.format( - self._eip_conductor.eip_name) + self.eip_conductor.eip_name) self.set_eip_status(eip_status_label, error=True) # signal connection_aborted to state machine: - qtsigs = self._eipconnection.qtsigs + qtsigs = self.eipconnection.qtsigs qtsigs.connection_aborted_signal.emit() def _on_eip_openvpn_already_running(self): @@ -553,6 +555,10 @@ class EIPStatusWidget(QtGui.QWidget): error=True) self.set_eipstatus_off() + # signal connection_aborted to state machine: + qtsigs = self.eipconnection.qtsigs + qtsigs.connection_aborted_signal.emit() + def _on_eip_alien_openvpn_already_running(self): self.set_eip_status( self.tr("Another openvpn instance is already running, and " @@ -561,18 +567,30 @@ class EIPStatusWidget(QtGui.QWidget): error=True) self.set_eipstatus_off() + # signal connection_aborted to state machine: + qtsigs = self.eipconnection.qtsigs + qtsigs.connection_aborted_signal.emit() + def _on_eip_openvpn_not_found_error(self): self.set_eip_status( self.tr("We could not find openvpn binary."), error=True) self.set_eipstatus_off() + # signal connection_aborted to state machine: + qtsigs = self.eipconnection.qtsigs + qtsigs.connection_aborted_signal.emit() + def _on_eip_vpn_launcher_exception(self): # XXX We should implement again translatable exceptions so # we can pass a translatable string to the panel (usermessage attr) self.set_eip_status("VPN Launcher error.", error=True) self.set_eipstatus_off() + # signal connection_aborted to state machine: + qtsigs = self.eipconnection.qtsigs + qtsigs.connection_aborted_signal.emit() + def _on_eip_no_polkit_agent_error(self): self.set_eip_status( # XXX this should change to polkit-kde where @@ -584,19 +602,31 @@ class EIPStatusWidget(QtGui.QWidget): error=True) self.set_eipstatus_off() + # signal connection_aborted to state machine: + qtsigs = self.eipconnection.qtsigs + qtsigs.connection_aborted_signal.emit() + def _on_eip_no_pkexec_error(self): self.set_eip_status( self.tr("We could not find pkexec in your system."), error=True) self.set_eipstatus_off() + # signal connection_aborted to state machine: + qtsigs = self.eipconnection.qtsigs + qtsigs.connection_aborted_signal.emit() + def _on_eip_no_tun_kext_error(self): self.set_eip_status( self.tr("{0} cannot be started because the tuntap extension is " "not installed properly in your " - "system.").format(self._eip_conductor.eip_name)) + "system.").format(self.eip_conductor.eip_name)) self.set_eipstatus_off() + # signal connection_aborted to state machine: + qtsigs = self.eipconnection.qtsigs + qtsigs.connection_aborted_signal.emit() + @QtCore.Slot() def _on_eip_network_unreachable(self): """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 1d467e60..a0eb84a2 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -597,7 +597,7 @@ class MainWindow(QtGui.QMainWindow): default_provider = settings.get_defaultprovider() if default_provider is None: - logger.warning("Trying toupdate eip enabled status but there's no" + logger.warning("Trying to update eip enabled status but there's no" " default provider. Disabling EIP for the time" " being...") self._backend_cannot_start_eip() @@ -637,7 +637,6 @@ class MainWindow(QtGui.QMainWindow): # so the user needs to log in first self._eip_status.disable_eip_start() else: - self._stop_eip() self._eip_status.disable_eip_start() self._eip_status.set_eip_status(self.tr("Disabled")) @@ -664,7 +663,6 @@ class MainWindow(QtGui.QMainWindow): # so the user needs to log in first self._eip_status.disable_eip_start() else: - self._stop_eip() self._eip_status.disable_eip_start() self._eip_status.set_eip_status(self.tr("Disabled")) @@ -1566,6 +1564,7 @@ class MainWindow(QtGui.QMainWindow): # XXX this should be handled by the state machine. self._eip_status.set_eip_status( self.tr("Starting...")) + self._eip_status.eip_button.setEnabled(False) domain = self._login_widget.get_selected_provider() self._backend.eip_setup(domain) diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py index f8e5479d..00a1387e 100644 --- a/src/leap/bitmask/gui/statemachines.py +++ b/src/leap/bitmask/gui/statemachines.py @@ -565,11 +565,15 @@ class ConnectionMachineBuilder(object): button, 'text', off_label) off.assignProperty( button, 'enabled', True) + off.assignProperty( + button, 'visible', True) if action: off.assignProperty( action, 'text', off_label) off.assignProperty( action, 'enabled', True) + off.assignProperty( + action, 'visible', True) off.setObjectName(_OFF) states[_OFF] = off diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index f2710c58..14d96c9b 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -33,7 +33,6 @@ from leap.bitmask.services.eip import get_vpn_launcher from leap.bitmask.services.eip.linuxvpnlauncher import LinuxVPNLauncher from leap.bitmask.services.eip.darwinvpnlauncher import DarwinVPNLauncher from leap.bitmask.util import first -from leap.bitmask.util import privilege_policies logger = logging.getLogger(__name__) diff --git a/src/leap/bitmask/services/eip/conductor.py b/src/leap/bitmask/services/eip/conductor.py index 0cd4c95c..53b1fde9 100644 --- a/src/leap/bitmask/services/eip/conductor.py +++ b/src/leap/bitmask/services/eip/conductor.py @@ -26,7 +26,6 @@ from leap.bitmask.services import EIP_SERVICE from leap.bitmask.services import get_service_display_name from leap.bitmask.services.eip.connection import EIPConnection from leap.bitmask.platform_init import IS_MAC -from leap.bitmask.util import make_address QtDelayedCall = QtCore.QTimer.singleShot logger = logging.getLogger(__name__) -- cgit v1.2.3 From e723927a9a5a1b6e40553499e9d5148df10fcc46 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 30 May 2014 11:22:25 -0300 Subject: Do nothing if the vpnprocess is not started. We were trying to access the `is_restart` attribute that causes a failure if the vpnprocess is not instantiated. --- src/leap/bitmask/backend.py | 1 + src/leap/bitmask/services/eip/vpnprocess.py | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index e67bee1b..4ec20be7 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -974,6 +974,7 @@ class Mail(object): threads.deferToThread(self._imap_controller.stop_imap_service, cv) logger.debug('Waiting for imap service to stop.') cv.wait(self.SERVICE_STOP_TIMEOUT) + logger.debug('IMAP stopped') self._signaler.signal(self._signaler.IMAP_STOPPED) def stop_imap_service(self): diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 098619be..1de4a851 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -317,13 +317,13 @@ class VPN(object): from twisted.internet import reactor self._stop_pollers() - # We assume that the only valid stops are initiated - # by an user action, not hard restarts - self._user_stopped = not restart - self._vpnproc.is_restart = restart - # First we try to be polite and send a SIGTERM... - if self._vpnproc: + if self._vpnproc is not None: + # We assume that the only valid stops are initiated + # by an user action, not hard restarts + self._user_stopped = not restart + self._vpnproc.is_restart = restart + self._sentterm = True self._vpnproc.terminate_openvpn(shutdown=shutdown) -- cgit v1.2.3 From 8b237f08b76bc94cfcd24d13945006a89c3aeeee Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 30 May 2014 12:16:17 -0300 Subject: Change password skips soledad if mx is not enabled. Use information from mainwindow instead of asking again to the backend. --- changes/bug-5540_change-password-fix | 1 + src/leap/bitmask/gui/mainwindow.py | 11 +++-- src/leap/bitmask/gui/preferenceswindow.py | 71 +++++++++++++------------------ 3 files changed, 37 insertions(+), 46 deletions(-) create mode 100644 changes/bug-5540_change-password-fix diff --git a/changes/bug-5540_change-password-fix b/changes/bug-5540_change-password-fix new file mode 100644 index 00000000..9c9c09d5 --- /dev/null +++ b/changes/bug-5540_change-password-fix @@ -0,0 +1 @@ +Change password doesn't work. Closes #5540. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index a0eb84a2..d46ef863 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -572,10 +572,13 @@ class MainWindow(QtGui.QMainWindow): Displays the preferences window. """ - user = self._login_widget.get_user() - prov = self._login_widget.get_selected_provider() - preferences = PreferencesWindow( - self, self._backend, self._soledad_started, user, prov) + user = self._logged_user + domain = self._login_widget.get_selected_provider() + mx_provided = False + if self._provider_details is not None: + mx_provided = MX_SERVICE in self._provider_details.services + preferences = PreferencesWindow(self, user, domain, self._backend, + self._soledad_started, mx_provided) self.soledad_ready.connect(preferences.set_soledad_ready) preferences.show() diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index c67052f3..a3b81d38 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -38,28 +38,31 @@ class PreferencesWindow(QtGui.QDialog): """ preferences_saved = QtCore.Signal() - def __init__(self, parent, backend, soledad_started, username, domain): + def __init__(self, parent, username, domain, backend, soledad_started, mx): """ :param parent: parent object of the PreferencesWindow. :parent type: QWidget - :param backend: Backend being used - :type backend: Backend - :param soledad_started: whether soledad has started or not - :type soledad_started: bool :param username: the user set in the login widget :type username: unicode :param domain: the selected domain in the login widget :type domain: unicode + :param backend: Backend being used + :type backend: Backend + :param soledad_started: whether soledad has started or not + :type soledad_started: bool + :param mx: whether the current provider provides mx or not. + :type mx: bool """ QtGui.QDialog.__init__(self, parent) self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") - self._backend = backend - self._settings = LeapSettings() - self._soledad_started = soledad_started self._username = username self._domain = domain + self._backend = backend + self._soledad_started = soledad_started + self._mx_provided = mx + self._settings = LeapSettings() self._backend_connect() # Load UI @@ -80,26 +83,17 @@ class PreferencesWindow(QtGui.QDialog): else: self._add_configured_providers() - self._backend.user_get_logged_in_status() + if self._username is None: + self._not_logged_in() + else: + self.ui.gbPasswordChange.setEnabled(True) + if self._mx_provided: + self._provides_mx() self._select_provider_by_name(domain) - @QtCore.Slot() - def _is_logged_in(self): - """ - TRIGGERS: - Signaler.srp_status_logged_in - - Actions to perform is the user is logged in. - """ - self._backend.provider_provides_mx() - - @QtCore.Slot() def _not_logged_in(self): """ - TRIGGERS: - Signaler.srp_status_not_logged_in - Actions to perform if the user is not logged in. """ msg = self.tr( @@ -107,12 +101,8 @@ class PreferencesWindow(QtGui.QDialog): self._set_password_change_status(msg) self.ui.gbPasswordChange.setEnabled(False) - @QtCore.Slot() def _provides_mx(self): """ - TRIGGERS: - Signaler.prov_provides_mx - Actions to perform if the provider provides MX. """ pw_enabled = True @@ -135,16 +125,6 @@ class PreferencesWindow(QtGui.QDialog): self.ui.gbPasswordChange.setEnabled(pw_enabled) - @QtCore.Slot() - def _not_provides_mx(self): - """ - TRIGGERS: - Signaler.prov_not_provides_mx - - Actions to perform if the provider does not provides MX. - """ - self.ui.gbPasswordChange.setEnabled(False) - @QtCore.Slot() def set_soledad_ready(self): """ @@ -226,7 +206,11 @@ class PreferencesWindow(QtGui.QDialog): """ new_password = self.ui.leNewPassword.text() logger.debug("SRP password changed successfully.") - self._backend.soledad_change_password(new_password) + + if self._mx_provided: + self._backend.soledad_change_password(new_password) + else: + self._change_password_success() @QtCore.Slot(unicode) def _srp_change_password_problem(self, msg): @@ -250,6 +234,13 @@ class PreferencesWindow(QtGui.QDialog): TRIGGERS: Signaler.soledad_password_change_ok + Soledad password change went OK. + """ + logger.debug("Soledad password changed successfully.") + self._change_password_success() + + def _change_password_success(self): + """ Callback used to display a successfully changed password. """ logger.debug("Soledad password changed successfully.") @@ -434,12 +425,8 @@ class PreferencesWindow(QtGui.QDialog): """ sig = self._backend.signaler - sig.prov_provides_mx.connect(self._provides_mx) sig.prov_get_supported_services.connect(self._load_services) - sig.srp_status_logged_in.connect(self._is_logged_in) - sig.srp_status_not_logged_in.connect(self._not_logged_in) - sig.srp_password_change_ok.connect(self._srp_change_password_ok) pwd_change_error = lambda: self._srp_change_password_problem( -- cgit v1.2.3 From b82589e29ce402725c2053dace1117ba8a785535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 30 May 2014 12:56:13 -0300 Subject: Improve signal handling in the mainwindow and wizard --- changes/bug_improve_signal_handling | 1 + src/leap/bitmask/gui/mainwindow.py | 13 ++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 changes/bug_improve_signal_handling diff --git a/changes/bug_improve_signal_handling b/changes/bug_improve_signal_handling new file mode 100644 index 00000000..4f88747f --- /dev/null +++ b/changes/bug_improve_signal_handling @@ -0,0 +1 @@ +- Improve signal handling in the mainwindow and wizard. \ No newline at end of file diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index a0eb84a2..af7b2699 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -186,7 +186,7 @@ class MainWindow(QtGui.QMainWindow): # timeout object used to trigger quit self._quit_timeout_callater = None - self._backend_connected_signals = {} + self._backend_connected_signals = [] self._backend_connect() self.ui.action_preferences.triggered.connect(self._show_preferences) @@ -318,7 +318,7 @@ class MainWindow(QtGui.QMainWindow): :param method: the method to call when the signal is triggered. :type method: callable, Slot or Signal """ - self._backend_connected_signals[signal] = method + self._backend_connected_signals.append((signal, method)) signal.connect(method) def _backend_bad_call(self, data): @@ -355,12 +355,12 @@ class MainWindow(QtGui.QMainWindow): conntrack(sig.prov_download_ca_cert, self._intermediate_stage) conntrack(sig.prov_download_provider_info, self._load_provider_config) conntrack(sig.prov_check_api_certificate, self._provider_config_loaded) + conntrack(sig.prov_check_api_certificate, self._get_provider_details) conntrack(sig.prov_problem_with_provider, self._login_problem_provider) conntrack(sig.prov_cancelled_setup, self._set_login_cancelled) - self._connect_and_track(sig.prov_get_details, - self._provider_get_details) + conntrack(sig.prov_get_details, self._provider_get_details) # Login signals conntrack(sig.srp_auth_ok, self._authentication_finished) @@ -399,7 +399,6 @@ class MainWindow(QtGui.QMainWindow): sig.backend_bad_call.connect(self._backend_bad_call) - sig.prov_check_api_certificate.connect(self._get_provider_details) sig.prov_unsupported_client.connect(self._needs_update) sig.prov_unsupported_api.connect(self._incompatible_api) sig.prov_get_all_services.connect(self._provider_get_all_services) @@ -435,13 +434,13 @@ class MainWindow(QtGui.QMainWindow): Some signals are emitted from the wizard, and we want to ignore those. """ - for signal, method in self._backend_connected_signals.items(): + for signal, method in self._backend_connected_signals: try: signal.disconnect(method) except RuntimeError: pass # Signal was not connected - self._backend_connected_signals = {} + self._backend_connected_signals = [] @QtCore.Slot() def _rejected_wizard(self): -- cgit v1.2.3 From 520ffec305f89cd1ee504c24adee9e1bee21c452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 30 May 2014 11:44:33 -0300 Subject: Update EIP UI if it fails to load the config --- changes/bug_update_ui_when_fails_at_config | 1 + src/leap/bitmask/gui/eip_status.py | 40 ++++++++++++------------------ src/leap/bitmask/gui/mainwindow.py | 1 + 3 files changed, 18 insertions(+), 24 deletions(-) create mode 100644 changes/bug_update_ui_when_fails_at_config diff --git a/changes/bug_update_ui_when_fails_at_config b/changes/bug_update_ui_when_fails_at_config new file mode 100644 index 00000000..84f19401 --- /dev/null +++ b/changes/bug_update_ui_when_fails_at_config @@ -0,0 +1 @@ +- Update EIP UI if it fails to download the config. \ No newline at end of file diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index ba041968..e7795083 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -526,6 +526,14 @@ class EIPStatusWidget(QtGui.QWidget): self.ui.lblEIPMessage.setText( self.tr("Route traffic through: {0}").format(self._provider)) + def aborted(self): + """ + Notify the state machine that EIP was aborted for some reason. + """ + # signal connection_aborted to state machine: + qtsigs = self.eipconnection.qtsigs + qtsigs.connection_aborted_signal.emit() + # # Slots for signals # @@ -544,9 +552,7 @@ class EIPStatusWidget(QtGui.QWidget): self.eip_conductor.eip_name) self.set_eip_status(eip_status_label, error=True) - # signal connection_aborted to state machine: - qtsigs = self.eipconnection.qtsigs - qtsigs.connection_aborted_signal.emit() + self.aborted() def _on_eip_openvpn_already_running(self): self.set_eip_status( @@ -555,9 +561,7 @@ class EIPStatusWidget(QtGui.QWidget): error=True) self.set_eipstatus_off() - # signal connection_aborted to state machine: - qtsigs = self.eipconnection.qtsigs - qtsigs.connection_aborted_signal.emit() + self.aborted() def _on_eip_alien_openvpn_already_running(self): self.set_eip_status( @@ -567,9 +571,7 @@ class EIPStatusWidget(QtGui.QWidget): error=True) self.set_eipstatus_off() - # signal connection_aborted to state machine: - qtsigs = self.eipconnection.qtsigs - qtsigs.connection_aborted_signal.emit() + self.aborted() def _on_eip_openvpn_not_found_error(self): self.set_eip_status( @@ -577,9 +579,7 @@ class EIPStatusWidget(QtGui.QWidget): error=True) self.set_eipstatus_off() - # signal connection_aborted to state machine: - qtsigs = self.eipconnection.qtsigs - qtsigs.connection_aborted_signal.emit() + self.aborted() def _on_eip_vpn_launcher_exception(self): # XXX We should implement again translatable exceptions so @@ -587,9 +587,7 @@ class EIPStatusWidget(QtGui.QWidget): self.set_eip_status("VPN Launcher error.", error=True) self.set_eipstatus_off() - # signal connection_aborted to state machine: - qtsigs = self.eipconnection.qtsigs - qtsigs.connection_aborted_signal.emit() + self.aborted() def _on_eip_no_polkit_agent_error(self): self.set_eip_status( @@ -602,9 +600,7 @@ class EIPStatusWidget(QtGui.QWidget): error=True) self.set_eipstatus_off() - # signal connection_aborted to state machine: - qtsigs = self.eipconnection.qtsigs - qtsigs.connection_aborted_signal.emit() + self.aborted() def _on_eip_no_pkexec_error(self): self.set_eip_status( @@ -612,9 +608,7 @@ class EIPStatusWidget(QtGui.QWidget): error=True) self.set_eipstatus_off() - # signal connection_aborted to state machine: - qtsigs = self.eipconnection.qtsigs - qtsigs.connection_aborted_signal.emit() + self.aborted() def _on_eip_no_tun_kext_error(self): self.set_eip_status( @@ -623,9 +617,7 @@ class EIPStatusWidget(QtGui.QWidget): "system.").format(self.eip_conductor.eip_name)) self.set_eipstatus_off() - # signal connection_aborted to state machine: - qtsigs = self.eipconnection.qtsigs - qtsigs.connection_aborted_signal.emit() + self.aborted() @QtCore.Slot() def _on_eip_network_unreachable(self): diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index a0eb84a2..5420eb4d 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1624,6 +1624,7 @@ class MainWindow(QtGui.QMainWindow): self.tr("Unable to connect: Problem with provider")) logger.error(data[self._backend.ERROR_KEY]) self._already_started_eip = False + self._eip_status.aborted() # end of EIP methods --------------------------------------------- -- cgit v1.2.3 From 687e1a87da9321b27ad966907db0f58f1c25b157 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 2 Jun 2014 15:45:06 -0500 Subject: add restore clearnet button. Closes: #5726 --- src/leap/bitmask/backend.py | 36 +++++++++----- src/leap/bitmask/gui/eip_status.py | 76 ++++++++++++++++++++++++----- src/leap/bitmask/gui/ui/eip_status.ui | 27 ++++++---- src/leap/bitmask/services/eip/conductor.py | 27 +++++++--- src/leap/bitmask/services/eip/vpnprocess.py | 21 +++++--- 5 files changed, 139 insertions(+), 48 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 4ec20be7..a18dffef 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -80,13 +80,13 @@ class ILEAPService(ILEAPComponent): Interface that every Service needs to implement """ - def start(self): + def start(self, *args, **kwargs): """ Start the service. """ pass - def stop(self): + def stop(self, *args, **kwargs): """ Stops the service. """ @@ -378,7 +378,7 @@ class EIP(object): if d is not None: d.cancel() - def _start_eip(self): + def _start_eip(self, restart=False): """ Start EIP """ @@ -404,9 +404,10 @@ class EIP(object): host, port = get_openvpn_management() self._vpn.start(eipconfig=eip_config, providerconfig=provider_config, - socket_host=host, socket_port=port) + socket_host=host, socket_port=port, + restart=restart) - def start(self): + def start(self, *args, **kwargs): """ Start the service. """ @@ -419,7 +420,7 @@ class EIP(object): return try: - self._start_eip() + self._start_eip(*args, **kwargs) except vpnprocess.OpenVPNAlreadyRunning: signaler.signal(signaler.EIP_OPENVPN_ALREADY_RUNNING) except vpnprocess.AlienOpenVPNAlreadyRunning: @@ -440,11 +441,6 @@ class EIP(object): logger.error("Unexpected problem: {0!r}".format(e)) else: logger.debug('EIP: no errors') - # TODO: are we connected here? - # kali -- no, we are not! CONNECTED should be passed only - # by the vpn observer. Currently handled by the state updater - # in eip_status - #signaler.signal(signaler.EIP_CONNECTED) def _do_stop(self, shutdown=False, restart=False): """ @@ -539,6 +535,12 @@ class EIP(object): self._signaler.signal(self._signaler.EIP_GET_INITIALIZED_PROVIDERS, filtered_domains) + def tear_fw_down(self): + """ + Tear the firewall down. + """ + self._vpn.tear_down_firewall() + def get_gateways_list(self, domain): """ Signal a list of gateways for the given provider. @@ -1181,6 +1183,7 @@ class Signaler(QtCore.QObject): eip_state_changed = QtCore.Signal(dict) eip_status_changed = QtCore.Signal(dict) eip_process_finished = QtCore.Signal(int) + eip_tear_fw_down = QtCore.Signal(object) # signals whether the needed files to start EIP exist or not eip_can_start = QtCore.Signal(object) @@ -1282,6 +1285,7 @@ class Signaler(QtCore.QObject): EIP_STATE_CHANGED = "eip_state_changed" EIP_STATUS_CHANGED = "eip_status_changed" EIP_PROCESS_FINISHED = "eip_process_finished" + EIP_TEAR_FW_DOWN = "eip_tear_fw_down" EIP_CAN_START = "eip_can_start" EIP_CANNOT_START = "eip_cannot_start" @@ -1717,7 +1721,7 @@ class Backend(object): """ self._call_queue.put(("eip", "cancel_setup_eip", None)) - def eip_start(self): + def eip_start(self, restart=False): """ Start the EIP service. @@ -1739,7 +1743,7 @@ class Backend(object): eip_status_changed -> tuple of str (download, upload) eip_vpn_launcher_exception """ - self._call_queue.put(("eip", "start", None)) + self._call_queue.put(("eip", "start", None, restart)) def eip_stop(self, shutdown=False, restart=False, failed=False): """ @@ -1805,6 +1809,12 @@ class Backend(object): self._call_queue.put(("eip", "can_start", None, domain)) + def tear_fw_down(self): + """ + Signal the need to tear the fw down. + """ + self._call_queue.put(("eip", "tear_fw_down", None)) + def user_login(self, provider, username, password): """ Execute the whole authentication process for a user diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index e7795083..fd8b35c3 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -31,6 +31,7 @@ from leap.common.check import leap_assert_type from ui_eip_status import Ui_EIPStatus +QtDelayedCall = QtCore.QTimer.singleShot logger = logging.getLogger(__name__) @@ -66,6 +67,9 @@ class EIPStatusWidget(QtGui.QWidget): self._service_name = get_service_display_name(EIP_SERVICE) self.ui.eip_bandwidth.hide() + self.hide_fw_down_button() + self.ui.btnFwDown.clicked.connect( + self._on_fw_down_button_clicked) # Set the EIP status icons self.CONNECTING_ICON = None @@ -109,8 +113,12 @@ class EIPStatusWidget(QtGui.QWidget): signaler.eip_state_changed.connect(self.update_vpn_state) signaler.eip_status_changed.connect(self.update_vpn_status) - signaler.eip_network_unreachable.connect( - self._on_eip_network_unreachable) + + # XXX we cannot connect this signal now because + # it interferes with the proper notifications during restarts + # without available network. + #signaler.eip_network_unreachable.connect( + #self._on_eip_network_unreachable) def _make_status_clickable(self): """ @@ -273,6 +281,8 @@ class EIPStatusWidget(QtGui.QWidget): Disables the start/stop button. """ self.set_startstop_enabled(False) + msg = self.tr("Encrypted Internet is starting") + self.set_eip_message(msg) @QtCore.Slot() def disable_eip_start(self): @@ -314,10 +324,18 @@ class EIPStatusWidget(QtGui.QWidget): if self.isVisible(): self._eip_status_menu.menuAction().setVisible(True) - # XXX disable (later) -------------------------- + def set_eip_message(self, message): + """ + Set the EIP Widget main message. + + :param message: the message to set in the widget + :type message: str or unicode + """ + self.ui.lblEIPMessage.setText(message) + def set_eip_status(self, status, error=False): """ - Sets the status label at the VPN stage to status + Set the status label at the VPN stage to status. :param status: status message :type status: str or unicode @@ -362,6 +380,26 @@ class EIPStatusWidget(QtGui.QWidget): self.ui.btnEipStartStop.clicked.connect( self.eipconnection.qtsigs.do_connect_signal) + def hide_fw_down_button(self): + """ + Hide firewall-down button. + """ + self.ui.btnFwDown.hide() + + def show_fw_down_button(self): + """ + Enable firewall-down button. + """ + self.ui.btnFwDown.show() + + def _on_fw_down_button_clicked(self): + """ + Raise a signal for tearing down the firewall, and hide the button + afterwards. + """ + self.eip_conductor._backend.tear_fw_down() + QtDelayedCall(50, self.hide_fw_down_button) + @QtCore.Slot(dict) def eip_stopped(self, restart=False, failed=False): """ @@ -382,7 +420,7 @@ class EIPStatusWidget(QtGui.QWidget): clear_traffic = self.tr("Traffic is being routed in the clear.") unreachable_net = self.tr("Network is unreachable.") - failed_msg = self.tr("Cannot start Encrypted Proxy.") + failed_msg = self.tr("Cannot start Encrypted Internet") if restart: msg = unreachable_net @@ -390,18 +428,19 @@ class EIPStatusWidget(QtGui.QWidget): msg = failed_msg else: msg = clear_traffic - self.ui.lblEIPMessage.setText(msg) + self.set_eip_message(msg) self.ui.lblEIPStatus.show() + self.show() def eip_failed_to_restart(self): """ Update EIP messages. """ - msg = self.tr("Could not restart Encrypted Proxy") + msg = self.tr("Could not restart Encrypted Internet") self.ui.lblEIPMessage.setText(msg) self.ui.lblEIPStatus.show() - - self.set_eip_status(self.tr("You can start the service manually.")) + self.set_eip_status(self.tr("You can launch the service manually.")) + self.show_fw_down_button() @QtCore.Slot(dict) def update_vpn_status(self, data=None): @@ -462,10 +501,15 @@ class EIPStatusWidget(QtGui.QWidget): # XXX should be handled by the state machine too. # --- is this currently being sent? self.eipconnection.qtsigs.connected_signal.emit() + self._on_eip_connected() # XXX should lookup vpn_state map in EIPConnection elif vpn_state == "AUTH": self.set_eip_status(self.tr("Authenticating...")) + # we wipe up any previous error info in the EIP message + # when we detect vpn authentication is happening + msg = self.tr("Encrypted Internet is starting") + self.set_eip_message(msg) elif vpn_state == "GET_CONFIG": self.set_eip_status(self.tr("Retrieving configuration...")) elif vpn_state == "WAIT": @@ -478,10 +522,10 @@ class EIPStatusWidget(QtGui.QWidget): # Put the following calls in Qt's event queue, otherwise # the UI won't update properly #self.send_disconnect_signal() - QtCore.QTimer.singleShot( + QtDelayedCall( 0, self.eipconnection.qtsigns.do_disconnect_signal.emit) msg = self.tr("Unable to start VPN, it's already running.") - QtCore.QTimer.singleShot(0, partial(self.set_eip_status, msg)) + QtDelayedCall(0, partial(self.set_eip_status, msg)) else: self.set_eip_status(vpn_state) @@ -524,7 +568,8 @@ class EIPStatusWidget(QtGui.QWidget): def set_provider(self, provider): self._provider = provider self.ui.lblEIPMessage.setText( - self.tr("Route traffic through: {0}").format(self._provider)) + self.tr("Routing traffic through: {0}").format( + self._provider)) def aborted(self): """ @@ -619,6 +664,13 @@ class EIPStatusWidget(QtGui.QWidget): self.aborted() + def _on_eip_connected(self): + """ + Reconnect the disconnecting signal when we are just connected, + so that we restore the disconnecting -> stop behaviour. + """ + self.eip_conductor.reconnect_stop_signal() + @QtCore.Slot() def _on_eip_network_unreachable(self): """ diff --git a/src/leap/bitmask/gui/ui/eip_status.ui b/src/leap/bitmask/gui/ui/eip_status.ui index 64821ad6..01d6b371 100644 --- a/src/leap/bitmask/gui/ui/eip_status.ui +++ b/src/leap/bitmask/gui/ui/eip_status.ui @@ -28,7 +28,7 @@ 0 - + Turn On @@ -86,7 +86,7 @@ - + @@ -118,7 +118,7 @@ - + @@ -161,12 +161,13 @@ 0.0 KB/s + + + :/images/black/32/arrow-down.png:/images/black/32/arrow-down.png + true - - :/images/light/16/down-arrow.png - @@ -211,12 +212,13 @@ 0.0 KB/s + + + :/images/black/32/arrow-up.png:/images/black/32/arrow-up.png + true - - :/images/light/16/up-arrow.png - @@ -237,6 +239,13 @@ + + + + Allow unencrypted traffic + + + diff --git a/src/leap/bitmask/services/eip/conductor.py b/src/leap/bitmask/services/eip/conductor.py index 53b1fde9..3c031952 100644 --- a/src/leap/bitmask/services/eip/conductor.py +++ b/src/leap/bitmask/services/eip/conductor.py @@ -114,6 +114,12 @@ class EIPConductor(object): """ self.qtsigs.do_connect_signal.emit() + def tear_fw_down(self): + """ + Tear the firewall down. + """ + self._backend.tear_fw_down() + @QtCore.Slot() def _start_eip(self): """ @@ -130,6 +136,7 @@ class EIPConductor(object): else: self._eip_status.eip_pre_up() self.user_stopped_eip = False + self._eip_status.hide_fw_down_button() # 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 @@ -138,7 +145,19 @@ class EIPConductor(object): self._eip_status.is_restart = False # DO the backend call! - self._backend.eip_start() + self._backend.eip_start(restart=is_restart) + + def reconnect_stop_signal(self): + """ + Restore the original behaviour associated with the disconnecting + signal, this is, trigger a normal stop, and not a restart one. + """ + + def do_stop(*args): + self._stop_eip(restart=False) + + self.qtsigs.disconnecting_signal.disconnect() + self.qtsigs.disconnecting_signal.connect(do_stop) @QtCore.Slot() def _stop_eip(self, restart=False, failed=False): @@ -178,10 +197,6 @@ class EIPConductor(object): def do_stop(*args): self._stop_eip(restart=False) - def reconnect_stop_signal(): - self.qtsigs.disconnecting_signal.disconnect() - self.qtsigs.disconnecting_signal.connect(do_stop) - if restart: # we bypass the on_eip_disconnected here plug_restart_on_disconnected() @@ -209,7 +224,7 @@ class EIPConductor(object): # XXX needed? if restart: - QtDelayedCall(3000, reconnect_stop_signal) + QtDelayedCall(2000, self.reconnect_stop_signal) @QtCore.Slot() def _do_eip_restart(self): diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 1de4a851..f56d464e 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -183,6 +183,8 @@ class VPN(object): kwargs['openvpn_verb'] = self._openvpn_verb kwargs['signaler'] = self._signaler + restart = kwargs.pop('restart', False) + # start the main vpn subprocess vpnproc = VPNProcess(*args, **kwargs) @@ -193,8 +195,9 @@ class VPN(object): # we try to bring the firewall up if IS_LINUX: gateways = vpnproc.getGateways() - firewall_up = self._launch_firewall(gateways) - if not firewall_up: + firewall_up = self._launch_firewall(gateways, + restart=restart) + if not restart and not firewall_up: logger.error("Could not bring firewall up, " "aborting openvpn launch.") return @@ -216,7 +219,7 @@ class VPN(object): self._pollers.extend(poll_list) self._start_pollers() - def _launch_firewall(self, gateways): + def _launch_firewall(self, gateways, restart=False): """ Launch the firewall using the privileged wrapper. @@ -231,8 +234,10 @@ class VPN(object): # XXX could check that the iptables rules are in place. BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT - exitCode = subprocess.call(["pkexec", - BM_ROOT, "firewall", "start"] + gateways) + cmd = ["pkexec", BM_ROOT, "firewall", "start"] + if restart: + cmd.append("restart") + exitCode = subprocess.call(cmd + gateways) return True if exitCode is 0 else False def is_fw_down(self): @@ -246,7 +251,7 @@ class VPN(object): fw_is_down = lambda: commands.getstatusoutput(fw_up_cmd)[0] == 256 return fw_is_down() - def _tear_down_firewall(self): + def tear_down_firewall(self): """ Tear the firewall down using the privileged wrapper. """ @@ -270,7 +275,7 @@ class VPN(object): # we try to tear the firewall down if IS_LINUX and self._user_stopped: - firewall_down = self._tear_down_firewall() + firewall_down = self.tear_down_firewall() if firewall_down: logger.debug("Firewall down") else: @@ -333,7 +338,7 @@ class VPN(object): self.TERMINATE_WAIT, self._kill_if_left_alive) if IS_LINUX and self._user_stopped: - firewall_down = self._tear_down_firewall() + firewall_down = self.tear_down_firewall() if firewall_down: logger.debug("Firewall down") else: -- cgit v1.2.3 From aeb89d2c64f8925d5063149e718ec2d97248b7c4 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 2 Jun 2014 17:27:47 -0500 Subject: add null checks, fix error on get_default_device. Closes: #5732 Also: -make firewall aware of restarts, and not tear down the fw if an error happens while a restart is going on. -notify errors to syslog. --- pkg/linux/bitmask-root | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 82e8799f..d1bf656e 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -22,14 +22,15 @@ It should only be called by the Bitmask application. USAGE: bitmask-root firewall stop - bitmask-root firewall start GATEWAY1 GATEWAY2 ... + bitmask-root firewall start [restart] GATEWAY1 GATEWAY2 ... bitmask-root openvpn stop bitmask-root openvpn start CONFIG1 CONFIG1 ... All actions return exit code 0 for success, non-zero otherwise. The `openvpn start` action is special: it calls exec on openvpn and replaces -the current process. +the current process. If the `restart` parameter is passed, the firewall will +not be teared down in the case of an error during launch. """ # TODO should be tested with python3, which can be the default on some distro. from __future__ import print_function @@ -38,12 +39,12 @@ import os import re import signal import socket +import syslog import subprocess import sys import time import traceback - cmdcheck = subprocess.check_output ## @@ -129,6 +130,8 @@ if DEBUG: logger.setLevel(logging.DEBUG) logger.addHandler(ch) +syslog.openlog(SCRIPT) + ## ## UTILITY ## @@ -413,6 +416,7 @@ def bail(msg=None, exception=None): """ if msg is not None: print("%s: %s" % (SCRIPT, msg)) + syslog.syslog(syslog.LOG_ERR, msg) if exception is not None: traceback.print_exc() exit(1) @@ -614,7 +618,7 @@ def get_default_device(): """ routes = subprocess.check_output([IP, "route", "show"]) match = re.search("^default .*dev ([^\s]*) .*$", routes, flags=re.M) - if match.groups(): + if match and match.groups(): return match.group(1) else: bail("Could not find default device") @@ -629,7 +633,7 @@ def get_local_network_ipv4(device): """ addresses = cmdcheck([IP, "-o", "address", "show", "dev", device]) match = re.search("^.*inet ([^ ]*) .*$", addresses, flags=re.M) - if match.groups(): + if match and match.groups(): return match.group(1) else: return None @@ -644,7 +648,7 @@ def get_local_network_ipv6(device): """ addresses = cmdcheck([IP, "-o", "address", "show", "dev", device]) match = re.search("^.*inet6 ([^ ]*) .*$", addresses, flags=re.M) - if match.groups(): + if match and match.groups(): return match.group(1) else: return None @@ -819,6 +823,11 @@ def main(): command = "_".join(sys.argv[1:3]) args = sys.argv[3:] + is_restart = False + if args and args[0] == "restart": + is_restart = True + args.remove('restart') + if command == "openvpn_start": openvpn_start(args) @@ -830,8 +839,9 @@ def main(): firewall_start(args) nameserver_setter.start(NAMESERVER) except Exception as ex: - nameserver_restorer.start() - firewall_stop() + if not is_restart: + nameserver_restorer.start() + firewall_stop() bail("ERROR: could not start firewall", ex) elif command == "firewall_stop": -- cgit v1.2.3 From 0626d6349472bdcd934328165cfe1a0e7d891bfe Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 4 Jun 2014 10:11:46 -0500 Subject: catch openvpn unexpected ending show also the back-to-clearnet button on that case. --- src/leap/bitmask/gui/eip_status.py | 5 +++++ src/leap/bitmask/gui/mainwindow.py | 1 + src/leap/bitmask/services/eip/conductor.py | 10 ++++++++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index fd8b35c3..7649f840 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -400,6 +400,11 @@ class EIPStatusWidget(QtGui.QWidget): self.eip_conductor._backend.tear_fw_down() QtDelayedCall(50, self.hide_fw_down_button) + # XXX do actual check + msg = "Traffic is being routed in the clear." + self.set_eip_message(msg) + self.set_eip_status("") + @QtCore.Slot(dict) def eip_stopped(self, restart=False, failed=False): """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index e7406106..968e5ead 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1564,6 +1564,7 @@ class MainWindow(QtGui.QMainWindow): if should_start and not self._already_started_eip: # XXX this should be handled by the state machine. + self._enable_eip_start_action() self._eip_status.set_eip_status( self.tr("Starting...")) self._eip_status.eip_button.setEnabled(False) diff --git a/src/leap/bitmask/services/eip/conductor.py b/src/leap/bitmask/services/eip/conductor.py index 3c031952..4c01225f 100644 --- a/src/leap/bitmask/services/eip/conductor.py +++ b/src/leap/bitmask/services/eip/conductor.py @@ -297,8 +297,11 @@ class EIPConductor(object): signal = self.qtsigs.connection_aborted_signal self._backend.eip_terminate() - # XXX FIXME --- check exitcode is != 0 really - if exitCode != 0 and not self.user_stopped_eip: + # XXX FIXME --- check exitcode is != 0 really. + # bitmask-root is masking the exitcode, so we might need + # to fix it on that side. + #if exitCode != 0 and not self.user_stopped_eip: + if not self.user_stopped_eip: eip_status_label = self._eip_status.tr( "{0} finished in an unexpected manner!") eip_status_label = eip_status_label.format(self.eip_name) @@ -307,6 +310,9 @@ class EIPConductor(object): self._eip_status.set_eip_status(eip_status_label, error=True) signal = self.qtsigs.connection_died_signal + self._eip_status.show_fw_down_button() + msg = self._eip_status.tr("Outgoing traffic is blocked") + self._eip_status.set_eip_message(msg) if exitCode == 0 and IS_MAC: # XXX remove this warning after I fix cocoasudo. -- cgit v1.2.3 From 532a4f63d219e9d9daa1a3252aa8909ca6d8030a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 4 Jun 2014 11:31:31 -0500 Subject: fix status errors during first eip connection - show eip button - update systray action - remove the provider update on disconnection. --- src/leap/bitmask/gui/eip_status.py | 9 ++++++++- src/leap/bitmask/gui/mainwindow.py | 22 +--------------------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 7649f840..f03a1a1d 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -332,6 +332,7 @@ class EIPStatusWidget(QtGui.QWidget): :type message: str or unicode """ self.ui.lblEIPMessage.setText(message) + self.ui.lblEIPMessage.show() def set_eip_status(self, status, error=False): """ @@ -515,6 +516,8 @@ class EIPStatusWidget(QtGui.QWidget): # when we detect vpn authentication is happening msg = self.tr("Encrypted Internet is starting") self.set_eip_message(msg) + # on the first-run path, we hadn't showed the button yet. + self.eip_button.show() elif vpn_state == "GET_CONFIG": self.set_eip_status(self.tr("Retrieving configuration...")) elif vpn_state == "WAIT": @@ -572,9 +575,10 @@ class EIPStatusWidget(QtGui.QWidget): def set_provider(self, provider): self._provider = provider + self.ui.lblEIPMessage.setText( self.tr("Routing traffic through: {0}").format( - self._provider)) + provider)) def aborted(self): """ @@ -594,6 +598,9 @@ class EIPStatusWidget(QtGui.QWidget): TRIGGERS: Signaler.eip_connection_aborted """ + # TODO this name is very misleading, since there's a generic signal + # that's called connection_aborted / connection_died... + # should rename to something more specific about missing config. logger.error("Tried to start EIP but cannot find any " "available provider!") diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 968e5ead..6d30e399 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -158,8 +158,6 @@ class MainWindow(QtGui.QMainWindow): self._eip_conductor.connect_signals() self._eip_conductor.qtsigs.connected_signal.connect( self._on_eip_connection_connected) - self._eip_conductor.qtsigs.disconnected_signal.connect( - self._on_eip_connection_disconnected) self._eip_conductor.qtsigs.connected_signal.connect( self._maybe_run_soledad_setup_checks) @@ -1448,6 +1446,7 @@ class MainWindow(QtGui.QMainWindow): Enables the EIP start action in the systray menu. """ self._action_eip_startstop.setEnabled(True) + self._eip_status.enable_eip_start() @QtCore.Slot() def _on_eip_connection_connected(self): @@ -1468,25 +1467,6 @@ class MainWindow(QtGui.QMainWindow): # check for connectivity self._check_name_resolution(domain) - @QtCore.Slot() - def _on_eip_connection_disconnected(self): - """ - TRIGGERS: - self._eip_conductor.qtsigs.disconnected_signal - - Workaround for updating the eip_status widget with - the provider when the eip connection disconnects. - """ - # TODO - # We should move this to the conductor<->widget interface. - # To do that, we need to subscribe to logged_user, - # for example by using the observer pattern or a proxy object. - user = self._logged_user - if user: - domain = self._login_widget.get_selected_provider() - full_user_id = make_address(user, domain) - self._eip_status.set_provider(full_user_id) - def _check_name_resolution(self, domain): # FIXME this has to be moved to backend !!! # Should move to netchecks module. -- cgit v1.2.3 From aba3ea21d83e6e073baf01643b68832530a6f4d0 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 4 Jun 2014 12:18:18 -0500 Subject: tear down fw on cold starts. Closes: ##5727 --- src/leap/bitmask/backend.py | 6 ++++++ src/leap/bitmask/gui/eip_status.py | 2 ++ src/leap/bitmask/gui/mainwindow.py | 2 ++ src/leap/bitmask/services/eip/conductor.py | 4 ++-- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index a18dffef..3c97c797 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -381,6 +381,9 @@ class EIP(object): def _start_eip(self, restart=False): """ Start EIP + + :param restart: whether is is a restart. + :type restart: bool """ provider_config = self._provider_config eip_config = eipconfig.EIPConfig() @@ -1742,6 +1745,9 @@ class Backend(object): eip_state_changed -> str eip_status_changed -> tuple of str (download, upload) eip_vpn_launcher_exception + + :param restart: whether is is a restart. + :type restart: bool """ self._call_queue.put(("eip", "start", None, restart)) diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index f03a1a1d..280ce79e 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -85,6 +85,7 @@ class EIPStatusWidget(QtGui.QWidget): self._provider = "" self.is_restart = False + self.is_cold_start = True # Action for the systray self._eip_disabled_action = QtGui.QAction( @@ -508,6 +509,7 @@ class EIPStatusWidget(QtGui.QWidget): # --- is this currently being sent? self.eipconnection.qtsigs.connected_signal.emit() self._on_eip_connected() + self.is_cold_start = False # XXX should lookup vpn_state map in EIPConnection elif vpn_state == "AUTH": diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 6d30e399..a3b81fde 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1543,6 +1543,8 @@ class MainWindow(QtGui.QMainWindow): should_start = self._provides_eip_and_enabled() if should_start and not self._already_started_eip: + if self._eip_status.is_cold_start: + self._backend.tear_fw_down() # XXX this should be handled by the state machine. self._enable_eip_start_action() self._eip_status.set_eip_status( diff --git a/src/leap/bitmask/services/eip/conductor.py b/src/leap/bitmask/services/eip/conductor.py index 4c01225f..cde53631 100644 --- a/src/leap/bitmask/services/eip/conductor.py +++ b/src/leap/bitmask/services/eip/conductor.py @@ -125,8 +125,8 @@ class EIPConductor(object): """ Starts EIP. """ - # FIXME --- pass is_restart parameter to here ??? - is_restart = self._eip_status and self._eip_status.is_restart + st = self._eip_status + is_restart = st and st.is_restart def reconnect(): self.qtsigs.disconnecting_signal.connect(self._stop_eip) -- cgit v1.2.3 From 6e19fd7c55ae1c4520536cb272b1ffcb6e8abd4c Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 5 Jun 2014 10:23:44 -0300 Subject: Add support for gnome-shell polkit agent. The gnome-shell desktop does not uses a separate process as others do. It uses an agent within its own process so we need to check for gnome-shell and then assume that its polkit is working. --- changes/bug-4144_support-gnome-shell-polkit | 1 + src/leap/bitmask/services/eip/linuxvpnlauncher.py | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 changes/bug-4144_support-gnome-shell-polkit diff --git a/changes/bug-4144_support-gnome-shell-polkit b/changes/bug-4144_support-gnome-shell-polkit new file mode 100644 index 00000000..b8da093c --- /dev/null +++ b/changes/bug-4144_support-gnome-shell-polkit @@ -0,0 +1 @@ +- Add support for gnome-shell polkit agent. Closes #4144, #4218. diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index 1f0813e0..54845a8a 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -71,7 +71,20 @@ def _is_auth_agent_running(): 'ps aux | grep "[l]xpolkit"' ] is_running = [commands.getoutput(cmd) for cmd in polkit_options] - return any(is_running) + + # gnome-shell does not uses a separate process for the polkit-agent, it + # uses a polkit-agent within its own process so we can't ps-grep it. + is_running = any(is_running) + if not is_running: + is_gnome_shell = commands.getoutput('ps aux | grep [g]nome-shell') + + # $DESKTOP_SESSION == 'gnome' -> gnome-shell + # $DESKTOP_SESSION == 'gnome-fallback' -> gnome-shell fallback mode, + # uses polkit-gnome... + if is_gnome_shell and os.getenv("DESKTOP_SESSION") == 'gnome': + is_running = True + + return is_running def _try_to_launch_agent(): -- cgit v1.2.3 From 0f1cc128cfa1c6d693639e5a4a70097eec11df1b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 5 Jun 2014 10:59:44 -0500 Subject: add version command --- changes/feature-bitmask-root-versioning | 1 + pkg/linux/bitmask-root | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 changes/feature-bitmask-root-versioning diff --git a/changes/feature-bitmask-root-versioning b/changes/feature-bitmask-root-versioning new file mode 100644 index 00000000..bfe69041 --- /dev/null +++ b/changes/feature-bitmask-root-versioning @@ -0,0 +1 @@ +- Add versioning support to bitmask-root. diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index d1bf656e..2c423da1 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -51,6 +51,7 @@ cmdcheck = subprocess.check_output ## CONSTANTS ## +VERSION = "1" SCRIPT = "bitmask-root" NAMESERVER = "10.42.0.1" BITMASK_CHAIN = "bitmask" @@ -819,7 +820,12 @@ def firewall_stop(): def main(): - if len(sys.argv) >= 3: + """ + Entry point for cmdline execution. + """ + # TODO use argparse instead. + + if len(sys.argv) >= 2: command = "_".join(sys.argv[1:3]) args = sys.argv[3:] @@ -828,6 +834,10 @@ def main(): is_restart = True args.remove('restart') + if command == "version": + print(VERSION) + exit(0) + if command == "openvpn_start": openvpn_start(args) -- cgit v1.2.3 From 15758f43dd53f3b330b9786b531d7d3924f4b106 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 5 Jun 2014 11:08:46 -0500 Subject: check for uid == 0 --- pkg/linux/bitmask-root | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index 2c423da1..c6685877 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -838,6 +838,9 @@ def main(): print(VERSION) exit(0) + if os.getuid() != 0: + bail("ERROR: must be run as root") + if command == "openvpn_start": openvpn_start(args) -- cgit v1.2.3 From 460429b6016046fd91e521b371da1b9eb75735a5 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 5 Jun 2014 11:00:05 -0500 Subject: pep8 cleanup --- pkg/linux/bitmask-root | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index c6685877..1929b51b 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -765,11 +765,13 @@ def firewall_start(args): "--jump", "ACCEPT") # allow multicast Simple Service Discovery Protocol ip4tables("--append", BITMASK_CHAIN, - "--protocol", "udp", "--destination", "239.255.255.250", "--dport", "1900", + "--protocol", "udp", + "--destination", "239.255.255.250", "--dport", "1900", "-o", default_device, "--jump", "RETURN") # allow multicast Bonjour/mDNS ip4tables("--append", BITMASK_CHAIN, - "--protocol", "udp", "--destination", "224.0.0.251", "--dport", "5353", + "--protocol", "udp", + "--destination", "224.0.0.251", "--dport", "5353", "-o", default_device, "--jump", "RETURN") if local_network_ipv6: ip6tables("--append", BITMASK_CHAIN, @@ -777,11 +779,13 @@ def firewall_start(args): "--jump", "ACCEPT") # allow multicast Simple Service Discovery Protocol ip6tables("--append", BITMASK_CHAIN, - "--protocol", "udp", "--destination", "FF05::C", "--dport", "1900", + "--protocol", "udp", + "--destination", "FF05::C", "--dport", "1900", "-o", default_device, "--jump", "RETURN") # allow multicast Bonjour/mDNS ip6tables("--append", BITMASK_CHAIN, - "--protocol", "udp", "--destination", "FF02::FB", "--dport", "5353", + "--protocol", "udp", + "--destination", "FF02::FB", "--dport", "5353", "-o", default_device, "--jump", "RETURN") # allow ipv4 traffic to gateways @@ -792,15 +796,19 @@ def firewall_start(args): # log rejected packets to syslog if DEBUG: iptables("--append", BITMASK_CHAIN, "-o", default_device, - "--jump", "LOG", "--log-prefix", "iptables denied: ", "--log-level", "7") + "--jump", "LOG", "--log-prefix", "iptables denied: ", + "--log-level", "7") - # for now, ensure all other ipv6 packets get rejected (regardless of device) + # for now, ensure all other ipv6 packets get rejected (regardless of + # device) # (not sure why, but "-p any" doesn't work) ip6tables("--append", BITMASK_CHAIN, "-p", "tcp", "--jump", "REJECT") ip6tables("--append", BITMASK_CHAIN, "-p", "udp", "--jump", "REJECT") # reject all other ipv4 sent over the default device - ip4tables("--append", BITMASK_CHAIN, "-o", default_device, "--jump", "REJECT") + ip4tables("--append", BITMASK_CHAIN, "-o", + default_device, "--jump", "REJECT") + def firewall_stop(): """ @@ -853,8 +861,8 @@ def main(): nameserver_setter.start(NAMESERVER) except Exception as ex: if not is_restart: - nameserver_restorer.start() - firewall_stop() + nameserver_restorer.start() + firewall_stop() bail("ERROR: could not start firewall", ex) elif command == "firewall_stop": -- cgit v1.2.3 From d550c15da5eed6c51735fcc0ef50bf004dd9e0e6 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 5 Jun 2014 11:00:22 -0500 Subject: add bitmask-root notes to checklist --- docs/release_checklist.wiki | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release_checklist.wiki b/docs/release_checklist.wiki index fc99fdf0..075591a7 100644 --- a/docs/release_checklist.wiki +++ b/docs/release_checklist.wiki @@ -1,5 +1,6 @@ = Bitmask Release Checklist (*) = * [ ] Check that all tests are passing! + * [ ] Check that the version in bitmask_client/pkg/linux/bitmask-root is bumped if needed. * [ ] Tag everything * Should be done for the following packages, in order: * [ ] 1. leap.common -- cgit v1.2.3 From 556c589dd470ed2b48b5421c38156333da78c369 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 5 Jun 2014 11:18:43 -0500 Subject: update manpages --- docs/man/bitmask-root.1.rst | 17 ++++++++++++----- docs/man/bitmask.1.rst | 4 ++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/man/bitmask-root.1.rst b/docs/man/bitmask-root.1.rst index 7ed53aa9..c18cc4d6 100644 --- a/docs/man/bitmask-root.1.rst +++ b/docs/man/bitmask-root.1.rst @@ -7,23 +7,24 @@ privileged helper for bitmask, the encrypted internet access toolkit. ------------------------------------------------------------------------ :Author: LEAP Encryption Access Project https://leap.se -:Date: 2014-05-19 +:Date: 2014-06-05 :Copyright: GPLv3+ -:Version: 0.5.1 +:Version: 0.5.2 :Manual section: 1 :Manual group: General Commands Manual SYNOPSIS ======== -bitmask-root [openvpn | firewall | isup ] [start | stop] [ARGS] +bitmask-root [openvpn | firewall | version] [start | stop | isup] [ARGS] DESCRIPTION =========== *bitmask-root* is a privileged helper for bitmask. -It is used to start or stop openvpn and the bitmask firewall. +It is used to start or stop openvpn and the bitmask firewall. To operate, it +needs to be executed with root privileges. OPTIONS @@ -33,7 +34,9 @@ openvpn -------- **start** [ARGS] Starts openvpn. All args are passed to openvpn, and - filtered against a list of allowed args. + filtered against a list of allowed args. If the next + argument is `restart`, the firewall will not be teared + down in the case of errors lauching openvpn. **stop** Stops openvpn. @@ -46,6 +49,10 @@ firewall **stop** Stops the firewall. +version +-------- + +**version** Prints the `bitmask-root` version string. BUGS diff --git a/docs/man/bitmask.1.rst b/docs/man/bitmask.1.rst index 38da64af..6eae7ff5 100644 --- a/docs/man/bitmask.1.rst +++ b/docs/man/bitmask.1.rst @@ -7,9 +7,9 @@ graphical client to control LEAP, the encrypted internet access toolkit. ------------------------------------------------------------------------ :Author: LEAP Encryption Access Project https://leap.se -:Date: 2014-05-19 +:Date: 2014-06-05 :Copyright: GPLv3+ -:Version: 0.5.1 +:Version: 0.5.2 :Manual section: 1 :Manual group: General Commands Manual -- cgit v1.2.3 From 25ccc3bf2d6cf6618ddcf717a232db53763f1573 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 5 Jun 2014 11:39:19 -0500 Subject: style fixes --- src/leap/bitmask/platform_init/initializers.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index 14d96c9b..b163e1bd 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -14,11 +14,9 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - """ -Platform dependant initializing code +Platform-dependant initialization code. """ - import logging import os import platform @@ -47,7 +45,7 @@ _system = platform.system() def init_platform(): """ - Returns the right initializer for the platform we are running in, or + Return the right initializer for the platform we are running in, or None if no proper initializer is found """ initializer = None @@ -79,7 +77,7 @@ UPDOWN_BADEXEC_MSG = BADEXEC_MSG % ( def get_missing_updown_dialog(): """ - Creates a dialog for notifying of missing updown scripts. + Create a dialog for notifying of missing updown scripts. Returns that dialog. :rtype: QtGui.QMessageBox instance @@ -101,7 +99,7 @@ def get_missing_updown_dialog(): def check_missing(): """ - Checks for the need of installing missing scripts, and + Check for the need of installing missing scripts, and raises a dialog to ask user for permission to do it. """ config = LeapSettings() @@ -149,7 +147,7 @@ def check_missing(): def _windows_has_tap_device(): """ - Loops over the windows registry trying to find if the tap0901 tap driver + Loop over the windows registry trying to find if the tap0901 tap driver has been installed on this machine. """ import _winreg as reg @@ -175,7 +173,7 @@ def _windows_has_tap_device(): def WindowsInitializer(): """ - Raises a dialog in case that the windows tap driver has not been found + Raise a dialog in case that the windows tap driver has not been found in the registry, asking the user for permission to install the driver """ if not _windows_has_tap_device(): @@ -219,7 +217,7 @@ def WindowsInitializer(): def _darwin_has_tun_kext(): """ - Returns True only if we found a directory under the system kext folder + Return True only if we found a directory under the system kext folder containing a kext named tun.kext, AND we found a startup item named 'tun' """ # XXX we should be smarter here and use kextstats output. @@ -235,7 +233,7 @@ def _darwin_has_tun_kext(): def _darwin_install_missing_scripts(badexec, notfound): """ - Tries to install the missing up/down scripts. + Try to install the missing up/down scripts. :param badexec: error for notifying execution error during command. :type badexec: str @@ -290,7 +288,7 @@ def _darwin_install_missing_scripts(badexec, notfound): def DarwinInitializer(): """ - Raises a dialog in case that the osx tuntap driver has not been found + Raise a dialog in case that the osx tuntap driver has not been found in the registry, asking the user for permission to install the driver """ # XXX split this function into several @@ -346,7 +344,7 @@ def DarwinInitializer(): # def _linux_install_missing_scripts(badexec, notfound): """ - Tries to install the missing up/down scripts. + Try to install the missing up/down scripts. :param badexec: error for notifying execution error during command. :type badexec: str @@ -397,7 +395,7 @@ def _linux_install_missing_scripts(badexec, notfound): def LinuxInitializer(): """ - Raises a dialog in case that either updown scripts or policykit file + Raise a dialog in case that either updown scripts or policykit file are missing or they have incorrect permissions. """ check_missing() -- cgit v1.2.3 From f79943067681dbbfa2e78d0e50261d0938129aef Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 5 Jun 2014 12:13:03 -0500 Subject: remove BOM char from relnotes.txt. Closes: #5745 --- relnotes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relnotes.txt b/relnotes.txt index a658a782..2704d6f0 100644 --- a/relnotes.txt +++ b/relnotes.txt @@ -1,4 +1,4 @@ -ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.5.1 +ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.5.1 The LEAP team is pleased to announce the immediate availability of version 0.5.1 of Bitmask, the Internet Encryption Toolkit, codename -- cgit v1.2.3 From e1f3552c9c368661f9cc1c8be3ddef6017557402 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 5 Jun 2014 17:14:08 -0300 Subject: Grep for gnome-shell as it were a polkit agent. The $DESKTOP_SESSION is not reliable since is set to 'default' in any case that you choose as your default desktop environment. Also, after some tests, I've seen that the `gnome-shell` process is not launched in the fallback mode, so we use it as a condition for the polkit agent present. --- src/leap/bitmask/services/eip/linuxvpnlauncher.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index 54845a8a..955768d1 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -63,28 +63,21 @@ def _is_auth_agent_running(): :return: True if it's running, False if it's not. :rtype: boolean """ + # Note that gnome-shell does not uses a separate process for the + # polkit-agent, it uses a polkit-agent within its own process so we can't + # ps-grep a polkit process, we can ps-grep gnome-shell itself. + # 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 "polkit-[m]ate-authentication-agent-1"', - 'ps aux | grep "[l]xpolkit"' + 'ps aux | grep "[l]xpolkit"', + 'ps aux | grep "[g]nome-shell"', ] is_running = [commands.getoutput(cmd) for cmd in polkit_options] - # gnome-shell does not uses a separate process for the polkit-agent, it - # uses a polkit-agent within its own process so we can't ps-grep it. - is_running = any(is_running) - if not is_running: - is_gnome_shell = commands.getoutput('ps aux | grep [g]nome-shell') - - # $DESKTOP_SESSION == 'gnome' -> gnome-shell - # $DESKTOP_SESSION == 'gnome-fallback' -> gnome-shell fallback mode, - # uses polkit-gnome... - if is_gnome_shell and os.getenv("DESKTOP_SESSION") == 'gnome': - is_running = True - - return is_running + return any(is_running) def _try_to_launch_agent(): -- cgit v1.2.3 From 2e0062555fd0a092e0f9f25ac46d189b44805108 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 5 Jun 2014 15:21:18 -0300 Subject: Reorder logging helpers and handlers. --- src/leap/bitmask/app.py | 105 ++-------------- src/leap/bitmask/backend_app.py | 0 src/leap/bitmask/frontend_app.py | 0 src/leap/bitmask/gui/loggerwindow.py | 2 +- src/leap/bitmask/gui/mainwindow.py | 2 +- src/leap/bitmask/logs/__init__.py | 3 + src/leap/bitmask/logs/leap_log_handler.py | 137 +++++++++++++++++++++ src/leap/bitmask/logs/log_silencer.py | 93 ++++++++++++++ src/leap/bitmask/logs/streamtologger.py | 59 +++++++++ .../bitmask/logs/tests/test_leap_log_handler.py | 120 ++++++++++++++++++ src/leap/bitmask/logs/tests/test_streamtologger.py | 126 +++++++++++++++++++ src/leap/bitmask/logs/utils.py | 92 ++++++++++++++ src/leap/bitmask/util/__init__.py | 5 - src/leap/bitmask/util/leap_log_handler.py | 137 --------------------- src/leap/bitmask/util/log_silencer.py | 93 -------------- src/leap/bitmask/util/streamtologger.py | 59 --------- .../bitmask/util/tests/test_leap_log_handler.py | 120 ------------------ src/leap/bitmask/util/tests/test_streamtologger.py | 126 ------------------- 18 files changed, 641 insertions(+), 638 deletions(-) create mode 100644 src/leap/bitmask/backend_app.py create mode 100644 src/leap/bitmask/frontend_app.py create mode 100644 src/leap/bitmask/logs/__init__.py create mode 100644 src/leap/bitmask/logs/leap_log_handler.py create mode 100644 src/leap/bitmask/logs/log_silencer.py create mode 100644 src/leap/bitmask/logs/streamtologger.py create mode 100644 src/leap/bitmask/logs/tests/test_leap_log_handler.py create mode 100644 src/leap/bitmask/logs/tests/test_streamtologger.py create mode 100644 src/leap/bitmask/logs/utils.py delete mode 100644 src/leap/bitmask/util/leap_log_handler.py delete mode 100644 src/leap/bitmask/util/log_silencer.py delete mode 100644 src/leap/bitmask/util/streamtologger.py delete mode 100644 src/leap/bitmask/util/tests/test_leap_log_handler.py delete mode 100644 src/leap/bitmask/util/tests/test_streamtologger.py diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 05f81f0b..e965604a 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -39,7 +39,6 @@ # M:::::::::::~NMMM7???7MMMM:::::::::::::::::::::::NMMMI??I7MMMM:::::::::::::M # M::::::::::::::7MMMMMMM+:::::::::::::::::::::::::::?MMMMMMMZ:::::::::::::::M # (thanks to: http://www.glassgiant.com/ascii/) -import logging import signal import sys import os @@ -50,10 +49,7 @@ from PySide import QtCore, QtGui from leap.bitmask import __version__ as VERSION from leap.bitmask.util import leap_argparse -from leap.bitmask.util import log_silencer, LOG_FORMAT -from leap.bitmask.util.leap_log_handler import LeapLogHandler -from leap.bitmask.util.streamtologger import StreamToLogger -from leap.bitmask.platform_init import IS_WIN +from leap.bitmask.logs.utils import get_logger from leap.bitmask.services.mail import plumber from leap.common.events import server as event_server from leap.mail import __version__ as MAIL_VERSION @@ -89,90 +85,6 @@ def sigterm_handler(*args, **kwargs): mainwindow.quit() -def add_logger_handlers(debug=False, logfile=None, replace_stdout=True): - """ - Create the logger and attach the handlers. - - :param debug: the level of the messages that we should log - :type debug: bool - :param logfile: the file name of where we should to save the logs - :type logfile: str - :return: the new logger with the attached handlers. - :rtype: logging.Logger - """ - # TODO: get severity from command line args - if debug: - level = logging.DEBUG - else: - level = logging.WARNING - - # Create logger and formatter - logger = logging.getLogger(name='leap') - logger.setLevel(level) - formatter = logging.Formatter(LOG_FORMAT) - - # Console handler - try: - import coloredlogs - console = coloredlogs.ColoredStreamHandler(level=level) - except ImportError: - console = logging.StreamHandler() - console.setLevel(level) - console.setFormatter(formatter) - using_coloredlog = False - else: - using_coloredlog = True - - if using_coloredlog: - replace_stdout = False - - silencer = log_silencer.SelectiveSilencerFilter() - console.addFilter(silencer) - logger.addHandler(console) - logger.debug('Console handler plugged!') - - # LEAP custom handler - leap_handler = LeapLogHandler() - leap_handler.setLevel(level) - leap_handler.addFilter(silencer) - logger.addHandler(leap_handler) - logger.debug('Leap handler plugged!') - - # File handler - if logfile is not None: - logger.debug('Setting logfile to %s ', logfile) - fileh = logging.FileHandler(logfile) - fileh.setLevel(logging.DEBUG) - fileh.setFormatter(formatter) - fileh.addFilter(silencer) - logger.addHandler(fileh) - logger.debug('File handler plugged!') - - if replace_stdout: - replace_stdout_stderr_with_logging(logger) - - return logger - - -def replace_stdout_stderr_with_logging(logger): - """ - Replace: - - the standard output - - the standard error - - the twisted log output - with a custom one that writes to the logger. - """ - # Disabling this on windows since it breaks ALL THE THINGS - # The issue for this is #4149 - if not IS_WIN: - sys.stdout = StreamToLogger(logger, logging.DEBUG) - sys.stderr = StreamToLogger(logger, logging.ERROR) - - # Replace twisted's logger to use our custom output. - from twisted.python import log - log.startLogging(sys.stdout) - - def do_display_version(opts): """ Display version and exit. @@ -214,6 +126,14 @@ def main(): mail_logfile = opts.mail_log_file start_hidden = opts.start_hidden + replace_stdout = True + if opts.repair or opts.import_maildir: + # We don't want too much clutter on the comand mode + # this could be more generic with a Command class. + replace_stdout = False + + logger = get_logger(debug, logfile, replace_stdout) + ############################################################# # Given how paths and bundling works, we need to delay the imports # of certain parts that depend on this path settings. @@ -232,13 +152,6 @@ def main(): BaseConfig.standalone = standalone - replace_stdout = True - if opts.repair or opts.import_maildir: - # We don't want too much clutter on the comand mode - # this could be more generic with a Command class. - replace_stdout = False - logger = add_logger_handlers(debug, logfile, replace_stdout) - # ok, we got logging in place, we can satisfy mail plumbing requests # and show logs there. it normally will exit there if we got that path. do_mail_plumbing(opts) diff --git a/src/leap/bitmask/backend_app.py b/src/leap/bitmask/backend_app.py new file mode 100644 index 00000000..e69de29b diff --git a/src/leap/bitmask/frontend_app.py b/src/leap/bitmask/frontend_app.py new file mode 100644 index 00000000..e69de29b diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py index f19b172f..3a8354b1 100644 --- a/src/leap/bitmask/gui/loggerwindow.py +++ b/src/leap/bitmask/gui/loggerwindow.py @@ -27,7 +27,7 @@ from twisted.internet import threads from ui_loggerwindow import Ui_LoggerWindow from leap.bitmask.util.constants import PASTEBIN_API_DEV_KEY -from leap.bitmask.util.leap_log_handler import LeapLogHandler +from leap.bitmask.logs.leap_log_handler import LeapLogHandler from leap.bitmask.util import pastebin from leap.common.check import leap_assert, leap_assert_type diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index a3b81fde..c61b7dc9 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -52,7 +52,7 @@ from leap.bitmask.services import EIP_SERVICE, MX_SERVICE from leap.bitmask.util import make_address from leap.bitmask.util.keyring_helpers import has_keyring -from leap.bitmask.util.leap_log_handler import LeapLogHandler +from leap.bitmask.logs.leap_log_handler import LeapLogHandler if IS_WIN: from leap.bitmask.platform_init.locks import WindowsLock diff --git a/src/leap/bitmask/logs/__init__.py b/src/leap/bitmask/logs/__init__.py new file mode 100644 index 00000000..0516b304 --- /dev/null +++ b/src/leap/bitmask/logs/__init__.py @@ -0,0 +1,3 @@ +# levelname length == 8, since 'CRITICAL' is the longest +LOG_FORMAT = ('%(asctime)s - %(levelname)-8s - ' + 'L#%(lineno)-4s : %(name)s:%(funcName)s() - %(message)s') diff --git a/src/leap/bitmask/logs/leap_log_handler.py b/src/leap/bitmask/logs/leap_log_handler.py new file mode 100644 index 00000000..24141638 --- /dev/null +++ b/src/leap/bitmask/logs/leap_log_handler.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# leap_log_handler.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 . +""" +Custom handler for the logger window. +""" +import logging + +from PySide import QtCore + +from leap.bitmask.logs import LOG_FORMAT + + +class LogHandler(logging.Handler): + """ + This is the custom handler that implements our desired formatting + and also keeps a history of all the logged events. + """ + + MESSAGE_KEY = 'message' + RECORD_KEY = 'record' + + def __init__(self, qtsignal): + """ + LogHander initialization. + Calls parent method and keeps a reference to the qtsignal + that will be used to fire the gui update. + """ + # TODO This is going to eat lots of memory after some time. + # Should be pruned at some moment. + self._log_history = [] + + logging.Handler.__init__(self) + self._qtsignal = qtsignal + + def _get_format(self, logging_level): + """ + Sets the log format depending on the parameter. + It uses html and css to set the colors for the logs. + + :param logging_level: the debug level to define the color. + :type logging_level: str. + """ + formatter = logging.Formatter(LOG_FORMAT) + return formatter + + def emit(self, logRecord): + """ + This method is fired every time that a record is logged by the + logging module. + This method reimplements logging.Handler.emit that is fired + in every logged message. + + :param logRecord: the record emitted by the logging module. + :type logRecord: logging.LogRecord. + """ + self.setFormatter(self._get_format(logRecord.levelname)) + log = self.format(logRecord) + log_item = {self.RECORD_KEY: logRecord, self.MESSAGE_KEY: log} + self._log_history.append(log_item) + self._qtsignal(log_item) + + +class HandlerAdapter(object): + """ + New style class that accesses all attributes from the LogHandler. + + Used as a workaround for a problem with multiple inheritance with Pyside + that surfaced under OSX with pyside 1.1.0. + """ + MESSAGE_KEY = 'message' + RECORD_KEY = 'record' + + def __init__(self, qtsignal): + self._handler = LogHandler(qtsignal=qtsignal) + + def setLevel(self, *args, **kwargs): + return self._handler.setLevel(*args, **kwargs) + + def addFilter(self, *args, **kwargs): + return self._handler.addFilter(*args, **kwargs) + + def handle(self, *args, **kwargs): + return self._handler.handle(*args, **kwargs) + + @property + def level(self): + return self._handler.level + + +class LeapLogHandler(QtCore.QObject, HandlerAdapter): + """ + Custom logging handler. It emits Qt signals so it can be plugged to a gui. + + Its inner handler also stores an history of logs that can be fetched after + having been connected to a gui. + """ + # All dicts returned are of the form + # {'record': LogRecord, 'message': str} + new_log = QtCore.Signal(dict) + + def __init__(self): + """ + LeapLogHandler initialization. + Initializes parent classes. + """ + QtCore.QObject.__init__(self) + HandlerAdapter.__init__(self, qtsignal=self.qtsignal) + + def qtsignal(self, log_item): + # WARNING: the new-style connection does NOT work because PySide + # translates the emit method to self.emit, and that collides with + # the emit method for logging.Handler + # self.new_log.emit(log_item) + QtCore.QObject.emit( + self, + QtCore.SIGNAL('new_log(PyObject)'), log_item) + + @property + def log_history(self): + """ + Returns the history of the logged messages. + """ + return self._handler._log_history diff --git a/src/leap/bitmask/logs/log_silencer.py b/src/leap/bitmask/logs/log_silencer.py new file mode 100644 index 00000000..56b290e4 --- /dev/null +++ b/src/leap/bitmask/logs/log_silencer.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# log_silencer.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 . +""" +Filter for leap logs. +""" +import logging +import os +import re + +from leap.bitmask.util import get_path_prefix + + +class SelectiveSilencerFilter(logging.Filter): + """ + Configurable filter for root leap logger. + + If you want to ignore components from the logging, just add them, + one by line, to ~/.config/leap/leap.dev.conf + """ + # TODO we can augment this by properly parsing the log-silencer file + # and having different sections: ignore, levels, ... + + # TODO use ConfigParser to unify sections [log-ignore] [log-debug] etc + + CONFIG_NAME = "leap.dev.conf" + + # Components to be completely silenced in the main bitmask logs. + # You probably should think twice before adding a component to + # the tuple below. Only very well tested components should go here, and + # only in those cases in which we gain more from silencing them than from + # having their logs into the main log file that the user will likely send + # to us. + SILENCER_RULES = ( + 'leap.common.events', + 'leap.common.decorators', + ) + + def __init__(self): + """ + Tries to load silencer rules from the default path, + or load from the SILENCER_RULES tuple if not found. + """ + self.rules = None + if os.path.isfile(self._rules_path): + self.rules = self._load_rules() + if not self.rules: + self.rules = self.SILENCER_RULES + + @property + def _rules_path(self): + """ + The configuration file for custom ignore rules. + """ + return os.path.join(get_path_prefix(), "leap", self.CONFIG_NAME) + + def _load_rules(self): + """ + Loads a list of paths to be ignored from the logging. + """ + lines = open(self._rules_path).readlines() + return map(lambda line: re.sub('\s', '', line), + lines) + + def filter(self, record): + """ + Implements the filter functionality for this Filter + + :param record: the record to be examined + :type record: logging.LogRecord + :returns: a bool indicating whether the record should be logged or not. + :rtype: bool + """ + if not self.rules: + return True + logger_path = record.name + for path in self.rules: + if logger_path.startswith(path): + return False + return True diff --git a/src/leap/bitmask/logs/streamtologger.py b/src/leap/bitmask/logs/streamtologger.py new file mode 100644 index 00000000..25a06718 --- /dev/null +++ b/src/leap/bitmask/logs/streamtologger.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# streamtologger.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 . +""" +Stream object that redirects writes to a logger instance. +""" +import logging + + +class StreamToLogger(object): + """ + Fake file-like stream object that redirects writes to a logger instance. + + Credits to: + http://www.electricmonk.nl/log/2011/08/14/\ + redirect-stdout-and-stderr-to-a-logger-in-python/ + """ + def __init__(self, logger, log_level=logging.INFO): + """ + Constructor, defines the logger and level to use to log messages. + + :param logger: logger object to log messages. + :type logger: logging.Handler + :param log_level: the level to use to log messages through the logger. + :type log_level: int + look at logging-levels in 'logging' docs. + """ + self._logger = logger + self._log_level = log_level + + def write(self, data): + """ + Simulates the 'write' method in a file object. + It writes the data receibed in buf to the logger 'self._logger'. + + :param data: data to write to the 'file' + :type data: str + """ + for line in data.rstrip().splitlines(): + self._logger.log(self._log_level, line.rstrip()) + + def flush(self): + """ + Dummy method. Needed to replace the twisted.log output. + """ + pass diff --git a/src/leap/bitmask/logs/tests/test_leap_log_handler.py b/src/leap/bitmask/logs/tests/test_leap_log_handler.py new file mode 100644 index 00000000..20b09aef --- /dev/null +++ b/src/leap/bitmask/logs/tests/test_leap_log_handler.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# test_leap_log_handler.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 . +""" +tests for leap_log_handler +""" +try: + import unittest2 as unittest +except ImportError: + import unittest + +import logging + +from leap.bitmask.logs.leap_log_handler import LeapLogHandler +from leap.bitmask.util.pyside_tests_helper import BasicPySlotCase +from leap.common.testing.basetest import BaseLeapTest + +from mock import Mock + + +class LeapLogHandlerTest(BaseLeapTest, BasicPySlotCase): + """ + LeapLogHandlerTest's tests. + """ + def _callback(self, *args): + """ + Simple callback to track if a signal was emitted. + """ + self.called = True + self.emitted_msg = args[0][LeapLogHandler.MESSAGE_KEY] + + def setUp(self): + BasicPySlotCase.setUp(self) + + # Create the logger + level = logging.DEBUG + self.logger = logging.getLogger(name='test') + self.logger.setLevel(level) + + # Create the handler + self.leap_handler = LeapLogHandler() + self.leap_handler.setLevel(level) + self.logger.addHandler(self.leap_handler) + + def tearDown(self): + BasicPySlotCase.tearDown(self) + try: + self.leap_handler.new_log.disconnect() + except Exception: + pass + + def test_history_starts_empty(self): + self.assertEqual(self.leap_handler.log_history, []) + + def test_one_log_captured(self): + self.logger.debug('test') + self.assertEqual(len(self.leap_handler.log_history), 1) + + def test_history_records_order(self): + self.logger.debug('test 01') + self.logger.debug('test 02') + self.logger.debug('test 03') + + logs = [] + for message in self.leap_handler.log_history: + logs.append(message[LeapLogHandler.RECORD_KEY].msg) + + self.assertIn('test 01', logs) + self.assertIn('test 02', logs) + self.assertIn('test 03', logs) + + def test_history_messages_order(self): + self.logger.debug('test 01') + self.logger.debug('test 02') + self.logger.debug('test 03') + + logs = [] + for message in self.leap_handler.log_history: + logs.append(message[LeapLogHandler.MESSAGE_KEY]) + + self.assertIn('test 01', logs[0]) + self.assertIn('test 02', logs[1]) + self.assertIn('test 03', logs[2]) + + def test_emits_signal(self): + log_format = '%(name)s - %(levelname)s - %(message)s' + formatter = logging.Formatter(log_format) + get_format = Mock(return_value=formatter) + self.leap_handler._handler._get_format = get_format + + self.leap_handler.new_log.connect(self._callback) + self.logger.debug('test') + + expected_log_msg = "test - DEBUG - test" + + # signal emitted + self.assertTrue(self.called) + + # emitted message + self.assertEqual(self.emitted_msg, expected_log_msg) + + # Mock called + self.assertTrue(get_format.called) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/leap/bitmask/logs/tests/test_streamtologger.py b/src/leap/bitmask/logs/tests/test_streamtologger.py new file mode 100644 index 00000000..9bbadde8 --- /dev/null +++ b/src/leap/bitmask/logs/tests/test_streamtologger.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +# test_streamtologger.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 . +""" +tests for streamtologger +""" + +try: + import unittest2 as unittest +except ImportError: + import unittest + +import logging +import sys + +from leap.bitmask.logs.streamtologger import StreamToLogger +from leap.common.testing.basetest import BaseLeapTest + + +class SimpleLogHandler(logging.Handler): + """ + The simplest log handler that allows to check if the log was + delivered to the handler correctly. + """ + def __init__(self): + logging.Handler.__init__(self) + self._last_log = "" + self._last_log_level = "" + + def emit(self, record): + self._last_log = record.getMessage() + self._last_log_level = record.levelno + + def get_last_log(self): + """ + Returns the last logged message by this handler. + + :return: the last logged message. + :rtype: str + """ + return self._last_log + + def get_last_log_level(self): + """ + Returns the level of the last logged message by this handler. + + :return: the last logged level. + :rtype: str + """ + return self._last_log_level + + +class StreamToLoggerTest(BaseLeapTest): + """ + StreamToLogger's tests. + + NOTE: we may need to find a way to test the use case that an exception + is raised. I couldn't catch the output of an exception because the + test failed if some exception is raised. + """ + def setUp(self): + # Create the logger + level = logging.DEBUG + self.logger = logging.getLogger(name='test') + self.logger.setLevel(level) + + # Simple log handler + self.handler = SimpleLogHandler() + self.logger.addHandler(self.handler) + + # Preserve original values + self._sys_stdout = sys.stdout + self._sys_stderr = sys.stderr + + # Create the handler + sys.stdout = StreamToLogger(self.logger, logging.DEBUG) + sys.stderr = StreamToLogger(self.logger, logging.ERROR) + + def tearDown(self): + # Restore original values + sys.stdout = self._sys_stdout + sys.stderr = self._sys_stderr + + def test_logger_starts_empty(self): + self.assertEqual(self.handler.get_last_log(), '') + + def test_standard_output(self): + message = 'Test string' + print message + + log = self.handler.get_last_log() + log_level = self.handler.get_last_log_level() + + self.assertEqual(log, message) + self.assertEqual(log_level, logging.DEBUG) + + def test_standard_error(self): + message = 'Test string' + sys.stderr.write(message) + + log_level = self.handler.get_last_log_level() + log = self.handler.get_last_log() + + self.assertEqual(log, message) + self.assertEqual(log_level, logging.ERROR) + + def test_twisted_log(self): + from twisted.python import log + log.startLogging(sys.stdout) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/src/leap/bitmask/logs/utils.py b/src/leap/bitmask/logs/utils.py new file mode 100644 index 00000000..06959c45 --- /dev/null +++ b/src/leap/bitmask/logs/utils.py @@ -0,0 +1,92 @@ +import logging +import sys + +from leap.bitmask.logs import LOG_FORMAT +from leap.bitmask.logs.log_silencer import SelectiveSilencerFilter +from leap.bitmask.logs.leap_log_handler import LeapLogHandler +from leap.bitmask.logs.streamtologger import StreamToLogger +from leap.bitmask.platform_init import IS_WIN + + +def get_logger(debug=False, logfile=None, replace_stdout=True): + """ + Create the logger and attach the handlers. + + :param debug: the level of the messages that we should log + :type debug: bool + :param logfile: the file name of where we should to save the logs + :type logfile: str + :return: the new logger with the attached handlers. + :rtype: logging.Logger + """ + # TODO: get severity from command line args + if debug: + level = logging.DEBUG + else: + level = logging.WARNING + + # Create logger and formatter + logger = logging.getLogger(name='leap') + logger.setLevel(level) + formatter = logging.Formatter(LOG_FORMAT) + + # Console handler + try: + import coloredlogs + console = coloredlogs.ColoredStreamHandler(level=level) + except ImportError: + console = logging.StreamHandler() + console.setLevel(level) + console.setFormatter(formatter) + using_coloredlog = False + else: + using_coloredlog = True + + if using_coloredlog: + replace_stdout = False + + silencer = SelectiveSilencerFilter() + console.addFilter(silencer) + logger.addHandler(console) + logger.debug('Console handler plugged!') + + # LEAP custom handler + leap_handler = LeapLogHandler() + leap_handler.setLevel(level) + leap_handler.addFilter(silencer) + logger.addHandler(leap_handler) + logger.debug('Leap handler plugged!') + + # File handler + if logfile is not None: + logger.debug('Setting logfile to %s ', logfile) + fileh = logging.FileHandler(logfile) + fileh.setLevel(logging.DEBUG) + fileh.setFormatter(formatter) + fileh.addFilter(silencer) + logger.addHandler(fileh) + logger.debug('File handler plugged!') + + if replace_stdout: + replace_stdout_stderr_with_logging(logger) + + return logger + + +def replace_stdout_stderr_with_logging(logger): + """ + Replace: + - the standard output + - the standard error + - the twisted log output + with a custom one that writes to the logger. + """ + # Disabling this on windows since it breaks ALL THE THINGS + # The issue for this is #4149 + if not IS_WIN: + sys.stdout = StreamToLogger(logger, logging.DEBUG) + sys.stderr = StreamToLogger(logger, logging.ERROR) + + # Replace twisted's logger to use our custom output. + from twisted.python import log + log.startLogging(sys.stdout) diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py index 2b2cd874..c35be99e 100644 --- a/src/leap/bitmask/util/__init__.py +++ b/src/leap/bitmask/util/__init__.py @@ -28,11 +28,6 @@ from leap.common.config import get_path_prefix as common_get_path_prefix # We'll give your money back if it does not alleviate the eye strain, at least. -# levelname length == 8, since 'CRITICAL' is the longest -LOG_FORMAT = ('%(asctime)s - %(levelname)-8s - ' - 'L#%(lineno)-4s : %(name)s:%(funcName)s() - %(message)s') - - def first(things): """ Return the head of a collection. diff --git a/src/leap/bitmask/util/leap_log_handler.py b/src/leap/bitmask/util/leap_log_handler.py deleted file mode 100644 index 807e53d4..00000000 --- a/src/leap/bitmask/util/leap_log_handler.py +++ /dev/null @@ -1,137 +0,0 @@ -# -*- coding: utf-8 -*- -# leap_log_handler.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 . -""" -Custom handler for the logger window. -""" -import logging - -from PySide import QtCore - -from leap.bitmask.util import LOG_FORMAT - - -class LogHandler(logging.Handler): - """ - This is the custom handler that implements our desired formatting - and also keeps a history of all the logged events. - """ - - MESSAGE_KEY = 'message' - RECORD_KEY = 'record' - - def __init__(self, qtsignal): - """ - LogHander initialization. - Calls parent method and keeps a reference to the qtsignal - that will be used to fire the gui update. - """ - # TODO This is going to eat lots of memory after some time. - # Should be pruned at some moment. - self._log_history = [] - - logging.Handler.__init__(self) - self._qtsignal = qtsignal - - def _get_format(self, logging_level): - """ - Sets the log format depending on the parameter. - It uses html and css to set the colors for the logs. - - :param logging_level: the debug level to define the color. - :type logging_level: str. - """ - formatter = logging.Formatter(LOG_FORMAT) - return formatter - - def emit(self, logRecord): - """ - This method is fired every time that a record is logged by the - logging module. - This method reimplements logging.Handler.emit that is fired - in every logged message. - - :param logRecord: the record emitted by the logging module. - :type logRecord: logging.LogRecord. - """ - self.setFormatter(self._get_format(logRecord.levelname)) - log = self.format(logRecord) - log_item = {self.RECORD_KEY: logRecord, self.MESSAGE_KEY: log} - self._log_history.append(log_item) - self._qtsignal(log_item) - - -class HandlerAdapter(object): - """ - New style class that accesses all attributes from the LogHandler. - - Used as a workaround for a problem with multiple inheritance with Pyside - that surfaced under OSX with pyside 1.1.0. - """ - MESSAGE_KEY = 'message' - RECORD_KEY = 'record' - - def __init__(self, qtsignal): - self._handler = LogHandler(qtsignal=qtsignal) - - def setLevel(self, *args, **kwargs): - return self._handler.setLevel(*args, **kwargs) - - def addFilter(self, *args, **kwargs): - return self._handler.addFilter(*args, **kwargs) - - def handle(self, *args, **kwargs): - return self._handler.handle(*args, **kwargs) - - @property - def level(self): - return self._handler.level - - -class LeapLogHandler(QtCore.QObject, HandlerAdapter): - """ - Custom logging handler. It emits Qt signals so it can be plugged to a gui. - - Its inner handler also stores an history of logs that can be fetched after - having been connected to a gui. - """ - # All dicts returned are of the form - # {'record': LogRecord, 'message': str} - new_log = QtCore.Signal(dict) - - def __init__(self): - """ - LeapLogHandler initialization. - Initializes parent classes. - """ - QtCore.QObject.__init__(self) - HandlerAdapter.__init__(self, qtsignal=self.qtsignal) - - def qtsignal(self, log_item): - # WARNING: the new-style connection does NOT work because PySide - # translates the emit method to self.emit, and that collides with - # the emit method for logging.Handler - # self.new_log.emit(log_item) - QtCore.QObject.emit( - self, - QtCore.SIGNAL('new_log(PyObject)'), log_item) - - @property - def log_history(self): - """ - Returns the history of the logged messages. - """ - return self._handler._log_history diff --git a/src/leap/bitmask/util/log_silencer.py b/src/leap/bitmask/util/log_silencer.py deleted file mode 100644 index 56b290e4..00000000 --- a/src/leap/bitmask/util/log_silencer.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- -# log_silencer.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 . -""" -Filter for leap logs. -""" -import logging -import os -import re - -from leap.bitmask.util import get_path_prefix - - -class SelectiveSilencerFilter(logging.Filter): - """ - Configurable filter for root leap logger. - - If you want to ignore components from the logging, just add them, - one by line, to ~/.config/leap/leap.dev.conf - """ - # TODO we can augment this by properly parsing the log-silencer file - # and having different sections: ignore, levels, ... - - # TODO use ConfigParser to unify sections [log-ignore] [log-debug] etc - - CONFIG_NAME = "leap.dev.conf" - - # Components to be completely silenced in the main bitmask logs. - # You probably should think twice before adding a component to - # the tuple below. Only very well tested components should go here, and - # only in those cases in which we gain more from silencing them than from - # having their logs into the main log file that the user will likely send - # to us. - SILENCER_RULES = ( - 'leap.common.events', - 'leap.common.decorators', - ) - - def __init__(self): - """ - Tries to load silencer rules from the default path, - or load from the SILENCER_RULES tuple if not found. - """ - self.rules = None - if os.path.isfile(self._rules_path): - self.rules = self._load_rules() - if not self.rules: - self.rules = self.SILENCER_RULES - - @property - def _rules_path(self): - """ - The configuration file for custom ignore rules. - """ - return os.path.join(get_path_prefix(), "leap", self.CONFIG_NAME) - - def _load_rules(self): - """ - Loads a list of paths to be ignored from the logging. - """ - lines = open(self._rules_path).readlines() - return map(lambda line: re.sub('\s', '', line), - lines) - - def filter(self, record): - """ - Implements the filter functionality for this Filter - - :param record: the record to be examined - :type record: logging.LogRecord - :returns: a bool indicating whether the record should be logged or not. - :rtype: bool - """ - if not self.rules: - return True - logger_path = record.name - for path in self.rules: - if logger_path.startswith(path): - return False - return True diff --git a/src/leap/bitmask/util/streamtologger.py b/src/leap/bitmask/util/streamtologger.py deleted file mode 100644 index 25a06718..00000000 --- a/src/leap/bitmask/util/streamtologger.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# streamtologger.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 . -""" -Stream object that redirects writes to a logger instance. -""" -import logging - - -class StreamToLogger(object): - """ - Fake file-like stream object that redirects writes to a logger instance. - - Credits to: - http://www.electricmonk.nl/log/2011/08/14/\ - redirect-stdout-and-stderr-to-a-logger-in-python/ - """ - def __init__(self, logger, log_level=logging.INFO): - """ - Constructor, defines the logger and level to use to log messages. - - :param logger: logger object to log messages. - :type logger: logging.Handler - :param log_level: the level to use to log messages through the logger. - :type log_level: int - look at logging-levels in 'logging' docs. - """ - self._logger = logger - self._log_level = log_level - - def write(self, data): - """ - Simulates the 'write' method in a file object. - It writes the data receibed in buf to the logger 'self._logger'. - - :param data: data to write to the 'file' - :type data: str - """ - for line in data.rstrip().splitlines(): - self._logger.log(self._log_level, line.rstrip()) - - def flush(self): - """ - Dummy method. Needed to replace the twisted.log output. - """ - pass diff --git a/src/leap/bitmask/util/tests/test_leap_log_handler.py b/src/leap/bitmask/util/tests/test_leap_log_handler.py deleted file mode 100644 index 518fd35b..00000000 --- a/src/leap/bitmask/util/tests/test_leap_log_handler.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -# test_leap_log_handler.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 . -""" -tests for leap_log_handler -""" -try: - import unittest2 as unittest -except ImportError: - import unittest - -import logging - -from leap.bitmask.util.leap_log_handler import LeapLogHandler -from leap.bitmask.util.pyside_tests_helper import BasicPySlotCase -from leap.common.testing.basetest import BaseLeapTest - -from mock import Mock - - -class LeapLogHandlerTest(BaseLeapTest, BasicPySlotCase): - """ - LeapLogHandlerTest's tests. - """ - def _callback(self, *args): - """ - Simple callback to track if a signal was emitted. - """ - self.called = True - self.emitted_msg = args[0][LeapLogHandler.MESSAGE_KEY] - - def setUp(self): - BasicPySlotCase.setUp(self) - - # Create the logger - level = logging.DEBUG - self.logger = logging.getLogger(name='test') - self.logger.setLevel(level) - - # Create the handler - self.leap_handler = LeapLogHandler() - self.leap_handler.setLevel(level) - self.logger.addHandler(self.leap_handler) - - def tearDown(self): - BasicPySlotCase.tearDown(self) - try: - self.leap_handler.new_log.disconnect() - except Exception: - pass - - def test_history_starts_empty(self): - self.assertEqual(self.leap_handler.log_history, []) - - def test_one_log_captured(self): - self.logger.debug('test') - self.assertEqual(len(self.leap_handler.log_history), 1) - - def test_history_records_order(self): - self.logger.debug('test 01') - self.logger.debug('test 02') - self.logger.debug('test 03') - - logs = [] - for message in self.leap_handler.log_history: - logs.append(message[LeapLogHandler.RECORD_KEY].msg) - - self.assertIn('test 01', logs) - self.assertIn('test 02', logs) - self.assertIn('test 03', logs) - - def test_history_messages_order(self): - self.logger.debug('test 01') - self.logger.debug('test 02') - self.logger.debug('test 03') - - logs = [] - for message in self.leap_handler.log_history: - logs.append(message[LeapLogHandler.MESSAGE_KEY]) - - self.assertIn('test 01', logs[0]) - self.assertIn('test 02', logs[1]) - self.assertIn('test 03', logs[2]) - - def test_emits_signal(self): - log_format = '%(name)s - %(levelname)s - %(message)s' - formatter = logging.Formatter(log_format) - get_format = Mock(return_value=formatter) - self.leap_handler._handler._get_format = get_format - - self.leap_handler.new_log.connect(self._callback) - self.logger.debug('test') - - expected_log_msg = "test - DEBUG - test" - - # signal emitted - self.assertTrue(self.called) - - # emitted message - self.assertEqual(self.emitted_msg, expected_log_msg) - - # Mock called - self.assertTrue(get_format.called) - - -if __name__ == "__main__": - unittest.main() diff --git a/src/leap/bitmask/util/tests/test_streamtologger.py b/src/leap/bitmask/util/tests/test_streamtologger.py deleted file mode 100644 index fc97b794..00000000 --- a/src/leap/bitmask/util/tests/test_streamtologger.py +++ /dev/null @@ -1,126 +0,0 @@ -# -*- coding: utf-8 -*- -# test_streamtologger.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 . -""" -tests for streamtologger -""" - -try: - import unittest2 as unittest -except ImportError: - import unittest - -import logging -import sys - -from leap.bitmask.util.streamtologger import StreamToLogger -from leap.common.testing.basetest import BaseLeapTest - - -class SimpleLogHandler(logging.Handler): - """ - The simplest log handler that allows to check if the log was - delivered to the handler correctly. - """ - def __init__(self): - logging.Handler.__init__(self) - self._last_log = "" - self._last_log_level = "" - - def emit(self, record): - self._last_log = record.getMessage() - self._last_log_level = record.levelno - - def get_last_log(self): - """ - Returns the last logged message by this handler. - - :return: the last logged message. - :rtype: str - """ - return self._last_log - - def get_last_log_level(self): - """ - Returns the level of the last logged message by this handler. - - :return: the last logged level. - :rtype: str - """ - return self._last_log_level - - -class StreamToLoggerTest(BaseLeapTest): - """ - StreamToLogger's tests. - - NOTE: we may need to find a way to test the use case that an exception - is raised. I couldn't catch the output of an exception because the - test failed if some exception is raised. - """ - def setUp(self): - # Create the logger - level = logging.DEBUG - self.logger = logging.getLogger(name='test') - self.logger.setLevel(level) - - # Simple log handler - self.handler = SimpleLogHandler() - self.logger.addHandler(self.handler) - - # Preserve original values - self._sys_stdout = sys.stdout - self._sys_stderr = sys.stderr - - # Create the handler - sys.stdout = StreamToLogger(self.logger, logging.DEBUG) - sys.stderr = StreamToLogger(self.logger, logging.ERROR) - - def tearDown(self): - # Restore original values - sys.stdout = self._sys_stdout - sys.stderr = self._sys_stderr - - def test_logger_starts_empty(self): - self.assertEqual(self.handler.get_last_log(), '') - - def test_standard_output(self): - message = 'Test string' - print message - - log = self.handler.get_last_log() - log_level = self.handler.get_last_log_level() - - self.assertEqual(log, message) - self.assertEqual(log_level, logging.DEBUG) - - def test_standard_error(self): - message = 'Test string' - sys.stderr.write(message) - - log_level = self.handler.get_last_log_level() - log = self.handler.get_last_log() - - self.assertEqual(log, message) - self.assertEqual(log_level, logging.ERROR) - - def test_twisted_log(self): - from twisted.python import log - log.startLogging(sys.stdout) - - -if __name__ == "__main__": - unittest.main(verbosity=2) -- cgit v1.2.3 From aed6d24b68615bd4b42fd750df97d32bd643ab83 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 5 Jun 2014 12:10:18 -0500 Subject: check if resolvconf is missing in system --- changes/feature_init-check-resolvconf | 1 + src/leap/bitmask/platform_init/initializers.py | 49 ++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 changes/feature_init-check-resolvconf diff --git a/changes/feature_init-check-resolvconf b/changes/feature_init-check-resolvconf new file mode 100644 index 00000000..81733910 --- /dev/null +++ b/changes/feature_init-check-resolvconf @@ -0,0 +1 @@ +- Warn user if resolvconf cannot be found. diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index b163e1bd..b282a229 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -21,6 +21,7 @@ import logging import os import platform import stat +import sys import subprocess import tempfile @@ -342,6 +343,46 @@ def DarwinInitializer(): # # Linux initializers # + +def _get_missing_resolvconf_dialog(): + """ + Create a dialog for notifying about missing openresolv. + + :rtype: QtGui.QMessageBox instance + """ + NO_RESOLVCONF = ( + "Could not find resolvconf installed in your system.\n" + "Do you want to quit Bitmask now?") + + EXPLAIN = ( + "Encrypted Internet needs resolvconf installed to work properly.\n" + "Please use your package manager to install it.\n") + + msg = QtGui.QMessageBox() + msg.setWindowTitle(msg.tr("Missing resolvconf framework")) + msg.setText(msg.tr(NO_RESOLVCONF)) + # but maybe the user really deserve to know more + msg.setInformativeText(msg.tr(EXPLAIN)) + msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) + msg.setDefaultButton(QtGui.QMessageBox.Yes) + return msg + + +def _linux_check_resolvconf(): + """ + Raise a dialog warning about the lack of the resolvconf framework. + """ + RESOLVCONF_PATH = "/sbin/resolvconf" + missing = not os.path.isfile(RESOLVCONF_PATH) + + if missing: + msg = _get_missing_resolvconf_dialog() + ret = msg.exec_() + + if ret == QtGui.QMessageBox.Yes: + sys.exit() + + def _linux_install_missing_scripts(badexec, notfound): """ Try to install the missing up/down scripts. @@ -395,7 +436,11 @@ def _linux_install_missing_scripts(badexec, notfound): def LinuxInitializer(): """ - Raise a dialog in case that either updown scripts or policykit file - are missing or they have incorrect permissions. + Raise a dialog if needed files are missing. + + Missing files can be either system-wide resolvconf, bitmask-root, or + policykit file. The dialog will also be raised if some of those files are + found to have incorrect permissions. """ + _linux_check_resolvconf() check_missing() -- cgit v1.2.3 From bc60662ccc740d191cf8a1d368e66459dd1c4ae5 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 5 Jun 2014 15:38:09 -0500 Subject: change elements naming --- src/leap/bitmask/gui/eip_status.py | 21 ++++++++++++--------- src/leap/bitmask/gui/ui/eip_status.ui | 2 +- src/leap/bitmask/services/eip/conductor.py | 5 ++--- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 280ce79e..dcd7ef42 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -249,7 +249,7 @@ class EIPStatusWidget(QtGui.QWidget): def set_action_eip_startstop(self, action_eip_startstop): """ - Sets the action_eip_startstop to use. + Set the action_eip_startstop to use. :param action_eip_startstop: action_eip_status to be used :type action_eip_startstop: QtGui.QAction @@ -392,6 +392,9 @@ class EIPStatusWidget(QtGui.QWidget): """ Enable firewall-down button. """ + retry_msg = self.tr("Retry") + self.ui.btnEipStartStop.setText(retry_msg) + self._action_eip_startstop.setText(retry_msg) self.ui.btnFwDown.show() def _on_fw_down_button_clicked(self): @@ -404,6 +407,7 @@ class EIPStatusWidget(QtGui.QWidget): # XXX do actual check msg = "Traffic is being routed in the clear." + self.ui.btnEipStartStop.setText(self.tr("Turn ON")) self.set_eip_message(msg) self.set_eip_status("") @@ -419,15 +423,13 @@ class EIPStatusWidget(QtGui.QWidget): self._reset_traffic_rates() self.ui.eip_bandwidth.hide() - # XXX FIXME ! ----------------------- this needs to - # accomodate the messages about firewall status. Right now - # we're assuming it works correctly, but we should test fw + # This is assuming the firewall works correctly, but we should test fw # status positively. # Or better call it from the conductor... clear_traffic = self.tr("Traffic is being routed in the clear.") unreachable_net = self.tr("Network is unreachable.") - failed_msg = self.tr("Cannot start Encrypted Internet") + failed_msg = self.tr("Error connecting") if restart: msg = unreachable_net @@ -439,14 +441,15 @@ class EIPStatusWidget(QtGui.QWidget): self.ui.lblEIPStatus.show() self.show() - def eip_failed_to_restart(self): + def eip_failed_to_connect(self): """ - Update EIP messages. + Update EIP messages with error during (re)connection. """ - msg = self.tr("Could not restart Encrypted Internet") + msg = self.tr("Error connecting.") self.ui.lblEIPMessage.setText(msg) self.ui.lblEIPStatus.show() - self.set_eip_status(self.tr("You can launch the service manually.")) + self.set_eip_status(self.tr("Bitmask is blocking " + "unencrypted traffic.")) self.show_fw_down_button() @QtCore.Slot(dict) diff --git a/src/leap/bitmask/gui/ui/eip_status.ui b/src/leap/bitmask/gui/ui/eip_status.ui index 01d6b371..892f0f00 100644 --- a/src/leap/bitmask/gui/ui/eip_status.ui +++ b/src/leap/bitmask/gui/ui/eip_status.ui @@ -242,7 +242,7 @@ - Allow unencrypted traffic + Turn Off diff --git a/src/leap/bitmask/services/eip/conductor.py b/src/leap/bitmask/services/eip/conductor.py index cde53631..a8821160 100644 --- a/src/leap/bitmask/services/eip/conductor.py +++ b/src/leap/bitmask/services/eip/conductor.py @@ -258,7 +258,7 @@ class EIPConductor(object): """ logger.debug("TLS Error: eip_stop (failed)") self.qtsigs.connection_died_signal.emit() - QtDelayedCall(1000, self._eip_status.eip_failed_to_restart) + QtDelayedCall(1000, self._eip_status.eip_failed_to_connect) @QtCore.Slot(int) def _eip_finished(self, exitCode): @@ -311,8 +311,7 @@ class EIPConductor(object): error=True) signal = self.qtsigs.connection_died_signal self._eip_status.show_fw_down_button() - msg = self._eip_status.tr("Outgoing traffic is blocked") - self._eip_status.set_eip_message(msg) + self._eip_status.eip_failed_to_connect() if exitCode == 0 and IS_MAC: # XXX remove this warning after I fix cocoasudo. -- cgit v1.2.3 From 57aa4d51e37228333e56e98d9af3bfe7b278e2d1 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 5 Jun 2014 16:38:56 -0500 Subject: add icons for the vpn exit nodes --- Makefile | 2 +- changes/feature-country-code-flag | 1 + data/images/countries/nl.png | Bin 0 -> 453 bytes data/images/countries/tr.png | Bin 0 -> 492 bytes data/images/countries/us.png | Bin 0 -> 609 bytes data/resources/eipstatus.qrc | 7 +++++++ src/leap/bitmask/config/flags.py | 2 ++ src/leap/bitmask/gui/eip_status.py | 24 +++++++++++++++++++++++ src/leap/bitmask/gui/mainwindow.py | 1 + src/leap/bitmask/gui/ui/eip_status.ui | 21 +++++++++++++------- src/leap/bitmask/services/eip/eipconfig.py | 28 +++++++++++++++++++++++---- src/leap/bitmask/services/eip/vpnlauncher.py | 9 ++++++++- 12 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 changes/feature-country-code-flag create mode 100644 data/images/countries/nl.png create mode 100644 data/images/countries/tr.png create mode 100644 data/images/countries/us.png create mode 100644 data/resources/eipstatus.qrc diff --git a/Makefile b/Makefile index 358af126..73437449 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ PROJFILE = data/bitmask.pro #UI files to compile UI_FILES = loggerwindow.ui mainwindow.ui wizard.ui login.ui preferences.ui eip_status.ui mail_status.ui eippreferences.ui advanced_key_management.ui #Qt resource files to compile -RESOURCES = locale.qrc loggerwindow.qrc mainwindow.qrc icons.qrc +RESOURCES = locale.qrc loggerwindow.qrc mainwindow.qrc icons.qrc eipstatus.qrc #pyuic4 and pyrcc4 binaries PYUIC = pyside-uic diff --git a/changes/feature-country-code-flag b/changes/feature-country-code-flag new file mode 100644 index 00000000..b7b53626 --- /dev/null +++ b/changes/feature-country-code-flag @@ -0,0 +1 @@ +- Show flag of country for eip exit node, if available. Related: #1232 diff --git a/data/images/countries/nl.png b/data/images/countries/nl.png new file mode 100644 index 00000000..fe44791e Binary files /dev/null and b/data/images/countries/nl.png differ diff --git a/data/images/countries/tr.png b/data/images/countries/tr.png new file mode 100644 index 00000000..be32f77e Binary files /dev/null and b/data/images/countries/tr.png differ diff --git a/data/images/countries/us.png b/data/images/countries/us.png new file mode 100644 index 00000000..10f451fe Binary files /dev/null and b/data/images/countries/us.png differ diff --git a/data/resources/eipstatus.qrc b/data/resources/eipstatus.qrc new file mode 100644 index 00000000..5d0f2924 --- /dev/null +++ b/data/resources/eipstatus.qrc @@ -0,0 +1,7 @@ + + + ../images/countries/nl.png + ../images/countries/tr.png + ../images/countries/us.png + + diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py index 6b70659d..2f3fdde4 100644 --- a/src/leap/bitmask/config/flags.py +++ b/src/leap/bitmask/config/flags.py @@ -55,3 +55,5 @@ OPENVPN_VERBOSITY = 1 # Skip the checks in the wizard, use for testing purposes only! SKIP_WIZARD_CHECKS = False + +CURRENT_VPN_COUNTRY = None diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 280ce79e..69ef2616 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -24,6 +24,7 @@ from functools import partial from PySide import QtCore, QtGui +from leap.bitmask.config import flags 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 @@ -416,6 +417,7 @@ class EIPStatusWidget(QtGui.QWidget): Sets the state of the widget to how it should look after EIP has stopped """ + self.set_country_code("") self._reset_traffic_rates() self.ui.eip_bandwidth.hide() @@ -582,6 +584,25 @@ class EIPStatusWidget(QtGui.QWidget): self.tr("Routing traffic through: {0}").format( provider)) + ccode = flags.CURRENT_VPN_COUNTRY + if ccode is not None: + self.set_country_code(ccode) + + def set_country_code(self, code): + """ + Set the pixmap of the given country code + + :param code: the country code + :type code: str + """ + if code is not None and len(code) == 2: + img = ":/images/countries/%s.png" % (code.lower(),) + else: + img = None + cc = self.ui.lblGatewayCountryCode + cc.setPixmap(QtGui.QPixmap(img)) + cc.setToolTip(code) + def aborted(self): """ Notify the state machine that EIP was aborted for some reason. @@ -704,3 +725,6 @@ class EIPStatusWidget(QtGui.QWidget): """ self.set_eip_status("", error=error) self.set_eip_status_icon("error") + +import eipstatus_rc +assert(eipstatus_rc) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index a3b81fde..c22c8892 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1465,6 +1465,7 @@ class MainWindow(QtGui.QMainWindow): self._already_started_eip = True # check for connectivity + # we might want to leave a little time here... self._check_name_resolution(domain) def _check_name_resolution(self, domain): diff --git a/src/leap/bitmask/gui/ui/eip_status.ui b/src/leap/bitmask/gui/ui/eip_status.ui index 01d6b371..ee24ca71 100644 --- a/src/leap/bitmask/gui/ui/eip_status.ui +++ b/src/leap/bitmask/gui/ui/eip_status.ui @@ -28,7 +28,7 @@ 0 - + Turn On @@ -51,7 +51,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -86,7 +86,7 @@ - + @@ -105,7 +105,7 @@ - + Qt::Horizontal @@ -118,7 +118,7 @@ - + @@ -239,13 +239,20 @@ - + Allow unencrypted traffic + + + + + + + diff --git a/src/leap/bitmask/services/eip/eipconfig.py b/src/leap/bitmask/services/eip/eipconfig.py index 09a3d257..e7419b22 100644 --- a/src/leap/bitmask/services/eip/eipconfig.py +++ b/src/leap/bitmask/services/eip/eipconfig.py @@ -110,7 +110,7 @@ class VPNGatewaySelector(object): def get_gateways_list(self): """ - Returns the existing gateways, sorted by timezone proximity. + Return the existing gateways, sorted by timezone proximity. :rtype: list of tuples (location, ip) (str, IPv4Address or IPv6Address object) @@ -148,16 +148,36 @@ class VPNGatewaySelector(object): def get_gateways(self): """ - Returns the 4 best gateways, sorted by timezone proximity. + Return the 4 best gateways, sorted by timezone proximity. :rtype: list of IPv4Address or IPv6Address object. """ gateways = [ip for location, ip in self.get_gateways_list()][:4] return gateways + def get_gateways_country_code(self): + """ + Return a dict with ipaddress -> country code mapping. + + :rtype: dict + """ + country_codes = {} + + locations = self._eipconfig.get_locations() + gateways = self._eipconfig.get_gateways() + + for idx, gateway in enumerate(gateways): + gateway_location = gateway.get('location') + + ip = self._eipconfig.get_gateway_ip(idx) + if gateway_location is not None: + ccode = locations[gateway['location']]['country_code'] + country_codes[ip] = ccode + return country_codes + def _get_timezone_distance(self, offset): ''' - Returns the distance between the local timezone and + Return the distance between the local timezone and the one with offset 'offset'. :param offset: the distance of a timezone to GMT. @@ -179,7 +199,7 @@ class VPNGatewaySelector(object): def _get_local_offset(self): ''' - Returns the distance between GMT and the local timezone. + Return the distance between GMT and the local timezone. :rtype: int ''' diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index dcb48e8a..9629afae 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -25,6 +25,7 @@ import stat from abc import ABCMeta, abstractmethod from functools import partial +from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.platform_init import IS_LINUX @@ -122,9 +123,9 @@ class VPNLauncher(object): leap_settings = LeapSettings() domain = providerconfig.get_domain() gateway_conf = leap_settings.get_selected_gateway(domain) + gateway_selector = VPNGatewaySelector(eipconfig) if gateway_conf == leap_settings.GATEWAY_AUTOMATIC: - gateway_selector = VPNGatewaySelector(eipconfig) gateways = gateway_selector.get_gateways() else: gateways = [gateway_conf] @@ -133,6 +134,12 @@ class VPNLauncher(object): logger.error('No gateway was found!') raise VPNLauncherException('No gateway was found!') + # this only works for selecting the first gateway, as we're + # currently doing. + ccodes = gateway_selector.get_gateways_country_code() + gateway_ccode = ccodes[gateways[0]] + flags.CURRENT_VPN_COUNTRY = gateway_ccode + logger.debug("Using gateways ips: {0}".format(', '.join(gateways))) return gateways -- cgit v1.2.3 From 6ab80f96b1ed14ccf96cae37ff207649a26a38ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 6 Jun 2014 14:39:00 -0300 Subject: Fold in changes and update relnotes --- CHANGELOG.rst | 34 +++++++++++++++++++++++++++ changes/allow-local-multicast-in-firewall | 2 -- changes/bug-4144_support-gnome-shell-polkit | 1 - changes/bug-5540_change-password-fix | 1 - changes/bug-5695_fix-username-regex-support | 3 --- changes/bug-5721_eip-autostart-is-broken | 1 - changes/bug-block-ipv6-clientside | 1 - changes/bug_5687_switch-eip-off | 1 - changes/bug_add_openvpn_bin_path_osx | 1 - changes/bug_allow-usernames-ending-with-digit | 1 - changes/bug_improve_signal_handling | 1 - changes/bug_openvpn_not_found | 1 - changes/bug_set_userid | 1 - changes/bug_update_ui_when_fails_at_config | 1 - changes/feature-5689-psutil-compat | 1 - changes/feature-bitmask-root-versioning | 1 - changes/feature-country-code-flag | 1 - changes/feature_5692-fix-nameserver-restoring | 1 - changes/feature_init-check-resolvconf | 1 - changes/refactor-and-cleanup | 3 --- changes/refactor-mail-soledad | 5 ---- relnotes.txt | 18 ++++++-------- 22 files changed, 41 insertions(+), 40 deletions(-) delete mode 100644 changes/allow-local-multicast-in-firewall delete mode 100644 changes/bug-4144_support-gnome-shell-polkit delete mode 100644 changes/bug-5540_change-password-fix delete mode 100644 changes/bug-5695_fix-username-regex-support delete mode 100644 changes/bug-5721_eip-autostart-is-broken delete mode 100644 changes/bug-block-ipv6-clientside delete mode 100644 changes/bug_5687_switch-eip-off delete mode 100644 changes/bug_add_openvpn_bin_path_osx delete mode 100644 changes/bug_allow-usernames-ending-with-digit delete mode 100644 changes/bug_improve_signal_handling delete mode 100644 changes/bug_openvpn_not_found delete mode 100644 changes/bug_set_userid delete mode 100644 changes/bug_update_ui_when_fails_at_config delete mode 100644 changes/feature-5689-psutil-compat delete mode 100644 changes/feature-bitmask-root-versioning delete mode 100644 changes/feature-country-code-flag delete mode 100644 changes/feature_5692-fix-nameserver-restoring delete mode 100644 changes/feature_init-check-resolvconf delete mode 100644 changes/refactor-and-cleanup delete mode 100644 changes/refactor-mail-soledad diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b429595b..4faceb98 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,40 @@ History 2014 ==== +0.5.2 June 6 -- the "are we there yet" release: ++++++++++++++++++++++++++++++++++++++++++++++++ + +- Unblock local multicast IPs from linux firewall, to allow SSDP and + Bonjour/mDNS to work. +- Add support for gnome-shell polkit agent. Closes #4144, #4218. +- Update username regex to support the same as webapp. Closes #5965. +- Wrong error message for username too short. Fixes #5697. +- Cleanup and refactor username/password validators. +- Fix EIP autostart failing. Closes #5721. +- Block ipv6 traffic for the moment. Closes #5693 +- Fix bug with ipv6 blocking that caused block to not get removed from + firewall when Bitmask quit. +- Bring firewall down when switching EIP off. Closes #5687 +- Add OPENVPN_BIN_PATH for OSX so that EIP starts properly. +- Allow usernames to end in a digit. +- Improve signal handling in the mainwindow and wizard. +- Enable UI when OpenVPN bin is not found, plus check before starting + EIP. Fixes #5619. +- Properly set the userid for SMTP. +- Update EIP UI if it fails to download the config. +- Make use of cmdline in psutil backwards-compatible. Closes #5689 +- Add versioning support to bitmask-root. +- Show flag of country for eip exit node, if available. Related #1232 +- Fix nameserver restoring. Closes #5692 +- Warn user if resolvconf cannot be found. +- Refactor Keymanager to backend. Closes #5711. +- Cleanup backend from hacks. Closes #5698. +- Improve wait and quit process. +- Move soledad password change to backend. +- Move Mail logic to backend. +- Separate imap/smtp logic from conductor. +- Refactor SoledadBootstrapper to backend. Closes #5481. + 0.5.1 May 16 -- the "lil less leaky" release: +++++++++++++++++++++++++++++++++++++++++++++ diff --git a/changes/allow-local-multicast-in-firewall b/changes/allow-local-multicast-in-firewall deleted file mode 100644 index 4c17b92e..00000000 --- a/changes/allow-local-multicast-in-firewall +++ /dev/null @@ -1,2 +0,0 @@ -- unblock local multicast IPs from linux firewall, to allow SSDP and Bonjour/mDNS to work. -- fix bug with ipv6 blocking that caused block to not get removed from firewall when Bitmask quit. \ No newline at end of file diff --git a/changes/bug-4144_support-gnome-shell-polkit b/changes/bug-4144_support-gnome-shell-polkit deleted file mode 100644 index b8da093c..00000000 --- a/changes/bug-4144_support-gnome-shell-polkit +++ /dev/null @@ -1 +0,0 @@ -- Add support for gnome-shell polkit agent. Closes #4144, #4218. diff --git a/changes/bug-5540_change-password-fix b/changes/bug-5540_change-password-fix deleted file mode 100644 index 9c9c09d5..00000000 --- a/changes/bug-5540_change-password-fix +++ /dev/null @@ -1 +0,0 @@ -Change password doesn't work. Closes #5540. diff --git a/changes/bug-5695_fix-username-regex-support b/changes/bug-5695_fix-username-regex-support deleted file mode 100644 index 855ec149..00000000 --- a/changes/bug-5695_fix-username-regex-support +++ /dev/null @@ -1,3 +0,0 @@ -- Update username regex to support the same as webapp. Closes #5965. -- Wrong error message for username too short. (Bug #5697) -- Cleanup and refactor username/password validators. diff --git a/changes/bug-5721_eip-autostart-is-broken b/changes/bug-5721_eip-autostart-is-broken deleted file mode 100644 index ab1ea512..00000000 --- a/changes/bug-5721_eip-autostart-is-broken +++ /dev/null @@ -1 +0,0 @@ -- Fix EIP autostart failing. Closes #5721. diff --git a/changes/bug-block-ipv6-clientside b/changes/bug-block-ipv6-clientside deleted file mode 100644 index 9e6b88c4..00000000 --- a/changes/bug-block-ipv6-clientside +++ /dev/null @@ -1 +0,0 @@ -- Block ipv6 traffic for the moment. Closes: #5693 diff --git a/changes/bug_5687_switch-eip-off b/changes/bug_5687_switch-eip-off deleted file mode 100644 index 4cf0d24a..00000000 --- a/changes/bug_5687_switch-eip-off +++ /dev/null @@ -1 +0,0 @@ -- Bring firewall down when switching EIP off. Closes: #5687 diff --git a/changes/bug_add_openvpn_bin_path_osx b/changes/bug_add_openvpn_bin_path_osx deleted file mode 100644 index 367b5b8e..00000000 --- a/changes/bug_add_openvpn_bin_path_osx +++ /dev/null @@ -1 +0,0 @@ -- Add OPENVPN_BIN_PATH for OSX so that EIP starts properly. \ No newline at end of file diff --git a/changes/bug_allow-usernames-ending-with-digit b/changes/bug_allow-usernames-ending-with-digit deleted file mode 100644 index de47e7ac..00000000 --- a/changes/bug_allow-usernames-ending-with-digit +++ /dev/null @@ -1 +0,0 @@ -- Allow usernames to end in a digit. diff --git a/changes/bug_improve_signal_handling b/changes/bug_improve_signal_handling deleted file mode 100644 index 4f88747f..00000000 --- a/changes/bug_improve_signal_handling +++ /dev/null @@ -1 +0,0 @@ -- Improve signal handling in the mainwindow and wizard. \ No newline at end of file diff --git a/changes/bug_openvpn_not_found b/changes/bug_openvpn_not_found deleted file mode 100644 index c2363122..00000000 --- a/changes/bug_openvpn_not_found +++ /dev/null @@ -1 +0,0 @@ -- Enable UI when OpenVPN bin is not found, plus check before starting EIP. Fixes #5619. \ No newline at end of file diff --git a/changes/bug_set_userid b/changes/bug_set_userid deleted file mode 100644 index 050a8808..00000000 --- a/changes/bug_set_userid +++ /dev/null @@ -1 +0,0 @@ -- Properly set the userid for SMTP. \ No newline at end of file diff --git a/changes/bug_update_ui_when_fails_at_config b/changes/bug_update_ui_when_fails_at_config deleted file mode 100644 index 84f19401..00000000 --- a/changes/bug_update_ui_when_fails_at_config +++ /dev/null @@ -1 +0,0 @@ -- Update EIP UI if it fails to download the config. \ No newline at end of file diff --git a/changes/feature-5689-psutil-compat b/changes/feature-5689-psutil-compat deleted file mode 100644 index be11aea3..00000000 --- a/changes/feature-5689-psutil-compat +++ /dev/null @@ -1 +0,0 @@ -- Make use of cmdline in psutil backwards-compatible. Closes: #5689 diff --git a/changes/feature-bitmask-root-versioning b/changes/feature-bitmask-root-versioning deleted file mode 100644 index bfe69041..00000000 --- a/changes/feature-bitmask-root-versioning +++ /dev/null @@ -1 +0,0 @@ -- Add versioning support to bitmask-root. diff --git a/changes/feature-country-code-flag b/changes/feature-country-code-flag deleted file mode 100644 index b7b53626..00000000 --- a/changes/feature-country-code-flag +++ /dev/null @@ -1 +0,0 @@ -- Show flag of country for eip exit node, if available. Related: #1232 diff --git a/changes/feature_5692-fix-nameserver-restoring b/changes/feature_5692-fix-nameserver-restoring deleted file mode 100644 index 10ce3427..00000000 --- a/changes/feature_5692-fix-nameserver-restoring +++ /dev/null @@ -1 +0,0 @@ -- Fix nameserver restoring. Closes: #5692 diff --git a/changes/feature_init-check-resolvconf b/changes/feature_init-check-resolvconf deleted file mode 100644 index 81733910..00000000 --- a/changes/feature_init-check-resolvconf +++ /dev/null @@ -1 +0,0 @@ -- Warn user if resolvconf cannot be found. diff --git a/changes/refactor-and-cleanup b/changes/refactor-and-cleanup deleted file mode 100644 index 18331ed4..00000000 --- a/changes/refactor-and-cleanup +++ /dev/null @@ -1,3 +0,0 @@ -- Refactor Keymanager to backend. Closes #5711. -- Cleanup backend from hacks. Closes #5698. - diff --git a/changes/refactor-mail-soledad b/changes/refactor-mail-soledad deleted file mode 100644 index 32b1ab5b..00000000 --- a/changes/refactor-mail-soledad +++ /dev/null @@ -1,5 +0,0 @@ -- Improve wait and quit process. -- Move soledad password change to backend. -- Move Mail logic to backend. -- Separate imap/smtp logic from conductor. -- Refactor SoledadBootstrapper to backend. Closes #5481. diff --git a/relnotes.txt b/relnotes.txt index 2704d6f0..e95e8c15 100644 --- a/relnotes.txt +++ b/relnotes.txt @@ -1,8 +1,8 @@ -ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.5.1 +ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.5.2 The LEAP team is pleased to announce the immediate availability of -version 0.5.1 of Bitmask, the Internet Encryption Toolkit, codename -"lil less leaky". +version 0.5.2 of Bitmask, the Internet Encryption Toolkit, codename +"are we there yet". https://downloads.leap.se/client/ @@ -43,13 +43,9 @@ NOT trust your life to it. WHAT CAN THIS VERSION OF BITMASK DO FOR ME? -Bitmask 0.5.1 improves greatly its mail support and stability in -general, among other various bug fixes. You can refer to the CHANGELOG -for the meat. - -As always, you can connect to the Encrypted Internet Proxy service -offered by a provider of your choice, and enjoy a encrypted internet -connection that the spying eyes can only track back to your provider. +Bitmask 0.5.2 improves greatly its Encrypted internet support and +stability in general, among other various bug fixes. You can refer to +the CHANGELOG for the meat. Encrypted Internet on Linux now helps you don't shoot yourself in the foot by leaking traffic outside of the secure connection it @@ -108,6 +104,6 @@ beyond any border. The LEAP team, -May 16, 2014 +June 6, 2014 Somewhere in the middle of the intertubes. EOF -- cgit v1.2.3