diff options
| -rw-r--r-- | CHANGELOG.rst | 10 | ||||
| -rwxr-xr-x | pkg/linux/bitmask-root | 5 | ||||
| -rw-r--r-- | pkg/requirements.pip | 2 | ||||
| -rwxr-xr-x | pkg/tuf/release.py | 135 | ||||
| -rw-r--r-- | relnotes.txt | 8 | ||||
| -rw-r--r-- | src/leap/bitmask/config/providerconfig.py | 3 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/mainwindow.py | 52 | ||||
| -rw-r--r-- | src/leap/bitmask/provider/pinned.py | 5 | ||||
| -rw-r--r-- | src/leap/bitmask/provider/pinned_calyx.py | 93 | ||||
| -rw-r--r-- | src/leap/bitmask/provider/providerbootstrapper.py | 2 | ||||
| -rw-r--r-- | src/leap/bitmask/services/eip/eipconfig.py | 9 | ||||
| -rw-r--r-- | src/leap/bitmask/util/privilege_policies.py | 1 | 
12 files changed, 286 insertions, 39 deletions
| diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3c863657..82b86918 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,8 +6,8 @@ History  2014  ==== -0.7.0 September 26 -- the "one time download, all time updates" release: -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +0.7.0 December 12 -- the "One window to rule them all, and in the darkness bind them." release: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++  - Select current provider on EIP preferences. Closes #5815.  - Handle logout correctly when we stop_services to launch the @@ -33,6 +33,12 @@ History  - Remove the Advanced Key Management since we don't support stable    mail yet. Closes #6087.  - Single combined preferences window. Closes #4704, #4119, #5885. +- Fix soledad imports (#5989). +- Make pkg/tuf/release.py handle removals in the repo +- Remove instructions/references of mail from the client. Closes #6140. +- Add support for the internal LXDE polkit agent. Closes #6043. +- Allow the server to set a custom --fragment openvpn option (#5933) +- Add Calyx.net as pinned provider. Closes #6518.  0.6.1 August 15 -- the "knock knock knocking on beta's door" release:  +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root index ee195e3b..622a0b8a 100755 --- a/pkg/linux/bitmask-root +++ b/pkg/linux/bitmask-root @@ -51,7 +51,7 @@ cmdcheck = subprocess.check_output  # CONSTANTS  # -VERSION = "3" +VERSION = "4"  SCRIPT = "bitmask-root"  NAMESERVER = "10.42.0.1"  BITMASK_CHAIN = "bitmask" @@ -96,7 +96,8 @@ ALLOWED_FLAGS = {      "--management-client-user": ["USER"],      "--cert": ["FILE"],      "--key": ["FILE"], -    "--ca": ["FILE"] +    "--ca": ["FILE"], +    "--fragment": ["NUMBER"]  }  PARAM_FORMATS = { diff --git a/pkg/requirements.pip b/pkg/requirements.pip index 9f49bf03..8ce1793e 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -27,7 +27,7 @@ zope.proxy  pyzmq  leap.common>=0.3.7 -leap.soledad.client>=0.5.0 +leap.soledad.client>=0.6.0  leap.keymanager>=0.3.8  leap.mail>=0.3.9 diff --git a/pkg/tuf/release.py b/pkg/tuf/release.py new file mode 100755 index 00000000..0e1c989c --- /dev/null +++ b/pkg/tuf/release.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# release.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 <http://www.gnu.org/licenses/>. + +""" +Tool to generate TUF related files after a release + +The 'repo' folder should contain two folders: +  - 'metadata.staged' with all the jsons from the previows release +  - 'targets' where the release targets are +""" + +import datetime +import os.path +import sys + +from tuf.repository_tool import load_repository +from tuf.repository_tool import import_rsa_privatekey_from_file + +""" +Days until the expiration of targets.json and snapshot.json. After this ammount +of days the TUF client won't accept this files. +""" +EXPIRATION_DAYS = 90 + + +def usage(): +    print "Usage:  %s repo key" % (sys.argv[0],) + + +def main(): +    if len(sys.argv) < 3: +        usage() +        return + +    repo_path = sys.argv[1] +    key_path = sys.argv[2] +    targets = Targets(repo_path, key_path) +    targets.build() + +    print "%s/metadata.staged/(targets|snapshot).json[.gz] are ready" % \ +          (repo_path,) + + +class Targets(object): +    """ +    Targets builder class +    """ + +    def __init__(self, repo_path, key_path): +        """ +        Constructor + +        :param repo_path: path where the repo lives +        :type repo_path: str +        :param key_path: path where the private targets key lives +        :type key_path: str +        """ +        self._repo_path = repo_path +        self._key = import_rsa_privatekey_from_file(key_path) + +    def build(self): +        """ +        Generate snapshot.json[.gz] and targets.json[.gz] +        """ +        self._repo = load_repository(self._repo_path) +        self._load_targets() + +        self._repo.targets.load_signing_key(self._key) +        self._repo.snapshot.load_signing_key(self._key) +        self._repo.targets.compressions = ["gz"] +        self._repo.snapshot.compressions = ["gz"] +        self._repo.snapshot.expiration = ( +            datetime.datetime.now() + +            datetime.timedelta(days=EXPIRATION_DAYS)) +        self._repo.targets.expiration = ( +            datetime.datetime.now() + +            datetime.timedelta(days=EXPIRATION_DAYS)) +        self._repo.write_partial() + +    def _load_targets(self): +        """ +        Load a list of targets +        """ +        targets_path = os.path.join(self._repo_path, 'targets') +        target_list = self._repo.get_filepaths_in_directory( +            targets_path, +            recursive_walk=True, +            followlinks=True) + +        self._remove_obsolete_targets(target_list) + +        for target in target_list: +            octal_file_permissions = oct(os.stat(target).st_mode)[3:] +            custom_file_permissions = { +                'file_permissions': octal_file_permissions +            } +            self._repo.targets.add_target(target, custom_file_permissions) + +    def _remove_obsolete_targets(self, target_list): +        """ +        Remove obsolete targets from TUF targets + +        :param target_list: list of targets on full path comming from TUF +                            get_filepaths_in_directory +        :type target_list: list(str) +        """ +        targets_path = os.path.join(self._repo_path, 'targets') +        relative_path_list = map(lambda t: t.split("/targets")[1], target_list) +        removed_targets = (set(self._repo.targets.target_files.keys()) +                           - set(relative_path_list)) + +        for target in removed_targets: +            target_rel_path = target +            if target[0] == '/': +                target_rel_path = target[1:] +            target_path = os.path.join(targets_path, target_rel_path) +            self._repo.targets.remove_target(target_path) + + +if __name__ == "__main__": +    main() diff --git a/relnotes.txt b/relnotes.txt index 85d4ecbf..c4104fc5 100644 --- a/relnotes.txt +++ b/relnotes.txt @@ -1,8 +1,8 @@ -ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.7.0 +ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.7.0.  The LEAP  team is  pleased to announce  the immediate  availability of -version 0.7.0 of Bitmask,  the Internet Encryption  Toolkit,  codename -"one time download, all time updates". +version  0.7.0 of Bitmask, the  Internet Encryption Toolkit,  codename +"One window to rule them all, and in the darkness bind them."  https://downloads.leap.se/client/ @@ -107,6 +107,6 @@ beyond any border.  The LEAP team, -October 09, 2014 +December 12, 2014  Somewhere in the middle of the intertubes.  EOF diff --git a/src/leap/bitmask/config/providerconfig.py b/src/leap/bitmask/config/providerconfig.py index 57bc3a98..386c697d 100644 --- a/src/leap/bitmask/config/providerconfig.py +++ b/src/leap/bitmask/config/providerconfig.py @@ -201,7 +201,8 @@ class ProviderConfig(BaseConfig):              leap_check(cert_exists, error_msg, MissingCACert)              logger.debug("Going to verify SSL against %s" % (cert_path,)) -        return cert_path +        # OpenSSL does not handle unicode. +        return cert_path.encode('utf-8')      def provides_eip(self):          """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index cc4ede09..1b61de87 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -34,7 +34,6 @@ from leap.bitmask import __version_hash__ as VERSION_HASH  from leap.bitmask.backend.leapbackend import ERROR_KEY, PASSED_KEY  from leap.bitmask.config import flags -from leap.bitmask.config.leapsettings import LeapSettings  from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement  from leap.bitmask.gui.eip_status import EIPStatusWidget @@ -53,9 +52,6 @@ from leap.bitmask.platform_init import locks  from leap.bitmask.platform_init.initializers import init_platform  from leap.bitmask.platform_init.initializers import init_signals -from leap.bitmask.backend.backend_proxy import BackendProxy -from leap.bitmask.backend.leapsignaler import LeapSignaler -  from leap.bitmask.services.eip import conductor as eip_conductor  from leap.bitmask.services.mail import conductor as mail_conductor @@ -144,7 +140,8 @@ class MainWindow(QtGui.QMainWindow):          # Qt Signal Connections #####################################          # TODO separate logic from ui signals. -        self.app.service_selection_changed.connect(self._update_eip_enabled_status) +        self.app.service_selection_changed.connect( +            self._update_eip_enabled_status)          self._login_widget.login.connect(self._login)          self._login_widget.cancel_login.connect(self._cancel_login)          self._login_widget.logout.connect(self._logout) @@ -210,7 +207,11 @@ class MainWindow(QtGui.QMainWindow):          self.ui.action_quit.triggered.connect(self.quit)          self.ui.action_wizard.triggered.connect(self._launch_wizard)          self.ui.action_show_logs.triggered.connect(self._show_logger_window) -        self.ui.action_help.triggered.connect(self._help) + +        # XXX hide the help menu since it only shows email information and +        # right now we don't have stable mail and just confuses users. +        self.ui.action_help.setVisible(False) +        # self.ui.action_help.triggered.connect(self._help)          self.ui.action_create_new_account.triggered.connect(              self._on_provider_changed) @@ -600,10 +601,12 @@ class MainWindow(QtGui.QMainWindow):              self._backend_cannot_start_eip()              return -        if not EIP_SERVICE in self.app.settings.get_enabled_services(domain): +        if EIP_SERVICE not in self.app.settings.get_enabled_services(domain):              self._eip_conductor.terminate() +              def hide(): -              self.app.backend.eip_can_start(domain=domain) +                self.app.backend.eip_can_start(domain=domain) +              QtDelayedCall(100, hide)              # ^^ VERY VERY Hacky, but with the simple state machine,              # there is no way to signal 'disconnect and then disable' @@ -615,7 +618,6 @@ class MainWindow(QtGui.QMainWindow):              # check if EIP can start (will trigger widget update)              self.app.backend.eip_can_start(domain=domain) -      def _backend_can_start_eip(self):          """          TRIGGER: @@ -1003,22 +1005,22 @@ class MainWindow(QtGui.QMainWindow):          today = datetime.now().date()          greet = ("Happy New 1984!... or not ;)<br><br>"                   if today.month == 1 and today.day < 15 else "") -        QtGui.QMessageBox.about( -            self, self.tr("About Bitmask - %s") % (VERSION,), -            self.tr("Version: <b>%s</b> (%s)<br>" -                    "<br>%s" -                    "Bitmask is the Desktop client application for " -                    "the LEAP platform, supporting encrypted internet " -                    "proxy, secure email, and secure chat (coming soon).<br>" -                    "<br>" -                    "LEAP is a non-profit dedicated to giving " -                    "all internet users access to secure " -                    "communication. Our focus is on adapting " -                    "encryption technology to make it easy to use " -                    "and widely available. <br>" -                    "<br>" -                    "<a href='https://leap.se'>More about LEAP" -                    "</a>") % (VERSION, VERSION_HASH[:10], greet)) +        title = self.tr("About Bitmask - %s") % (VERSION,) +        msg = self.tr( +            "Version: <b>{ver}</b> ({ver_hash})<br>" +            "<br>{greet}" +            "Bitmask is the Desktop client application for the LEAP " +            "platform, supporting encrypted internet proxy.<br>" +            # "Secure email is comming soon.<br>" +            "<br>" +            "LEAP is a non-profit dedicated to giving all internet users " +            "access to secure communication. Our focus is on adapting " +            "encryption technology to make it easy to use and widely " +            "available.<br>" +            "<br>" +            "<a href='https://leap.se'>More about LEAP</a>") +        msg = msg.format(ver=VERSION, ver_hash=VERSION_HASH[:10], greet=greet) +        QtGui.QMessageBox.about(self, title, msg)      @QtCore.Slot()      def _help(self): diff --git a/src/leap/bitmask/provider/pinned.py b/src/leap/bitmask/provider/pinned.py index 6fd2fa70..09fcc52c 100644 --- a/src/leap/bitmask/provider/pinned.py +++ b/src/leap/bitmask/provider/pinned.py @@ -19,6 +19,7 @@ Pinned Providers  """  import logging +from leap.bitmask.provider import pinned_calyx  from leap.bitmask.provider import pinned_demobitmask  from leap.bitmask.provider import pinned_riseup @@ -35,6 +36,10 @@ class PinnedProviders(object):      PREFERRED_PROVIDER = pinned_demobitmask.DOMAIN      PROVIDERS = { +        pinned_calyx.DOMAIN: { +            CONFIG_KEY: pinned_calyx.PROVIDER_JSON, +            CACERT_KEY: pinned_calyx.CACERT_PEM, +        },          pinned_demobitmask.DOMAIN: {              CONFIG_KEY: pinned_demobitmask.PROVIDER_JSON,              CACERT_KEY: pinned_demobitmask.CACERT_PEM, diff --git a/src/leap/bitmask/provider/pinned_calyx.py b/src/leap/bitmask/provider/pinned_calyx.py new file mode 100644 index 00000000..eb9ab781 --- /dev/null +++ b/src/leap/bitmask/provider/pinned_calyx.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# pinned_calyx.py +# Copyright (C) 2013-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 <http://www.gnu.org/licenses/>. +""" +Pinned provider.json and cacert.pem for riseup.net +""" + +DOMAIN = "calyx.net" + +PROVIDER_JSON = """ +{ +  "api_uri": "https://api.calyx.net:4430", +  "api_version": "1", +  "ca_cert_fingerprint": "SHA256: 43683c9ba3862c5384a8c1885072fcac40b5d2d4dd67331443f13a3077fa2e69", +  "ca_cert_uri": "https://calyx.net/ca.crt", +  "default_language": "en", +  "description": { +    "en": "Calyx Institute privacy focused ISP testbed" +  }, +  "domain": "calyx.net", +  "enrollment_policy": "open", +  "languages": [ +    "en" +  ], +  "name": { +    "en": "calyx" +  }, +  "service": { +    "allow_anonymous": false, +    "allow_free": true, +    "allow_limited_bandwidth": false, +    "allow_paid": false, +    "allow_registration": true, +    "allow_unlimited_bandwidth": true, +    "bandwidth_limit": 102400, +    "default_service_level": 1, +    "levels": { +      "1": { +        "description": "Please donate.", +        "name": "free" +      } +    } +  }, +  "services": [ +    "openvpn" +  ] +} +""" + +CACERT_PEM = """-----BEGIN CERTIFICATE----- +MIIFYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQ0FADBEMQ4wDAYDVQQKDAVjYWx5 +eDEaMBgGA1UECwwRaHR0cHM6Ly9jYWx5eC5uZXQxFjAUBgNVBAMMDWNhbHl4IFJv +b3QgQ0EwHhcNMTMwNzAyMDAwMDAwWhcNMjMwNzAyMDAwMDAwWjBEMQ4wDAYDVQQK +DAVjYWx5eDEaMBgGA1UECwwRaHR0cHM6Ly9jYWx5eC5uZXQxFjAUBgNVBAMMDWNh +bHl4IFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDupdnx +Bgat537XOqrZOulE/RvjoXB1S07sy9/MMtksXFoQuWJZRCSTp1Jaqg3H/e9o1nct +LQO91+izfJe07TUyajFl7CfllYgMeyKTYcT85dFwNX4pcIHZr8UpmO0MpGBoR4W1 +8cPa3vxAG0CsyUmrASJVyhRouk4qazRosM5RwBxTdMzCK7L3SwqPQoxlY9YmRJlD +XYZlK5VMJd0dj9XxhMeFs5n43R0bsDENryrExSbuxoNfnUoQg3wffKk+Z0gW7YgW +ivPsbObqOgXUuBEU0xr9xMNBpU33ffLIsccrHq1EKp8zGfCOcww6v7+zEadUkVLo +6j/rRhYYgRw9lijZG1rMuV/mTGnUqbjHsdoz5mzkFFWeTSqo44lvhveUyCcwRNmi +2sjS77l0fCTzfreufffFoOEcRVMRfsnJdu/xPeARoXILEx8nQ421mSn6spOZlDQr +Tt0T0BAWt+VNc+m0IGSW3SwS7r5MUyQ/M5GrbQBGi5W2SzPriKZ79YTOwPVmXKLZ +vJoEuKRDkEPJLBAhcD5oSQljOm/Wp/hjmRH4HnI1y4XMshWlDsyRDB1Au5yrsfwN +noFVSskEcbXlZfNgml4lktLBqz+qwsw+voq6Ak7ROKbc0ii5s8+iNMbAtIK7GcFF +kuKKIyRmmGlDim/SDhlNdWo7Ah4Akde7zfWufwIDAQABo2AwXjAdBgNVHQ4EFgQU +AY8+K4ZupAQ+L9ttFJG3vaLBq5gwDgYDVR0PAQH/BAQDAgIEMAwGA1UdEwQFMAMB +Af8wHwYDVR0jBBgwFoAUAY8+K4ZupAQ+L9ttFJG3vaLBq5gwDQYJKoZIhvcNAQEN +BQADggIBAOpXi5o3g/2o2rPa53iG7Zgcy8RpePGgZk6xknGYWeLamEqSh+XWQZ2w +2kQP54bf8HfPj3ugJBWsVtYAs/ltJwzeBfYDrwEJd1N8tw2IRuGlQOWiTAVVLBj4 +Zs+dikSuMoA399f/7BlUIEpVLUiV/emTtbkjFnDeKEV9zql6ypR0BtR8Knf8ALvL +YfMsWLvTe4rXeypzxIaE2pn8ttcXLYAX0ml2MofTi5xcDhMn1vznKIvs82xhncQx +I1MJMWqPHNHgJUJpA+y1IFh5LPbpag9PKQ0yQ9sM+/dyGumF2jElsMw71flh/Txr +2dEv8+FNV1pPK26XJZBK24rNWFs30eAFfH9EQCwVla174I4PDoWqsIR7vtQMObDt +Bq34R3TjjJJIt2sCSlYLooWwiK7Q+d/SgYqA+MSDmmwhzm86ToK6cwbCsvuw1AxR +X6VIs4U8wOotgljzX/CSpKqlxcqZjhnAuelZ1+KiN8RHKPj7AzSLYOv/YwTjLTIq +EOxquoNR58uDa5pBG22a7xWbSaKosn/mEl8SrUr6klzzc8Vh09IMoxrw74uLdAg2 +1jnrhm7qg91Ttb0aXiqbV+Kg/qQzojdewnnoBFnv4jaQ3y8zDCfMhsBtWlWz4Knb +Zqga1WyRm3Gj1j6IV0oOincYMrw5YA7bgXpwop/Lo/mmliMA14ps +-----END CERTIFICATE-----""" diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index 71edbb87..8eefb9d9 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -148,7 +148,6 @@ class ProviderBootstrapper(AbstractBootstrapper):                                      timeout=REQUEST_TIMEOUT)              res.raise_for_status()          except requests.exceptions.SSLError as exc: -            logger.exception(exc)              self._err_msg = self.tr("Provider certificate could "                                      "not be verified")              raise @@ -156,7 +155,6 @@ class ProviderBootstrapper(AbstractBootstrapper):              # XXX careful!. The error might be also a SSL handshake              # timeout error, in which case we should retry a couple of times              # more, for cases where the ssl server gives high latencies. -            logger.exception(exc)              self._err_msg = self.tr("Provider does not support HTTPS")              raise diff --git a/src/leap/bitmask/services/eip/eipconfig.py b/src/leap/bitmask/services/eip/eipconfig.py index 5b51d12e..659ca1b1 100644 --- a/src/leap/bitmask/services/eip/eipconfig.py +++ b/src/leap/bitmask/services/eip/eipconfig.py @@ -216,7 +216,7 @@ class EIPConfig(ServiceConfig):      """      _service_name = "eip" -    OPENVPN_ALLOWED_KEYS = ("auth", "cipher", "tls-cipher") +    OPENVPN_ALLOWED_KEYS = ("auth", "cipher", "tls-cipher", "fragment")      OPENVPN_CIPHERS_REGEX = re.compile("[A-Z0-9\-]+")      def __init__(self): @@ -255,6 +255,11 @@ class EIPConfig(ServiceConfig):          These are sanitized with alphanumeric whitelist. +        NOTE: some openvpn config option don't take a value, but +        this method currently requires that every option has a value. +        Also, this does not yet work with values with spaces, like +        `keepalive 10 30` +          :returns: openvpn configuration dict          :rtype: C{dict}          """ @@ -262,7 +267,7 @@ class EIPConfig(ServiceConfig):          config = {}          for key, value in ovpncfg.items():              if key in self.OPENVPN_ALLOWED_KEYS and value is not None: -                sanitized_val = self.OPENVPN_CIPHERS_REGEX.findall(value) +                sanitized_val = self.OPENVPN_CIPHERS_REGEX.findall(str(value))                  if len(sanitized_val) != 0:                      _val = sanitized_val[0]                      config[str(key)] = str(_val) diff --git a/src/leap/bitmask/util/privilege_policies.py b/src/leap/bitmask/util/privilege_policies.py index 2016e67b..68a1af28 100644 --- a/src/leap/bitmask/util/privilege_policies.py +++ b/src/leap/bitmask/util/privilege_policies.py @@ -179,6 +179,7 @@ class LinuxPolicyChecker(PolicyChecker):              '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]xsession"',              'ps aux | grep "[g]nome-shell"',              'ps aux | grep "[f]ingerprint-polkit-agent"',          ] | 
