summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/live/ogOperations.py35
-rw-r--r--src/utils/inventory.py118
2 files changed, 134 insertions, 19 deletions
diff --git a/src/live/ogOperations.py b/src/live/ogOperations.py
index 6b1b01f..0e31cea 100644
--- a/src/live/ogOperations.py
+++ b/src/live/ogOperations.py
@@ -30,6 +30,7 @@ from src.utils.probe import os_probe, cache_probe
from src.utils.disk import *
from src.utils.cache import generate_cache_txt, umount_cache, init_cache
from src.utils.tiptorrent import *
+from src.utils.inventory import get_package_set
OG_SHELL = '/bin/bash'
@@ -252,30 +253,26 @@ class OgLiveOperations:
def software(self, request, path, ogRest):
disk = request.getDisk()
partition = request.getPartition()
+ partdev = get_partition_device(int(disk), int(partition))
+ mountpoint = partdev.replace('dev', 'mnt')
+ if not mount_mkdir(partdev, mountpoint):
+ raise RuntimeError(f'Error mounting {partdev} at {mountpoint}')
+ if not os.path.ismount(mountpoint):
+ raise RuntimeError('Invalid mountpoint for software inventory')
self._restartBrowser(self._url_log)
-
- try:
- cmd = f'{ogClient.OG_PATH}interfaceAdm/InventarioSoftware {disk} ' \
- f'{partition} {path}'
-
- ogRest.proc = subprocess.Popen([cmd],
- stdout=subprocess.PIPE,
- shell=True,
- executable=OG_SHELL)
- (output, error) = ogRest.proc.communicate()
- except:
- logging.error('Exception when running software inventory subprocess')
- raise ValueError('Error: Incorrect command value')
-
+ pkgset = get_package_set(mountpoint)
self._restartBrowser(self._url)
- software = ''
- with open(path, 'r') as f:
- software = f.read()
-
+ umount(mountpoint)
logging.info('Software inventory command OK')
- return software
+
+ # Software inventory result is still processed by legacy server code
+ # (ogAdmServer.c). Legacy response format is string where each
+ # software package is separated by a newline '\n'.
+ # Each package/software line follows this format:
+ # "{package_name} {package_version}"
+ return '\n'.join(map(str,pkgset))
def hardware(self, path, ogRest):
self._restartBrowser(self._url_log)
diff --git a/src/utils/inventory.py b/src/utils/inventory.py
new file mode 100644
index 0000000..7f02c31
--- /dev/null
+++ b/src/utils/inventory.py
@@ -0,0 +1,118 @@
+import platform
+import re
+import os
+
+from collections import namedtuple
+
+import hivex
+
+from src.utils.probe import getwindowsversion, getlinuxversion
+
+
+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:
+ 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)
+
+
+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()
+ h = hivex.Hivex(hivepath)
+ _fill_package_set_1(h, packages)
+ _fill_package_set_2(h, packages)
+ 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}'
+ winreghives = f'{mountpoint}{WINDOWS_HIVES_PATH}'
+ osrelease = f'{mountpoint}{OSRELEASE_PATH}'
+ softwarehive = f'{mountpoint}{WINDOWS_HIVES_SOFTWARE}'
+ if os.path.exists(softwarehive):
+ pkgset = _get_package_set_windows(softwarehive)
+ osname = getwindowsversion(winreghives)
+ elif os.path.exists(dpkg_status_path):
+ pkgset = _get_package_set_dpkg(dpkg_status_path)
+ osname = getlinuxversion(osrelease)
+ else:
+ raise ValueError(f'Cannot fetch software inventory at {mountpoint}')
+ # Legacy software inventory first element is the OS name
+ return [osname] + list(pkgset)