Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 42 additions & 37 deletions dissect/target/plugins/os/unix/_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import re
import uuid
from pathlib import Path
from typing import Iterator
from typing import Callable, Iterator

from flow.record.fieldtypes import posix_path

Expand Down Expand Up @@ -165,46 +165,55 @@ def domain(self) -> str | None:
def os(self) -> str:
return OperatingSystem.UNIX.value

def _parse_rh_legacy(self, path: Path) -> str | None:
hostname = None
file_contents = path.open("rt").readlines()
for line in file_contents:
if not line.startswith("HOSTNAME"):
continue
_, _, hostname = line.rstrip().partition("=")
return hostname

def _parse_hostname_string(self, paths: list[str] | None = None) -> dict[str, str] | None:
def _parse_hostname_string(self, paths: list[tuple[str, Callable[[Path], str] | None]] | None = None) -> dict[str, str] | None:
"""Returns a dict containing the hostname and domain name portion of the path(s) specified.

Args:
paths (list): list of paths
"""
redhat_legacy_path = "/etc/sysconfig/network"
paths = paths or ["/etc/hostname", "/etc/HOSTNAME", "/proc/sys/kernel/hostname", redhat_legacy_path]
hostname_dict = {"hostname": None, "domain": None}
paths (list): list of tuples with paths and callables to parse the path or None

for path in paths:
path = self.target.fs.path(path)

