Skip to content
20 changes: 19 additions & 1 deletion dissect/target/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,18 +459,36 @@ def __call__(self, *args, **kwargs) -> Iterator[Record | Any]:
self.target.log.debug("", exc_info=e)

def get_paths(self) -> Iterator[Path]:
"""Return all artifact paths."""
if self.target.is_direct:
yield from self._get_paths_direct()
else:
yield from self._get_paths()

def get_all_paths(self) -> Iterator[Path]:
"""Return all artifact and auxiliary paths.

The implementation of this function will
probably change in the future, but the interface
should stay the same.
"""
yield from self.get_paths()
yield from self._get_auxiliary_paths()

def _get_paths_direct(self) -> Iterator[Path]:
"""Return all paths as given by the user."""
for path in self.target._loader.paths:
yield self.target.fs.path(str(path))

def _get_paths(self) -> Iterator[Path]:
"""Return all files of interest to the plugin.
"""Return all artifact files of interest to the plugin.

To be implemented by the plugin subclass.
"""
raise NotImplementedError

def _get_auxiliary_paths(self) -> Iterator[Path]:
"""Return all auxiliary files of interest to the plugin.

To be implemented by the plugin subclass.
"""
Expand Down
18 changes: 16 additions & 2 deletions dissect/target/plugins/apps/webserver/apache.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import re
from datetime import datetime
from functools import cached_property
from pathlib import Path
from typing import TYPE_CHECKING, NamedTuple

from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
Expand All @@ -18,7 +19,6 @@

if TYPE_CHECKING:
from collections.abc import Iterator
from pathlib import Path

from dissect.target.target import Target

Expand Down Expand Up @@ -240,6 +240,7 @@ def __init__(self, target: Target):
self.access_paths = set()
self.error_paths = set()
self.virtual_hosts = set()
self.resolved_config_paths = set()
self.find_logs()

def check_compatible(self) -> None:
Expand All @@ -251,7 +252,8 @@ def check_compatible(self) -> None:

def find_logs(self) -> None:
"""Discover any present Apache log paths on the target system.
Populates ``self.access_paths``, ``self.error_paths`` and ``self.virtual_hosts``.
Populates ``self.access_paths``, ``self.error_paths``,
``self.virtual_hosts`` and ``self.resolved_config_paths``.

References:
- https://httpd.apache.org/docs/2.4/logs.html
Expand Down Expand Up @@ -280,6 +282,18 @@ def find_logs(self) -> None:
if path not in seen:
self._process_conf_file(path, seen)

def _get_paths(self) -> Iterator[Path]:
yield from self.access_paths | self.error_paths

def _get_auxiliary_paths(self) -> Iterator[Path]:
config_paths = set()
for path in self.DEFAULT_CONFIG_PATHS:
config_paths.add(Path(path))

config_paths.update(self.resolved_config_paths)

yield from config_paths

def _process_conf_file(self, path: Path, seen: set[Path] | None = None) -> None:
"""Process an Apache ``.conf`` file for ``ServerRoot``, ``CustomLog``, ``Include``
and ``OptionalInclude`` directives. Populates ``self.access_paths`` and ``self.error_paths``.
Expand Down
12 changes: 10 additions & 2 deletions dissect/target/plugins/apps/webserver/caddy.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import json
import re
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING

from dissect.util.ts import from_unix
Expand All @@ -17,7 +18,6 @@

if TYPE_CHECKING:
from collections.abc import Iterator
from pathlib import Path

from dissect.target.target import Target

Expand All @@ -32,6 +32,8 @@ class CaddyPlugin(WebserverPlugin):

__namespace__ = "caddy"

DEFAULT_CONFIG_PATH = "/etc/caddy/Caddyfile"

def __init__(self, target: Target):
super().__init__(target)
self.log_paths = self.get_log_paths()
Expand All @@ -47,7 +49,7 @@ def get_log_paths(self) -> list[Path]:
log_paths.extend(self.target.fs.path("/var/log").glob("caddy_access.log*"))

# Check for custom paths in Caddy config
if (config_file := self.target.fs.path("/etc/caddy/Caddyfile")).exists():
if (config_file := self.target.fs.path(self.DEFAULT_CONFIG_PATH)).exists():
found_roots = []
for line in config_file.open("rt"):
line = line.strip()
Expand Down Expand Up @@ -95,6 +97,12 @@ def get_log_paths(self) -> list[Path]:

return log_paths

def _get_paths(self) -> Iterator[Path]:
yield from self.log_paths

def _get_auxiliary_paths(self) -> Iterator[Path]:
yield from {Path(self.DEFAULT_CONFIG_PATH)}

@export(record=WebserverAccessLogRecord)
def access(self) -> Iterator[WebserverAccessLogRecord]:
"""Parses Caddy V1 CRF and Caddy V2 JSON access logs.
Expand Down
10 changes: 8 additions & 2 deletions dissect/target/plugins/apps/webserver/iis.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ def log_dirs(self) -> dict[str, set[Path]]:

return dirs

def _get_paths(self) -> Iterator[Path]:
for path in self.log_dirs.values():
yield from path

def _get_auxiliary_paths(self) -> Iterator[Path]:
yield from {self.config}

@export(record=BasicRecordDescriptor)
def logs(self) -> Iterator[TargetRecordDescriptor]:
"""Return contents of IIS (v7 and above) log files.
Expand All @@ -147,8 +154,7 @@ def logs(self) -> Iterator[TargetRecordDescriptor]:
self.target.log.info("Processing IIS log file %s in %s format", log_file, format)
yield from parsers[format](self.target, log_file)

# We don't implement _get_paths() in the IIS plugin because there's little use for it for the way the plugin
# is currently implemented. So handle direct files here.
# We handle direct files here because _get_paths cannot select (filter) on the type of logfile.
if self.target.is_direct:
for log_file in self.get_paths():
yield from parse_autodetect_format_log(self.target, log_file)
Expand Down
9 changes: 9 additions & 0 deletions dissect/target/plugins/apps/webserver/nginx.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def __init__(self, target: Target):
self.access_paths = set()
self.error_paths = set()
self.host_paths = set()
self.config_paths = set()

self.find_logs()

Expand All @@ -127,11 +128,19 @@ def find_logs(self) -> None:
if "*" in config_file:
base, _, glob = config_file.partition("*")
for f in self.target.fs.path(base).rglob(f"*{glob}"):
self.config_paths.add(f)
self.parse_config(f)

elif (config_file := self.target.fs.path(config_file)).exists():
self.config_paths.add(config_file)
self.parse_config(config_file)

def _get_paths(self) -> Iterator[Path]:
yield from self.access_paths | self.error_paths

def _get_auxiliary_paths(self) -> Iterator[Path]:
yield from self.config_paths

def parse_config(self, path: Path, seen: set[Path] | None = None) -> None:
"""Parse the given NGINX ``.conf`` file for ``access_log``, ``error_log`` and ``include`` directives."""

Expand Down
Loading