From c3306592fbb54fab0da44a8faaa8a1c6954537fd Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 18 Jan 2018 15:37:18 +0100 Subject: [feat] report missing polkit properly from main UI also refactor and move polkit_agent so that it does not depend on having bitmask on the path. --- src/leap/bitmask/vpn/_checks.py | 11 ++- src/leap/bitmask/vpn/autostart.py | 5 +- src/leap/bitmask/vpn/helpers/__init__.py | 17 ++++- src/leap/bitmask/vpn/helpers/linux/polkit_agent.py | 88 ---------------------- src/leap/bitmask/vpn/launchers/linux.py | 2 +- src/leap/bitmask/vpn/polkit.py | 75 ++++++++++++++++++ src/leap/bitmask/vpn/privilege.py | 49 +++--------- src/leap/bitmask/vpn/service.py | 24 ++++-- ui/README.md | 2 +- ui/app/components/main_panel/vpn_section.js | 13 ++++ ui/package.json | 2 +- ui/pydist/setup.py | 2 +- 12 files changed, 146 insertions(+), 144 deletions(-) delete mode 100644 src/leap/bitmask/vpn/helpers/linux/polkit_agent.py create mode 100644 src/leap/bitmask/vpn/polkit.py diff --git a/src/leap/bitmask/vpn/_checks.py b/src/leap/bitmask/vpn/_checks.py index 9586d096..c9e40f57 100644 --- a/src/leap/bitmask/vpn/_checks.py +++ b/src/leap/bitmask/vpn/_checks.py @@ -18,10 +18,10 @@ class ImproperlyConfigured(Exception): def get_failure_for(provider): - if not _has_valid_cert(provider): - raise ImproperlyConfigured('Missing VPN certificate') if IS_LINUX and not is_pkexec_in_system(): raise NoPkexecAvailable() + if not _has_valid_cert(provider): + raise ImproperlyConfigured('Missing VPN certificate') def is_service_ready(provider): @@ -35,8 +35,11 @@ def is_service_ready(provider): def cert_expires(provider): path = get_vpn_cert_path(provider) - with open(path, 'r') as f: - cert = f.read() + try: + with open(path, 'r') as f: + cert = f.read() + except IOError: + return None _, to = get_cert_time_boundaries(cert) expiry_date = datetime.fromtimestamp(mktime(to)) return expiry_date diff --git a/src/leap/bitmask/vpn/autostart.py b/src/leap/bitmask/vpn/autostart.py index 43abfdf5..af7b3669 100644 --- a/src/leap/bitmask/vpn/autostart.py +++ b/src/leap/bitmask/vpn/autostart.py @@ -34,7 +34,10 @@ Terminal=false with open(autostart_file, 'w') as f: f.write(AUTOSTART) elif status == 'off': - os.unlink(autostart_file) + try: + os.unlink(autostart_file) + except OSError: + pass if IS_MAC: diff --git a/src/leap/bitmask/vpn/helpers/__init__.py b/src/leap/bitmask/vpn/helpers/__init__.py index 8f8c1227..69b34e00 100644 --- a/src/leap/bitmask/vpn/helpers/__init__.py +++ b/src/leap/bitmask/vpn/helpers/__init__.py @@ -16,6 +16,7 @@ if IS_LINUX: from leap.bitmask.vpn.constants import OPENVPN_SYSTEM, OPENVPN_LOCAL from leap.bitmask.vpn.constants import POLKIT_SYSTEM, POLKIT_LOCAL from leap.bitmask.vpn.privilege import is_pkexec_in_system + from leap.bitmask.vpn.privilege import LinuxPolicyChecker def install(): helper_from = _config.get_bitmask_helper_path() @@ -40,6 +41,17 @@ if IS_LINUX: remove(POLKIT_LOCAL) remove(OPENVPN_LOCAL) + def privcheck(timeout=5): + has_pkexec = is_pkexec_in_system() + running = LinuxPolicyChecker.is_up() + if not running: + try: + LinuxPolicyChecker.get_usable_pkexec(timeout=timeout) + running = LinuxPolicyChecker.is_up() + except Exception: + running = False + return has_pkexec and running + def check(): helper = _is_up_to_date(_config.get_bitmask_helper_path(), BITMASK_ROOT_LOCAL, @@ -51,7 +63,7 @@ if IS_LINUX: _is_up_to_date(_config.get_bitmask_openvpn_path(), OPENVPN_LOCAL, "")) - return is_pkexec_in_system() and helper and polkit and openvpn + return helper and polkit and openvpn def _is_up_to_date(src, local, system): if src is None or not access(src, R_OK): @@ -72,6 +84,9 @@ elif IS_MAC: # XXX check if bitmask-helper is running return True + def privcheck(): + return True + def digest(path): with open(path, 'r') as f: diff --git a/src/leap/bitmask/vpn/helpers/linux/polkit_agent.py b/src/leap/bitmask/vpn/helpers/linux/polkit_agent.py deleted file mode 100644 index 5ca1a2f0..00000000 --- a/src/leap/bitmask/vpn/helpers/linux/polkit_agent.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- -# polkit_agent.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 . - -""" -Daemonizes polkit authentication agent. -""" - -import os -import subprocess -import sys - -import daemon - - -POLKIT_PATHS = ( - '/usr/bin/lxpolkit', - '/usr/bin/lxqt-policykit-agent', - '/usr/lib/policykit-1-gnome/polkit-gnome-authentication-agent-1', - '/usr/lib/x86_64-linux-gnu/polkit-mate/polkit-mate-authentication-agent-1', - '/usr/lib/mate-polkit/polkit-mate-authentication-agent-1', - '/usr/lib/x86_64-linux-gnu/libexec/polkit-kde-authentication-agent-1', - '/usr/lib/kde4/libexec/polkit-kde-authentication-agent-1', - # now we get weird - '/usr/libexec/policykit-1-pantheon/pantheon-agent-polkit', - '/usr/lib/polkit-1-dde/dde-polkit-agent', - # do you know some we're still missing? :) -) - - -# TODO write tests for this piece. -def _get_polkit_agent(): - """ - Return a valid polkit agent to use. - - :rtype: str or None - """ - for polkit in POLKIT_PATHS: - if os.path.isfile(polkit): - return polkit - - return None - - -def _launch_agent(): - """ - Launch a polkit authentication agent on a subprocess. - """ - polkit_agent = _get_polkit_agent() - - if polkit_agent is None: - print("No usable polkit was found.") - return - - print('Launching polkit auth agent') - try: - # XXX fix KDE launch. See: #3755 - subprocess.call(polkit_agent) - except Exception as e: - print('Error launching polkit authentication agent %r' % (e, )) - - -def launch(): - """ - Launch a polkit authentication agent as a daemon. - """ - with daemon.DaemonContext(): - _launch_agent() - - -if __name__ == "__main__": - if '--nodaemon' in sys.argv: - _launch_agent() - else: - launch() diff --git a/src/leap/bitmask/vpn/launchers/linux.py b/src/leap/bitmask/vpn/launchers/linux.py index b0cd4f7f..f3b46a42 100644 --- a/src/leap/bitmask/vpn/launchers/linux.py +++ b/src/leap/bitmask/vpn/launchers/linux.py @@ -176,7 +176,7 @@ class LinuxVPNLauncher(VPNLauncher): if os.getuid() != 0: policyChecker = LinuxPolicyChecker() - pkexec = policyChecker.maybe_pkexec() + pkexec = policyChecker.get_usable_pkexec() if pkexec: command.insert(0, first(pkexec)) diff --git a/src/leap/bitmask/vpn/polkit.py b/src/leap/bitmask/vpn/polkit.py new file mode 100644 index 00000000..ae3f9000 --- /dev/null +++ b/src/leap/bitmask/vpn/polkit.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# polkit_agent.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 . + +""" +Daemonizes polkit authentication agent. +""" + +import os +import subprocess + + +POLKIT_PATHS = ( + '/usr/bin/lxpolkit', + '/usr/bin/lxqt-policykit-agent', + '/usr/lib/policykit-1-gnome/polkit-gnome-authentication-agent-1', + '/usr/lib/x86_64-linux-gnu/polkit-mate/polkit-mate-authentication-agent-1', + '/usr/lib/mate-polkit/polkit-mate-authentication-agent-1', + '/usr/lib/x86_64-linux-gnu/libexec/polkit-kde-authentication-agent-1', + '/usr/lib/kde4/libexec/polkit-kde-authentication-agent-1', + # now we get weird + '/usr/libexec/policykit-1-pantheon/pantheon-agent-polkit', + '/usr/lib/polkit-1-dde/dde-polkit-agent', + # do you know some we're still missing? :) +) + +POLKIT_PROC_NAMES = ( + 'polkit-gnome-authentication-agent-1', + 'polkit-kde-authentication-agent-1', + 'polkit-mate-authentication-agent-1', + 'lxpolkit', + 'lxsession', + 'gnome-shell', + 'gnome-flashback', + 'fingerprint-polkit-agent', + 'xfce-polkit', +) + + +# TODO write tests for this piece. +def _get_polkit_agent(): + """ + Return a valid polkit agent to use. + + :rtype: str or None + """ + for polkit in POLKIT_PATHS: + if os.path.isfile(polkit): + return polkit + return None + + +def launch(): + """ + Launch a polkit authentication agent as a daemon. + """ + agent = _get_polkit_agent() + subprocess.call("(setsid {polkit} &)".format(polkit=agent), shell=True) + + +if __name__ == "__main__": + launch() diff --git a/src/leap/bitmask/vpn/privilege.py b/src/leap/bitmask/vpn/privilege.py index c7296878..1856ec8c 100644 --- a/src/leap/bitmask/vpn/privilege.py +++ b/src/leap/bitmask/vpn/privilege.py @@ -22,7 +22,6 @@ are operative under this client run. import commands import os -import subprocess import psutil import time @@ -30,8 +29,8 @@ from twisted.logger import Logger from twisted.python.procutils import which from leap.bitmask.util import STANDALONE, here - from .constants import IS_LINUX +from . import polkit log = Logger() @@ -97,7 +96,7 @@ class LinuxPolicyChecker(object): else self.LINUX_POLKIT_FILE) @classmethod - def maybe_pkexec(self): + def get_usable_pkexec(self, timeout=20): """ Checks whether pkexec is available in the system, and returns the path if found. @@ -117,20 +116,17 @@ class LinuxPolicyChecker(object): self.launch() seconds = 0 while not self.is_up(): - if seconds >= 20: + if seconds >= timeout: log.warn('No polkit auth agent found. pkexec ' + 'will use its own auth agent.') raise NoPolkitAuthAgentAvailable() - - # XXX: sleep()!!!! we should do it the twisted way time.sleep(1) seconds += 1 - pkexec_possibilities = which(self.PKEXEC_BIN) - if not pkexec_possibilities: + pkexec_choices = which(self.PKEXEC_BIN) + if not pkexec_choices: raise Exception("We couldn't find pkexec") - - return pkexec_possibilities + return pkexec_choices @classmethod def launch(self): @@ -138,17 +134,7 @@ class LinuxPolicyChecker(object): Tries to launch polkit agent. """ if not self.is_up(): - try: - # We need to quote the command because subprocess call - # will do "sh -c 'foo'", so if we do not quoute it we'll end - # up with a invocation to the python interpreter. And that - # is bad. - log.debug('Trying to launch polkit agent') - subprocess.call( - ["python -m leap.bitmask.vpn.helpers.linux.polkit_agent"], - shell=True) - except Exception: - log.failure('Error while launching vpn') + polkit.launch() @classmethod def is_up(self): @@ -162,25 +148,12 @@ class LinuxPolicyChecker(object): # 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. - polkit_options = ( - 'polkit-gnome-authentication-agent-1', - 'polkit-kde-authentication-agent-1', - 'polkit-mate-authentication-agent-1', - 'lxpolkit', - 'lxsession', - 'gnome-shell', - 'gnome-flashback', - 'fingerprint-polkit-agent', - 'xfce-polkit', - ) - - is_running = False + running = False for proc in psutil.process_iter(): - if any((polkit in proc.name() for polkit in polkit_options)): - is_running = True + if any((pk in proc.name() for pk in polkit.POLKIT_PROC_NAMES)): + running = True break - - return is_running + return running def is_pkexec_in_system(): diff --git a/src/leap/bitmask/vpn/service.py b/src/leap/bitmask/vpn/service.py index c12f66dd..0ca0f566 100644 --- a/src/leap/bitmask/vpn/service.py +++ b/src/leap/bitmask/vpn/service.py @@ -40,6 +40,7 @@ from leap.bitmask.vpn._checks import ( from leap.bitmask.vpn import privilege, helpers from leap.bitmask.vpn import autostart +from leap.bitmask.vpn.constants import IS_LINUX from leap.common.config import get_path_prefix from leap.common.files import check_and_fix_urw_only from leap.common.events import catalog, emit_async @@ -101,8 +102,8 @@ class VPNService(HookableService): def startService(self): # TODO trigger a check for validity of the certificates, # and schedule a re-download if needed. - # TODO start a watchDog service (to push status events) super(VPNService, self).startService() + if self._autostart: self.start_vpn() @@ -117,6 +118,7 @@ class VPNService(HookableService): def start_vpn(self, domain=None): self._cfg.set('autostart', True) autostart.autostart_app('on') + if self.do_status()['status'] == 'on': exc = Exception('VPN already started') exc.expected = True @@ -134,9 +136,6 @@ class VPNService(HookableService): exc.expected = True raise exc - # XXX we can signal status to frontend, use - # get_failure_for(provider) -- no polkit, etc. - fw_ok = self._firewall.start() if not fw_ok: raise Exception('Could not start firewall') @@ -219,12 +218,21 @@ class VPNService(HookableService): def do_check(self, domain=None): """Check whether the VPN Service is properly configured, - and can be started""" - ret = {'installed': helpers.check()} + and can be started. This returns info about the helpers being + installed, a polkit agent being present, and optionally a valid + certificate present for a domain.""" + hashelpers = helpers.check() + privcheck = helpers.privcheck(timeout=5) + ret = {'installed': hashelpers, 'privcheck': privcheck} + if not privcheck: + if IS_LINUX: + ret['error'] = 'nopolkit' if domain: ret['vpn_ready'] = is_service_ready(domain) - expiry = cert_expires(domain).strftime('%Y-%m-%dT%H:%M:%SZ') - ret['cert_expires'] = expiry + expiry = cert_expires(domain) + if expiry: + expiry_ts = expiry.strftime('%Y-%m-%dT%H:%M:%SZ') + ret['cert_expires'] = expiry_ts return ret @defer.inlineCallbacks diff --git a/ui/README.md b/ui/README.md index 08181615..bf1d8667 100644 --- a/ui/README.md +++ b/ui/README.md @@ -35,7 +35,7 @@ python package and installed in the virtualenv: In order to package for distribution: - make dist-build + make pydist-build NOTE: If you make changes to the asset files, like add or modify an image, you will need to stop then rerun `npm run watch` for the changes to take diff --git a/ui/app/components/main_panel/vpn_section.js b/ui/app/components/main_panel/vpn_section.js index 71bbbe1d..78146933 100644 --- a/ui/app/components/main_panel/vpn_section.js +++ b/ui/app/components/main_panel/vpn_section.js @@ -116,6 +116,9 @@ export default class vpnSection extends React.Component { console.log('check()', error) if (error == "Missing VPN certificate") { this.renewCert() + } + if (error == 'nopolkit') { + this.setState({vpn: "nopolkit"}) } else { this.setState({vpn: "failed", error: error}) } @@ -306,6 +309,16 @@ export default class vpnSection extends React.Component { button = icon = "wait" break + case "nopolkit": + info = "Missing authentication agent." + body = ( +
+

Could not find a polkit authentication agent. + Please run one and try again.

+
+ ) + icon= "disabled" + break case "failed": info = "Failed" if (this.state.ready) { diff --git a/ui/package.json b/ui/package.json index b593f7b1..14afbda4 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "bitmask_js", - "version": "0.0.1", + "version": "0.3.0", "description": "bitmask user interface in javascript", "license": "GPL-3.0", "homepage": "https://bitmask.net", diff --git a/ui/pydist/setup.py b/ui/pydist/setup.py index 8f6fd4cd..0bfeac78 100644 --- a/ui/pydist/setup.py +++ b/ui/pydist/setup.py @@ -39,7 +39,7 @@ timestamp = time.strftime('%Y%m%d%H%M', now.timetuple()) setup( name='leap.bitmask_js', - version='0.2.%s' % timestamp, + version='0.3.%s' % timestamp, description='Bitmask UI', long_description=long_description, author='LEAP Encrypted Access Project', -- cgit v1.2.3