403Webshell
Server IP : 3.96.16.70  /  Your IP : 216.73.216.15
Web Server : Apache
System : Linux ip-172-31-26-103.ca-central-1.compute.internal 6.1.163-186.299.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Feb 24 16:35:42 UTC 2026 x86_64
User : ec2-user ( 1000)
PHP Version : 8.4.18
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /lib/python3.9/site-packages/dnf-plugins/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /lib/python3.9/site-packages/dnf-plugins/release_notification.py
# Copyright Amazon.com, Inc. and its affiliates. All Rights Reserved.
#
# 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; version 2
# of the License.
#
# 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/gpl-2.0.html>.

from functools import singledispatch
import logging
from xml.etree import ElementTree
import tempfile
import os
import io
from collections import namedtuple

import dnf
import libdnf
import gpg


DEFAULT_DISTRO_TITLE = 'your OS'

UPDATE_HEADER = """WARNING:
  A newer release of {distro_title} is available.

  Available Versions:
"""

UPDATE_INSTANCE_TEXT = """  Version {name}:
    Run the following command to upgrade to {version_string}:

      dnf upgrade --releasever={version_string}

    Release notes:
     {release_notes}
"""

UPGRADE_HEADER = """NOTE:
  A new version of {distro_title} is available!

  Available Upgrades:
"""

UPGRADE_INSTANCE_TEXT = """  Version {name}:
    See {release_notes}
    To upgrade, run:
      dnf upgrade --releasever={version_string}
"""

RPM_GPG_KEY_PATH = '/etc/pki/rpm-gpg'

ReleaseVersion = namedtuple('UpdateVersion', ('name', 'version_string', 'release_notes'))

