mirror of https://git.48k.eu/ogclient
132 lines
4.3 KiB
Python
132 lines
4.3 KiB
Python
#
|
|
# Copyright (C) 2023 Soleta Networks <info@soleta.eu>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it under
|
|
# the terms of the GNU Affero General Public License as published by the
|
|
# Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
|
|
import platform
|
|
import re
|
|
import os
|
|
import logging
|
|
|
|
from collections import namedtuple
|
|
|
|
import hivex
|
|
|
|
from src.utils.probe import os_probe
|
|
|
|
|
|
Package = namedtuple('Package', ['name', 'version'])
|
|
Package.__str__ = lambda pkg: f'{pkg.name} {pkg.version}'
|
|
|
|
WINDOWS_HIVES_PATH = '/Windows/System32/config'
|
|
WINDOWS_HIVES_SOFTWARE = f'{WINDOWS_HIVES_PATH}/SOFTWARE'
|
|
DPKG_STATUS_PATH = '/var/lib/dpkg/status'
|
|
OSRELEASE_PATH = '/etc/os-release'
|
|
|
|
|
|
def _fill_package_set(h, key, pkg_set):
|
|
"""
|
|
Fill the package set looking for entries at the current registry
|
|
node childs.
|
|
|
|
Any valid node child must have "DisplayVersion" or "DisplayName" keys.
|
|
"""
|
|
childs = h.node_children(key)
|
|
valid_childs = [h.node_get_child(key, h.node_name(child))
|
|
for child in childs
|
|
for value in h.node_values(child) if h.value_key(value) == 'DisplayVersion']
|
|
for ch in valid_childs:
|
|
try:
|
|
name = h.value_string(h.node_get_value(ch, 'DisplayName'))
|
|
value = h.node_get_value(ch, 'DisplayVersion')
|
|
version = h.value_string(value)
|
|
pkg = Package(name, version)
|
|
pkg_set.add(pkg)
|
|
except RuntimeError:
|
|
logging.warning('Unable to fill package set with invalid child')
|
|
pass
|
|
|
|
|
|
def _fill_package_set_1(h, pkg_set):
|
|
"""
|
|
Looks for entries in registry path
|
|
/Microsoft/Windows/CurrentVersion/Uninstall
|
|
|
|
Fills the given set with Package instances for each program found.
|
|
"""
|
|
key = h.root()
|
|
key = h.node_get_child(key, 'Microsoft')
|
|
key = h.node_get_child(key, 'Windows')
|
|
key = h.node_get_child(key, 'CurrentVersion')
|
|
key = h.node_get_child(key, 'Uninstall')
|
|
_fill_package_set(h, key, pkg_set)
|
|
|
|
|
|
def _fill_package_set_2(h, pkg_set):
|
|
"""
|
|
Looks for entries in registry path
|
|
/Wow6432Node/Microsoft/Windows/CurrentVersion/Uninstall
|
|
64 bit Windows only.
|
|
|
|
Fills the given set with Package instances for each program found.
|
|
"""
|
|
key = h.root()
|
|
key = h.node_get_child(key, 'Wow6432Node')
|
|
key = h.node_get_child(key, 'Microsoft')
|
|
key = h.node_get_child(key, 'Windows')
|
|
key = h.node_get_child(key, 'CurrentVersion')
|
|
key = h.node_get_child(key, 'Uninstall')
|
|
_fill_package_set(h, key, pkg_set)
|
|
|
|
|
|
def _get_package_set_windows(hivepath):
|
|
packages = set()
|
|
try:
|
|
h = hivex.Hivex(hivepath)
|
|
_fill_package_set_1(h, packages)
|
|
_fill_package_set_2(h, packages)
|
|
except RuntimeError as e:
|
|
logging.error(f'Hivex was not able to operate over {hivepath}. Reported: {e}')
|
|
return packages
|
|
|
|
|
|
def _get_package_set_dpkg(dpkg_status_path):
|
|
regex_pkg = '(?:^Package: )(?P<name>.*)\n(Essential:.*\n)?(?:Status: install ok installed)'
|
|
regex_ver = '(?:^Version: )(?P<version>.*)'
|
|
packages = set()
|
|
with open(dpkg_status_path, 'r') as f:
|
|
# Split by empty line
|
|
for par in re.split('^\n+', f.read(), flags=re.MULTILINE):
|
|
# Search for package with "Status: install ok installed"
|
|
result = re.search(regex_pkg, par)
|
|
if result is None:
|
|
continue
|
|
else:
|
|
pkg_name = result.group('name')
|
|
# If we hit a properly installed package, search for its version
|
|
result = re.search(regex_ver, par, flags=re.MULTILINE)
|
|
if result is None:
|
|
continue
|
|
else:
|
|
pkg_version = result.group('version')
|
|
pkg = Package(pkg_name, pkg_version)
|
|
packages.add(pkg)
|
|
return packages
|
|
|
|
|
|
def get_package_set(mountpoint):
|
|
dpkg_status_path = f'{mountpoint}{DPKG_STATUS_PATH}'
|
|
softwarehive = f'{mountpoint}{WINDOWS_HIVES_SOFTWARE}'
|
|
if os.path.exists(softwarehive):
|
|
pkgset = _get_package_set_windows(softwarehive)
|
|
elif os.path.exists(dpkg_status_path):
|
|
pkgset = _get_package_set_dpkg(dpkg_status_path)
|
|
else:
|
|
pkgset = set()
|
|
osname = os_probe(mountpoint)
|
|
# Legacy software inventory first element is the OS name
|
|
return [osname] + list(pkgset)
|