diff options
| -rw-r--r-- | changes/feature-7291_move_tuf | 1 | ||||
| -rw-r--r-- | src/leap/bitmask/backend_app.py | 27 | ||||
| -rw-r--r-- | src/leap/bitmask/updater.py | 174 | 
3 files changed, 190 insertions, 12 deletions
diff --git a/changes/feature-7291_move_tuf b/changes/feature-7291_move_tuf new file mode 100644 index 00000000..3db1fd2b --- /dev/null +++ b/changes/feature-7291_move_tuf @@ -0,0 +1 @@ +- Move the updater code from the launcher to the client (Closes: #7291) diff --git a/src/leap/bitmask/backend_app.py b/src/leap/bitmask/backend_app.py index 1040fbf8..dcd08d97 100644 --- a/src/leap/bitmask/backend_app.py +++ b/src/leap/bitmask/backend_app.py @@ -20,6 +20,8 @@ Start point for the Backend.  import multiprocessing  import signal +from twisted.internet import reactor +  from leap.common.events import server as event_server  from leap.bitmask.backend.leapbackend import LeapBackend @@ -80,23 +82,24 @@ def run_backend(bypass_checks=False, flags_dict=None, frontend_pid=None):      if flags_dict is not None:          dict_to_flags(flags_dict) -    # HACK we should be able to run the ensure_server anyway but right now it -    # breaks if we run it twice. -    if not flags.STANDALONE: -        # start the events server -        # This is not needed for the standalone bundle since the launcher takes -        # care of it. -        try: -            from twisted.internet import reactor -            reactor.callWhenRunning(reactor.callLater, 0, -                                    event_server.ensure_server) -        except Exception as e: -            logger.error("Could not ensure server: %r" % (e,)) +    reactor.callWhenRunning(start_events_and_updater, logger)      backend = LeapBackend(bypass_checks=bypass_checks,                            frontend_pid=frontend_pid)      backend.run() +def start_events_and_updater(logger): +    event_server.ensure_server() + +    if flags.STANDALONE: +        try: +            from leap.bitmask.updater import Updater +            updater = Updater() +            updater.start() +        except ImportError: +            logger.error("Updates are not enabled in this distribution.") + +  if __name__ == '__main__':      run_backend() diff --git a/src/leap/bitmask/updater.py b/src/leap/bitmask/updater.py new file mode 100644 index 00000000..c35eff5f --- /dev/null +++ b/src/leap/bitmask/updater.py @@ -0,0 +1,174 @@ +# -*- coding: utf-8 -*- +# updater.py +# Copyright (C) 2014, 2015 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/>. +""" +Updater check and download loop. +""" +import os +import shutil +import platform +import time +import threading +import ConfigParser +import tuf.client.updater + +from leap.bitmask.logs.utils import get_logger +from leap.common.events import emit, catalog + + +logger = get_logger() + + +""" +Supported platforms. + +Maps platform names from `platform.system() + "-" + platform.machine()` to the +platform names we use in the repos. +""" +bundles_per_platform = { +    "Linux-i386": "linux-i386", +    "Linux-i686": "linux-i386", +    "Linux-x86_64": "linux-x86_64", +} + +CONFIG_PATH = "launcher.conf" +GENERAL_SECTION = "General" +DELAY_KEY = "updater_delay" + + +class Updater(threading.Thread): +    def __init__(self): +        """ +        Initialize the list of mirrors, paths and other TUF dependencies from +        the config file +        """ +        config = ConfigParser.ConfigParser() +        config.read(CONFIG_PATH) + +        if config.has_section(GENERAL_SECTION) and \ +                config.has_option(GENERAL_SECTION, DELAY_KEY): +            self.delay = config.getint(GENERAL_SECTION, DELAY_KEY) +        else: +            self.delay = 60 + +        self._load_mirrors(config) +        if not self.mirrors: +            logger.error("No updater mirrors found (missing or not well " +                         "formed launcher.conf)") + +        self.bundle_path = os.getcwd() +        self.source_path = self.bundle_path +        self.dest_path = os.path.join(self.bundle_path, 'tmp') +        self.update_path = os.path.join(self.bundle_path, 'updates') + +        tuf.conf.ssl_certificates = "./lib/leap/common/cacert.pem" + +        threading.Thread.__init__(self) +        self.daemon = True + +    def run(self): +        """ +        Check for updates +        """ +        if not self.mirrors: +            return + +        while True: +            try: +                tuf.conf.repository_directory = os.path.join(self.bundle_path, +                                                             'repo') + +                updater = tuf.client.updater.Updater('leap-updater', +                                                     self.mirrors) +                updater.refresh() + +                targets = updater.all_targets() +                updated_targets = updater.updated_targets(targets, +                                                          self.source_path) +                if updated_targets: +                    logger.info("There is updates needed. Start downloading " +                                "updates.") +                for target in updated_targets: +                    updater.download_target(target, self.dest_path) +                    self._set_permissions(target) +                if os.path.isdir(self.dest_path): +                    if os.path.isdir(self.update_path): +                        shutil.rmtree(self.update_path) +                    shutil.move(self.dest_path, self.update_path) +                    filepath = sorted([f['filepath'] for f in updated_targets]) +                    emit(catalog.UPDATER_NEW_UPDATES, +                         ", ".join(filepath)) +                    logger.info("Updates ready: %s" % (filepath,)) +                    return +            except NotImplemented as e: +                logger.error("NotImplemented: %s" % (e,)) +                return +            except Exception as e: +                logger.error("An unexpected error has occurred while " +                             "updating: %s" % (e,)) +            finally: +                time.sleep(self.delay) + +    def _load_mirrors(self, config): +        """ +        Retrieve the mirrors from config and place them in self.mirrors + +        :param config: parsed configuration file +        :type config: ConfigParser +        """ +        self.mirrors = {} +        for section in config.sections(): +            if section[:6] != 'Mirror': +                continue +            url_prefix = config.get(section, 'url_prefix') +            metadata_path = self._repo_path() + '/metadata' +            targets_path = self._repo_path() + '/targets' +            self.mirrors[section[7:]] = {'url_prefix': url_prefix, +                                         'metadata_path': metadata_path, +                                         'targets_path': targets_path, +                                         'confined_target_dirs': ['']} + +    def _set_permissions(self, target): +        """ +        Walk over all the targets and set the rigt permissions on each file. +        The permisions are stored in the custom field 'file_permissions' of the +        TUF's targets.json + +        :param target: the already parsed target json +        :type target: tuf.formats.TARGETFILES_SCHEMA +        """ +        file_permissions_str = target["fileinfo"]["custom"]["file_permissions"] +        file_permissions = int(file_permissions_str, 8) +        filepath = target['filepath'] +        if filepath[0] == '/': +            filepath = filepath[1:] +        file_path = os.path.join(self.dest_path, filepath) +        os.chmod(file_path, file_permissions) + +    def _repo_path(self): +        """ +        Find the remote repo path deneding on the platform. + +        :return: the path to add to the remote repo url for the specific platform. +        :rtype: str + +        :raises NotImplemented: When the system where bitmask is running is not +                                supported by the updater. +        """ +        system = platform.system() + "-" + platform.machine() +        if system not in bundles_per_platform: +            raise NotImplementedError("Platform %s not supported" % (system,)) +        return bundles_per_platform[system]  | 