class ReleaseNotification(dnf.Plugin):
    """Populates the release metadata and informs the user of available upgrade paths.
    """
    name = "release-notification"
    singleton = None

    def __init__(self, base, cli):
        """Sets up new instance and registers the os release command if were executing in the cli.
        """
        self.base = base
        self.cli = cli
        self.current_release = self.base.conf.releasever

        self.logger = logging.getLogger('dnf')
        self.cfg = None
        self.release_info = None

        if ReleaseNotification.singleton is None:
            ReleaseNotification.singleton = self

        if self.cli is not None:
            self.cli.register_command(CheckReleaseUpdateCommand)

    def _get_os_metadata(self, os_release_file_path="/etc/os-release", cached=True):
        """Retrieve the OS metadata from the /etc/os-release file.  Returns a dictionary of metadata values.  Returns
        an empty dictionary if no data was found.
        """
        if getattr(self, '_os_metadata', None) is None or cached is False:
            try:
                os_release_file = open(os_release_file_path, 'r')
            except OSError:
                self.logger.error(f"Unable to open os release file: {os_release_file_path}")
                return {}

            metadata = {}

            for line in os_release_file.readlines():
                name, value = line.split('=', 1)
                metadata[name] = value.strip()

            os_release_file.close()

            self._os_metadata = metadata

        return self._os_metadata
        
    @property
    def os_release_title(self):
        return self._get_os_metadata().get('NAME', DEFAULT_DISTRO_TITLE)

    def pre_config(self):
        """DNF hook for loading plugin config variables.
        """
        self.cfg = self.read_config(self.base.conf)

        if self.cfg and 'Main' in self.cfg.sections():
            release_info_url = self.cfg.get('Main', 'release_info_url', None)
            release_info_url = libdnf.conf.ConfigParser.substitute(release_info_url, self.base.conf.substitutions)
            try:
                gpgcheck = self.cfg.get('Main', 'gpgcheck', None)
                gpgcheck = gpgcheck.lower() in ['true', 'yes', '1']
            except KeyError as err:
                gpgcheck = True
            try:
                release_info_asc = self.cfg.get('Main', 'release_info_asc', None)
            except KeyError as err:
                release_info_asc = release_info_url + ".asc"
            release_info_asc = libdnf.conf.ConfigParser.substitute(release_info_asc, self.base.conf.substitutions)

            self.release_info = ReleaseUpdateInfo(self.base, release_info_url, gpgcheck, release_info_asc)

    def transaction(self):
        """Reads the plugin config and populates the release metadata. If execution is under the cli, output available
        releases to user.
        """
        if self.cli is None or 'osrelease' == self.cli.command.aliases[0]:
            return

        self.run_release_check()

    def run_release_check(self, output_separators=True, output_versions_only=False, output_latest_version_only=False):
        """Performs check on release info metadata and determines if an update is available.
        """
        if self.release_info is None:
            return

        try:
            release_data = self.release_info.retrieve_release_info(self.current_release)
        except RuntimeError as exp:
            self.logger.error(f'Error encountered while trying to retrieve release update information: {exp.args[0]}')
            return
        
        if not release_data:
            self.logger.error('Returned release info was invalid.')
            return

        if self.current_release not in release_data.get('releases', []):
            return

        relevant_release = release_data['releases'][self.current_release]

        update_releases, upgrade_releases = self.generate_release_versions(relevant_release)

        if output_latest_version_only:
            update_releases = update_releases[-1:]
            upgrade_releases = []

        output = []

        if output_versions_only is True:
            output_separators = False
            output = [release.version_string for release in update_releases + upgrade_releases]
        else:
            if update_releases:
                output.append(UPDATE_HEADER.format(distro_title=self.os_release_title))
                for update in update_releases:
                    output.append(UPDATE_INSTANCE_TEXT.format(**update._asdict()))

            if upgrade_releases:
                output.append(UPGRADE_HEADER.format(distro_title=self.os_release_title))
                for upgrade in upgrade_releases:
                    output.append(UPGRADE_INSTANCE_TEXT.format(**upgrade._asdict()))

        if output:
            if output_separators: self.logger.warning('=' * self.base.output.term.columns)

            self.logger.warning('\n'.join(output))

            if output_separators: self.logger.warning('=' * self.base.output.term.columns)

    def generate_release_versions(self, relevant_release):
        updates = []
        upgrades = []

        for update in relevant_release['update']:
                updates.append(
                    ReleaseVersion(update['name'], update['version_string'], update['release_notes'])
                    )

        for upgrade in relevant_release['upgrade']:
                upgrades.append(
                    ReleaseVersion(upgrade['name'], upgrade['version_string'], upgrade['release_notes'])
                    )

        return (
                sorted(updates, key=lambda r: tuple(map(int, r.version_string.split('.')))),
                sorted(upgrades, key=lambda r: tuple(map(int, r.version_string.split('.'))))
            )

class CheckReleaseUpdateCommand(dnf.cli.Command):
    """Provides a manual command for checking for release updates.
    """
    aliases = ('check-release-update', 'relupdate')
    summary = ('check for updates or upgrades to the current release')

    @staticmethod
    def set_argparser(parser):
        parser.add_argument(
            '--version-only',
            action='store_true',
            help='List only version strings for OS updates.'
            )
        parser.add_argument(
            '--latest-only',
            action='store_true',
            help='Show only the latest OS update version.'
            )

    def configure(self):
        self.logger = logging.getLogger('dnf')
        
        demands = self.cli.demands

    def run(self):
        if(ReleaseNotification.singleton is None):
            self.logger.error(
                'Cannot check for release updates. The ReleaseNotification dnf plugin has not been properly initialized'
                )
            return

        ReleaseNotification.singleton.run_release_check(
            output_separators=False,
            output_versions_only=self.opts.version_only,
            output_latest_version_only=self.opts.latest_only,
            )