if not path.exists():
Returns:
Dictionary with ``hostname`` and ``domain`` keys.
"""
hostname = None
domain = None

paths = paths or [
("/etc/hostname", None),
("/etc/HOSTNAME", None),
("/proc/sys/kernel/hostname", None),
("/etc/sysconfig/network", self._parse_rh_legacy),
("/etc/hosts", self._parse_etc_hosts), # fallback if no other hostnames are found
]

for path, callable in paths:
if not (path := self.target.fs.path(path)).exists():
continue

if path.as_posix() == redhat_legacy_path:
hostname_string = self._parse_rh_legacy(path)
if callable:
hostname = callable(path)
else:
hostname_string = path.open("rt").read().rstrip()
hostname = path.open("rt").read().rstrip()

if hostname and "." in hostname:
hostname, domain = hostname.split(".", maxsplit=1)

if hostname_string and "." in hostname_string:
hostname_string = hostname_string.split(".", maxsplit=1)
hostname_dict = {"hostname": hostname_string[0], "domain": hostname_string[1]}
elif hostname_string != "":
hostname_dict = {"hostname": hostname_string, "domain": None}
else:
hostname_dict = {"hostname": None, "domain": None}
break # break whenever a valid hostname is found

return hostname_dict
return {"hostname": hostname if hostname else None, "domain": domain if domain else None}

def _parse_rh_legacy(self, path: Path) -> str | None:
hostname = None
file_contents = path.open("rt").readlines()
for line in file_contents:
if not line.startswith("HOSTNAME"):
continue
_, _, hostname = line.rstrip().partition("=")
return hostname

def _parse_etc_hosts(self, path: Path) -> str | None:
for line in path.open("rt"):
if line.startswith(("127.0.0.1 ", "::1 ")) and "localhost" not in line:
return line.split(" ")[1]

def _parse_hosts_string(self, paths: list[str] | None = None) -> dict[str, str]:
paths = paths or ["/etc/hosts"]
Expand All @@ -214,13 +223,9 @@ def _parse_hosts_string(self, paths: list[str] | None = None) -> dict[str, str]:
for fs in self.target.filesystems:
if fs.exists(path):
for line in fs.path(path).open("rt").readlines():
line = line.split()
if not line:
if not (line := line.split()):
continue

if (line[0].startswith("127.0.") or line[0].startswith("::1")) and line[
1
].lower() != "localhost":
if line[0].startswith(("127.0.", "::1")) and line[1].lower() != "localhost":
hosts_string = {"ip": line[0], "hostname": line[1]}
return hosts_string

Expand Down
14 changes: 10 additions & 4 deletions dissect/target/plugins/os/unix/bsd/freebsd/_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@ def __init__(self, target: Target):

@classmethod
def detect(cls, target: Target) -> Filesystem | None:
for fs in target.filesystems:
if fs.exists("/net") and (fs.exists("/.sujournal") or fs.exists("/entropy")):
return fs
FREEBSD_PATHS = {
"/net",
"/.sujournal",
"/entropy",
"/bin/freebsd-version",
}

return None
for fs in target.filesystems:
for path in FREEBSD_PATHS:
if fs.exists(path):
return fs

@export(property=True)
def version(self) -> str | None:
Expand Down
23 changes: 13 additions & 10 deletions dissect/target/plugins/os/unix/bsd/openbsd/_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 @@ -11,20 +9,25 @@
class OpenBsdPlugin(BsdPlugin):
def __init__(self, target: Target):
super().__init__(target)
self._hostname_dict = self._parse_hostname_string(["/etc/myname"])
self._hostname_dict = self._parse_hostname_string([("/etc/myname", None)])

@classmethod
def detect(cls, target: Target) -> Optional[Filesystem]:
for fs in target.filesystems:
if fs.exists("/bsd") or fs.exists("/bsd.rd") or fs.exists("/bsd.mp") or fs.exists("/bsd.mp"):
return fs
def detect(cls, target: Target) -> Filesystem | None:
BSD_PATHS = {
"/bsd",
"/bsd.rd",
"/bsd.mp",
}

return None
for fs in target.filesystems:
for path in BSD_PATHS:
if fs.exists(path):
return fs

@export(property=True)
def version(self) -> Optional[str]:
def version(self) -> str | None:
return None

@export(property=True)
def hostname(self) -> Optional[str]:
def hostname(self) -> str | None:
return self._hostname_dict.get("hostname", None)
21 changes: 14 additions & 7 deletions dissect/target/plugins/os/unix/linux/redhat/_os.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
from typing import Optional
from __future__ import annotations

from dissect.target.filesystem import Filesystem
from dissect.target.plugins.os.unix.linux._os import LinuxPlugin
from dissect.target.target import Target


class RedHatPlugin(LinuxPlugin):
"""RedHat, CentOS and Fedora Plugin."""

def __init__(self, target: Target):
super().__init__(target)

@classmethod
def detect(cls, target: Target) -> Optional[Filesystem]:
# also applicable to centos (which is a red hat derivative)
for fs in target.filesystems:
if fs.exists("/etc/sysconfig/network-scripts"):
return fs
def detect(cls, target: Target) -> Filesystem | None:
REDHAT_PATHS = {
"/etc/centos-release",
"/etc/fedora-release",
"/etc/redhat-release",
"/etc/sysconfig/network-scripts", # legacy detection
}

return None
for fs in target.filesystems:
for path in REDHAT_PATHS:
if fs.exists(path):
return fs
9 changes: 6 additions & 3 deletions dissect/target/plugins/os/unix/locale.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,18 @@ def language(self) -> list[str]:
"/etc/sysconfig/i18n",
]

found_languages = []
found_languages = set()

for locale_path in locale_paths:
if (path := self.target.fs.path(locale_path)).exists():
for line in path.open("rt"):
if "LANG=" in line:
found_languages.append(normalize_language(line.replace("LANG=", "").strip().strip('"')))
lang_str = line.partition("=")[-1].strip().strip('"')
if lang_str == "C.UTF-8":
continue
found_languages.add(normalize_language(lang_str))

return found_languages
return list(found_languages)

@export(record=UnixKeyboardRecord)
def keyboard(self) -> Iterator[UnixKeyboardRecord]:
Expand Down
Empty file.
4 changes: 2 additions & 2 deletions tests/plugins/os/unix/test__os.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ def test_parse_hostname_string(
target_unix: Target,
fs_unix: VirtualFilesystem,
path: Path,
expected_hostname: str,
expected_domain: str,
expected_hostname: str | None,
expected_domain: str | None,
file_content: str,
) -> None:
fs_unix.map_file_fh(path, BytesIO(file_content))
Expand Down