Skip to content

Commit

Permalink
Merge branch 'main' into add-proxmox-support
Browse files Browse the repository at this point in the history
  • Loading branch information
Horofic authored Sep 16, 2024
2 parents 4c98d9b + c5bf1ed commit 7a5bd65
Show file tree
Hide file tree
Showing 61 changed files with 1,636 additions and 361 deletions.
8 changes: 7 additions & 1 deletion dissect/target/filesystems/btrfs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import math
from typing import BinaryIO, Iterator, Optional, Union

import dissect.btrfs as btrfs
Expand Down Expand Up @@ -153,7 +154,7 @@ def lstat(self) -> fsutil.stat_result:
node = self.entry.inode

# mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime
st_info = st_info = fsutil.stat_result(
st_info = fsutil.stat_result(
[
entry.mode,
entry.inum,
Expand All @@ -176,5 +177,10 @@ def lstat(self) -> fsutil.stat_result:

# Btrfs has a birth time, called otime
st_info.st_birthtime = entry.otime.timestamp()
st_info.st_birthtime_ns = entry.otime_ns

# Add block information of the filesystem
st_info.st_blksize = entry.btrfs.sector_size
st_info.st_blocks = math.ceil(entry.size / st_info.st_blksize)

return st_info
13 changes: 13 additions & 0 deletions dissect/target/loaders/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,9 +341,20 @@ def _on_id(self, hostname: str, payload: bytes) -> None:
self.mqtt_client.subscribe(f"{self.case}/{host}/DISKS")
self.mqtt_client.subscribe(f"{self.case}/{host}/READ/#")
if self.command is not None:
self.mqtt_client.subscribe(f"{self.case}/{host}/CALLID")
self.mqtt_client.publish(f"{self.case}/{host}/COMM", self.command.encode("utf-8"))
time.sleep(1)

def _on_call_id(self, hostname: str, payload: bytes) -> None:
try:
decoded_payload = payload.decode("utf-8")
except UnicodeDecodeError as e:
log.error(f"Failed to decode payload for hostname {hostname}: {e}")
return

# The payload with the username and password is comma separated
print(f'"{hostname}",{decoded_payload}')

def _on_log(self, client: mqtt.Client, userdata: Any, log_level: int, message: str) -> None:
log.debug(message)

Expand All @@ -365,6 +376,8 @@ def _on_message(self, client: mqtt.Client, userdata: Any, msg: mqtt.client.MQTTM
self._on_read(hostname, tokens, msg.payload)
elif response == "ID":
self._on_id(hostname, msg.payload)
elif response == "CALLID":
self._on_call_id(hostname, msg.payload)

def seek(self, host: str, disk_id: int, offset: int, flength: int, optimization_strategy: int) -> None:
length = int(flength / self.factor)
Expand Down
13 changes: 10 additions & 3 deletions dissect/target/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def get_nonprivate_attributes(cls: Type[Plugin]) -> list[Any]:

def get_nonprivate_methods(cls: Type[Plugin]) -> list[Callable]:
"""Retrieve all public methods of a :class:`Plugin`."""
return [attr for attr in get_nonprivate_attributes(cls) if not isinstance(attr, property)]
return [attr for attr in get_nonprivate_attributes(cls) if not isinstance(attr, property) and callable(attr)]


def get_descriptors_on_nonprivate_methods(cls: Type[Plugin]) -> list[RecordDescriptor]:
Expand Down Expand Up @@ -1021,7 +1021,7 @@ def __init_subclass_subplugin__(cls, **kwargs):
continue

# The method needs to output records
if getattr(subplugin_func, "__output__", None) != "record":
if getattr(subplugin_func, "__output__", None) not in ["record", "yield"]:
continue

# The method may not be part of a parent class.
Expand Down Expand Up @@ -1107,6 +1107,9 @@ def __init_subclass__(cls, **kwargs):
cls.__init_subclass_namespace__(cls, **kwargs)


__COMMON_PLUGIN_METHOD_NAMES__ = {attr.__name__ for attr in get_nonprivate_methods(Plugin)}


class InternalPlugin(Plugin):
"""Parent class for internal plugins.
Expand All @@ -1116,13 +1119,17 @@ class InternalPlugin(Plugin):

def __init_subclass__(cls, **kwargs):
for method in get_nonprivate_methods(cls):
if callable(method):
if callable(method) and method.__name__ not in __COMMON_PLUGIN_METHOD_NAMES__:
method.__internal__ = True

super().__init_subclass__(**kwargs)
return cls


class InternalNamespacePlugin(NamespacePlugin, InternalPlugin):
pass


@dataclass(frozen=True, eq=True)
class PluginFunction:
name: str
Expand Down
2 changes: 2 additions & 0 deletions dissect/target/plugins/apps/remoteaccess/teamviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ class TeamViewerPlugin(RemoteAccessPlugin):
SYSTEM_GLOBS = [
"sysvol/Program Files/TeamViewer/*.log",
"sysvol/Program Files (x86)/TeamViewer/*.log",
"/var/log/teamviewer*/*.log",
]

USER_GLOBS = [
"AppData/Roaming/TeamViewer/teamviewer*_logfile.log",
"Library/Logs/TeamViewer/teamviewer*_logfile*.log",
]

RemoteAccessLogRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
Expand Down
12 changes: 8 additions & 4 deletions dissect/target/plugins/filesystem/ntfs/mft.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,18 @@ def check_compatible(self) -> None:
FilesystemFilenameCompactRecord,
]
)
@arg("--compact", action="store_true", help="compacts the MFT entry timestamps into a single record")
@arg(
"--compact",
group="fmt",
action="store_true",
help="compacts the MFT entry timestamps into a single record",
)
@arg("--fs", type=int, default=None, help="optional filesystem index, zero indexed")
@arg("--start", type=int, default=0, help="the first MFT segment number")
@arg("--end", type=int, default=-1, help="the last MFT segment number")
@arg(
"--macb",
group="fmt",
action="store_true",
help="compacts the MFT entry timestamps into aggregated records with MACB bitfield",
)
Expand Down Expand Up @@ -171,9 +177,7 @@ def noaggr(records: list[Record]) -> Iterator[Record]:

aggr = noaggr

if compact and macb:
raise ValueError("--macb and --compact are mutually exclusive")
elif compact:
if compact:
record_formatter = compacted_formatter
elif macb:
aggr = macb_aggr
Expand Down
8 changes: 3 additions & 5 deletions dissect/target/plugins/os/unix/bsd/freebsd/_os.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from __future__ import annotations

from typing import Optional

from dissect.target.filesystem import Filesystem
from dissect.target.plugin import export
from dissect.target.plugins.os.unix.bsd._os import BsdPlugin
Expand All @@ -14,13 +12,13 @@ def __init__(self, target: Target):
self._os_release = self._parse_os_release("/bin/freebsd-version*")

@classmethod
def detect(cls, target: Target) -> Optional[Filesystem]:
def detect(cls, target: Target) -> Filesystem | None:
for fs in target.filesystems:
if fs.exists("/net") or fs.exists("/.sujournal"):
if fs.exists("/net") and (fs.exists("/.sujournal") or fs.exists("/entropy")):
return fs

return None

@export(property=True)
def version(self) -> Optional[str]:
def version(self) -> str | None:
return self._os_release.get("USERLAND_VERSION")
27 changes: 14 additions & 13 deletions dissect/target/plugins/os/unix/linux/_os.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import logging
from typing import Optional

from dissect.target.filesystem import Filesystem
from dissect.target.helpers.network_managers import (
Expand All @@ -8,6 +9,8 @@
)
from dissect.target.plugin import OperatingSystem, export
from dissect.target.plugins.os.unix._os import UnixPlugin
from dissect.target.plugins.os.unix.bsd.osx._os import MacPlugin
from dissect.target.plugins.os.windows._os import WindowsPlugin
from dissect.target.target import Target

log = logging.getLogger(__name__)
Expand All @@ -20,17 +23,13 @@ def __init__(self, target: Target):
self.network_manager.discover()

@classmethod
def detect(cls, target: Target) -> Optional[Filesystem]:
def detect(cls, target: Target) -> Filesystem | None:
for fs in target.filesystems:
if (
fs.exists("/var")
and fs.exists("/etc")
and fs.exists("/opt")
or (fs.exists("/sys") or fs.exists("/proc"))
and not fs.exists("/Library")
):
(fs.exists("/var") and fs.exists("/etc") and fs.exists("/opt"))
or (fs.exists("/sys/module") or fs.exists("/proc/sys"))
) and not (MacPlugin.detect(target) or WindowsPlugin.detect(target)):
return fs
return None

@export(property=True)
def ips(self) -> list[str]:
Expand Down Expand Up @@ -68,7 +67,7 @@ def netmask(self) -> list[str]:
return self.network_manager.get_config_value("netmask")

@export(property=True)
def version(self) -> str:
def version(self) -> str | None:
distrib_description = self._os_release.get("DISTRIB_DESCRIPTION", "")
name = self._os_release.get("NAME", "") or self._os_release.get("DISTRIB_ID", "")
version = (
Expand All @@ -77,11 +76,13 @@ def version(self) -> str:
or self._os_release.get("DISTRIB_RELEASE", "")
)

if not any([name, version, distrib_description]):
return None

if len(f"{name} {version}") > len(distrib_description):
return f"{name} {version}"
distrib_description = f"{name} {version}"

else:
return distrib_description
return distrib_description or None

@export(property=True)
def os(self) -> str:
Expand Down
2 changes: 1 addition & 1 deletion dissect/target/plugins/os/unix/linux/redhat/_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from dissect.target.target import Target


class RedHat(LinuxPlugin):
class RedHatPlugin(LinuxPlugin):
def __init__(self, target: Target):
super().__init__(target)

Expand Down
23 changes: 17 additions & 6 deletions dissect/target/plugins/os/unix/locale.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from __future__ import annotations

from pathlib import Path
from typing import Iterator

from dissect.target.helpers.localeutil import normalize_language
from dissect.target.helpers.record import TargetRecordDescriptor
Expand Down Expand Up @@ -30,7 +33,7 @@ def check_compatible(self) -> None:
pass

@export(property=True)
def timezone(self):
def timezone(self) -> str | None:
"""Get the timezone of the system."""

# /etc/timezone should contain a simple timezone string
Expand Down Expand Up @@ -58,15 +61,23 @@ def timezone(self):
size = p_localtime.stat().st_size
sha1 = p_localtime.get().sha1()
for path in self.target.fs.path("/usr/share/zoneinfo").rglob("*"):
# Ignore posix files in zoneinfo directory (RHEL).
if path.name.startswith("posix"):
continue

if path.is_file() and path.stat().st_size == size and path.get().sha1() == sha1:
return timezone_from_path(path)

@export(property=True)
def language(self):
def language(self) -> list[str]:
"""Get the configured locale(s) of the system."""
# Although this purports to be a generic function for Unix targets,
# these paths are Linux specific.
locale_paths = ["/etc/default/locale", "/etc/locale.conf"]

# Although this purports to be a generic function for Unix targets, these paths are Linux specific.
locale_paths = [
"/etc/default/locale",
"/etc/locale.conf",
"/etc/sysconfig/i18n",
]

found_languages = []

Expand All @@ -79,7 +90,7 @@ def language(self):
return found_languages

@export(record=UnixKeyboardRecord)
def keyboard(self):
def keyboard(self) -> Iterator[UnixKeyboardRecord]:
"""Get the keyboard layout(s) of the system."""

paths = ["/etc/default/keyboard", "/etc/vconsole.conf"] + list(
Expand Down
5 changes: 4 additions & 1 deletion dissect/target/plugins/os/windows/adpolicy.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ def _xmltasks(self, policy_dir):
xml = task_file.read_text()
tree = ElementTree.fromstring(xml)
for task in tree.findall(".//{*}Task"):
properties = task.find("Properties") or task
# https://github.com/python/cpython/issues/83122
if (properties := task.find("Properties")) is None:
properties = task

task_data = ElementTree.tostring(task)
yield ADPolicyRecord(
last_modification_time=task_file_stat.st_mtime,
Expand Down
Empty file.
Loading

0 comments on commit 7a5bd65

Please sign in to comment.