class ReleaseUpdateInfo():
    """Manages retrieving and validating release update information.
    """

    def __init__(self, dnf_base, release_info_url, gpgcheck, release_info_asc):
        self.dnf_base = dnf_base
        self.release_info_url = release_info_url
        self.release_info_asc = release_info_asc
        self.gpgcheck = gpgcheck

        self.logger = logging.getLogger('dnf')

    def _validate_release_info_signature(self, release_info_data, release_info_sig):
        """Validate the release info data against the provided signature.  Utilize the keys provided by the os rpm-gpg
        key store under /etc/pki/rpm-gpg.

            Returns True if the signature matches a key.
            Returns False otherwise.
        """
        try:
            data_io = io.BytesIO(release_info_data)
            sig_io = io.BytesIO(release_info_sig)
        except TypeError as exp:
            data_io = io.BytesIO(release_info_data.encode())
            sig_io = io.BytesIO(release_info_sig.encode())

        try:
            key_dir_entries = os.scandir(RPM_GPG_KEY_PATH)
        except FileNotFoundError as exp:
            return False

        # use a temp dir for gpg home to ensure no unexpected keys are loaded
        with tempfile.TemporaryDirectory(prefix='tmp.{}'.format(os.getpid())) as gpg_home_dir:
            gpg_context = gpg.Context()
            gpg_context.home_dir = gpg_home_dir

            for dir_entry in key_dir_entries:
                if not dir_entry.is_file() or not os.access(dir_entry.path, os.R_OK):
                    continue

                gpg_context.key_import(open(dir_entry.path))

            try:
                _, result = gpg_context.verify(data_io, sig_io)
            except gpg.errors.BadSignatures as exp:
                self.logger.error('Unable to verify release info signature.')
                return False

        return True

    def _process_release_info(self, release_info_data):
        """Process xml release info data.
            1. Validate data against accepted schema (TODO)
            2. Translate validated data to dictionary.
        
            Returns dictionary of release info data.
            Returns empty dictionary on failure.
        """

        # assume correctedly formatted xml data; should be validated via schema before now

        try:
            tree = ElementTree.fromstring(release_info_data)
        except ElementTree.ParseError as exp:
            self.logger.error('Unable to parse release info data.')
            return {}

        release_data = {}

        for release_node in tree.findall('./releases/release'):
            release_version = release_node.attrib['version']
            if not release_version in release_data:
                release_data[release_version] = {'update': [], 'upgrade': []}

            for update_child in release_node.findall('./update') + release_node.findall('./upgrade'):
                release_data[release_version][update_child.tag.lower()].append(
                    {
                        'name': update_child.findtext('./name'),
                        'version_string': update_child.findtext('./version_string'),
                        'release_notes': update_child.findtext('./release_notes'),
                    }
                )

        # we pack the release data into another dictionary to leave room to return other types of data in the future
        return {'releases': release_data}

    def retrieve_release_info(self, target_release):
        """Retrieve the OS release metadata from the configured locations.
        """
        if self.release_info_url is None or self.release_info_asc is None:
            raise RuntimeError('Release info location is invalid.') 

        try:
            with self.dnf_base.urlopen(self.release_info_url.format(releasever=target_release)) as fh:
                release_info_data = fh.read()
        except (RuntimeError, OSError) as exp:
            raise RuntimeError(f'Unable to retrieve release info data. {exp.args[0]}')

        if self.gpgcheck:
            try:
                with self.dnf_base.urlopen(self.release_info_asc.format(releasever=target_release)) as fh:
                    release_info_asc = fh.read()
            except (RuntimeError, OSError) as exp:
                raise RuntimeError(f'Unable to retrieve release info signature. {exp.args[0]}')

        if not release_info_data:
            raise RuntimeError('Retrieved release info data was invalid.')

        if self.gpgcheck and not release_info_asc:
            raise RuntimeError('Retrieved release info signature was invalid.')

        if self.gpgcheck and not self._validate_release_info_signature(release_info_data, release_info_asc):
            raise RuntimeError('Unable to validate release info data against the provided signatures.')

        return self._process_release_info(release_info_data)

Youez - 2016 - github.com/yon3zu
LinuXploit