dissect.target 3.20.dev26__py3-none-any.whl → 3.20.dev28__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -247,10 +247,14 @@ class ConfigurationEntry(FilesystemEntry):
247
247
  Returns:
248
248
  A file-like object holding a byte representation of :attr:`parser_items`.
249
249
  """
250
+
250
251
  if isinstance(self.parser_items, ConfigurationParser):
251
252
  # Currently trying to open the underlying entry
252
253
  return self.entry.open()
253
254
 
255
+ if isinstance(self.parser_items, bytes):
256
+ return io.BytesIO(self.parser_items)
257
+
254
258
  output_data = self._write_value_mapping(self.parser_items)
255
259
  return io.BytesIO(bytes(output_data, "utf-8"))
256
260
 
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import io
4
4
  import json
5
+ import logging
5
6
  import re
6
7
  import sys
7
8
  from collections import deque
@@ -47,6 +48,9 @@ except ImportError:
47
48
  HAS_TOML = False
48
49
 
49
50
 
51
+ log = logging.getLogger(__name__)
52
+
53
+
50
54
  def _update_dictionary(current: dict[str, Any], key: str, value: Any) -> None:
51
55
  if prev_value := current.get(key):
52
56
  if isinstance(prev_value, dict):
@@ -161,7 +165,7 @@ class ConfigurationParser:
161
165
  def get(self, item: str, default: Optional[Any] = None) -> Any:
162
166
  return self.parsed_data.get(item, default)
163
167
 
164
- def read_file(self, fh: TextIO) -> None:
168
+ def read_file(self, fh: TextIO | io.BytesIO) -> None:
165
169
  """Parse a configuration file.
166
170
 
167
171
  Raises:
@@ -303,6 +307,14 @@ class Txt(ConfigurationParser):
303
307
  self.parsed_data = {"content": fh.read(), "size": str(fh.tell())}
304
308
 
305
309
 
310
+ class Bin(ConfigurationParser):
311
+
312
+ """Read the file into ``binary`` and show the number of bytes read"""
313
+
314
+ def parse_file(self, fh: io.BytesIO) -> None:
315
+ self.parsed_data = {"binary": fh.read(), "size": str(fh.tell())}
316
+
317
+
306
318
  class Xml(ConfigurationParser):
307
319
  """Parses an XML file. Ignores any constructor parameters passed from ``ConfigurationParser`."""
308
320
 
@@ -457,6 +469,76 @@ class Toml(ConfigurationParser):
457
469
  raise ConfigurationParsingError("Failed to parse file, please install tomli.")
458
470
 
459
471
 
472
+ class Env(ConfigurationParser):
473
+ """Parses ``.env`` file contents according to Docker and bash specification.
474
+
475
+ Does not apply interpolation of substituted values, eg. ``foo=${bar}`` and does not attempt
476
+ to parse list or dict strings. Does not support dynamic env files, eg. `` foo=`bar` ``. Also
477
+ does not support multi-line key/value assignments (yet).
478
+
479
+ Resources:
480
+ - https://docs.docker.com/compose/environment-variables/variable-interpolation/#env-file-syntax
481
+ - https://github.com/theskumar/python-dotenv/blob/main/src/dotenv/parser.py
482
+ """
483
+
484
+ RE_KV = re.compile(r"^(?P<key>.+?)=(?P<value>(\".+?\")|(\'.+?\')|(.*?))?(?P<comment> \#.+?)?$")
485
+
486
+ def __init__(self, comments: bool = True, *args, **kwargs) -> None:
487
+ super().__init__(*args, **kwargs)
488
+ self.comments = comments
489
+ self.parsed_data: dict | tuple[dict, str | None] = {}
490
+
491
+ def parse_file(self, fh: TextIO) -> None:
492
+ for line in fh.readlines():
493
+ # Blank lines are ignored.
494
+ # Lines beginning with ``#`` are processed as comments and ignored.
495
+ if not line or line[0] == "#" or "=" not in line:
496
+ continue
497
+
498
+ # Each line represents a key-value pair. Values can optionally be quoted.
499
+ # Inline comments for unquoted values must be preceded with a space.
500
+ # Value may be empty.
501
+ match = self.RE_KV.match(line)
502
+
503
+ # Line could be invalid
504
+ if not match:
505
+ log.warning("Could not parse line in %s: '%s'", fh, line)
506
+ continue
507
+
508
+ key = match.groupdict()["key"]
509
+ value = match.groupdict().get("value") or ""
510
+ value = value.strip()
511
+ comment = match.groupdict().get("comment")
512
+ comment = comment.replace(" # ", "", 1) if comment else None
513
+
514
+ # Surrounding whitespace characters are removed, unless quoted.
515
+ if value and ((value[0] == '"' and value[-1] == '"') or (value[0] == "'" and value[-1] == "'")):
516
+ is_quoted = True
517
+ value = value.strip("\"'")
518
+ else:
519
+ is_quoted = False
520
+ value = value.strip()
521
+
522
+ # Unquoted values may start with a quote if they are properly escaped.
523
+ if not is_quoted and value[:2] in ["\\'", '\\"']:
524
+ value = value[1:]
525
+
526
+ # Interpret boolean values
527
+ if value.lower() in ["1", "true"]:
528
+ value = True
529
+ elif value.lower() in ["0", "false"]:
530
+ value = False
531
+
532
+ # Interpret integer values
533
+ if isinstance(value, str) and re.match(r"^[0-9]{1,}$", value):
534
+ value = int(value)
535
+
536
+ if key.strip() in self.parsed_data:
537
+ log.warning("Duplicate environment key '%s' in file %s", key.strip(), fh)
538
+
539
+ self.parsed_data[key.strip()] = (value, comment) if self.comments else value
540
+
541
+
460
542
  class ScopeManager:
461
543
  """A (context)manager for dictionary scoping.
462
544
 
@@ -733,6 +815,8 @@ MATCH_MAP: dict[str, ParserConfig] = {
733
815
  "*/sysconfig/network-scripts/ifcfg-*": ParserConfig(Default),
734
816
  "*/sysctl.d/*.conf": ParserConfig(Default),
735
817
  "*/xml/*": ParserConfig(Xml),
818
+ "*.bashrc": ParserConfig(Txt),
819
+ "*/vim/vimrc*": ParserConfig(Txt),
736
820
  }
737
821
 
738
822
  CONFIG_MAP: dict[tuple[str, ...], ParserConfig] = {
@@ -744,6 +828,13 @@ CONFIG_MAP: dict[tuple[str, ...], ParserConfig] = {
744
828
  "cnf": ParserConfig(Default),
745
829
  "conf": ParserConfig(Default, separator=(r"\s",)),
746
830
  "sample": ParserConfig(Txt),
831
+ "sh": ParserConfig(Txt),
832
+ "key": ParserConfig(Txt),
833
+ "crt": ParserConfig(Txt),
834
+ "pem": ParserConfig(Txt),
835
+ "pl": ParserConfig(Txt), # various admin panels
836
+ "lua": ParserConfig(Txt), # wireshark etc.
837
+ "txt": ParserConfig(Txt),
747
838
  "systemd": ParserConfig(SystemD),
748
839
  "template": ParserConfig(Txt),
749
840
  "toml": ParserConfig(Toml),
@@ -759,6 +850,7 @@ KNOWN_FILES: dict[str, type[ConfigurationParser]] = {
759
850
  "nsswitch.conf": ParserConfig(Default, separator=(":",)),
760
851
  "lsb-release": ParserConfig(Default),
761
852
  "catalog": ParserConfig(Xml),
853
+ "ld.so.cache": ParserConfig(Bin),
762
854
  "fstab": ParserConfig(
763
855
  CSVish,
764
856
  separator=(r"\s",),
@@ -832,9 +924,11 @@ def parse_config(
832
924
  parser_type = _select_parser(entry, hint)
833
925
 
834
926
  parser = parser_type.create_parser(options)
835
-
836
927
  with entry.open() as fh:
837
- open_file = io.TextIOWrapper(fh, encoding="utf-8")
928
+ if not isinstance(parser, Bin):
929
+ open_file = io.TextIOWrapper(fh, encoding="utf-8")
930
+ else:
931
+ open_file = io.BytesIO(fh.read())
838
932
  parser.read_file(open_file)
839
933
 
840
934
  return parser
@@ -0,0 +1,56 @@
1
+ from typing import Iterator
2
+
3
+ from dissect.target.helpers.configutil import Env
4
+ from dissect.target.helpers.record import TargetRecordDescriptor
5
+ from dissect.target.plugin import Plugin, arg, export
6
+
7
+ EnvironmentFileRecord = TargetRecordDescriptor(
8
+ "application/other/file/environment",
9
+ [
10
+ ("datetime", "ts_mtime"),
11
+ ("string", "key"),
12
+ ("string", "value"),
13
+ ("string", "comment"),
14
+ ("path", "path"),
15
+ ],
16
+ )
17
+
18
+
19
+ class EnvironmentFilePlugin(Plugin):
20
+ """Environment file plugin."""
21
+
22
+ def check_compatible(self) -> None:
23
+ # `--env-path` is provided at runtime
24
+ pass
25
+
26
+ @export(record=EnvironmentFileRecord)
27
+ @arg("--env-path", help="path to scan environment files in")
28
+ @arg("--extension", help="extension of files to scan", default="env")
29
+ def envfile(self, env_path: str, extension: str = "env") -> Iterator[EnvironmentFileRecord]:
30
+ """Yield environment variables found in ``.env`` files at the provided path."""
31
+
32
+ if not env_path:
33
+ self.target.log.error("No ``--path`` provided!")
34
+
35
+ if not (path := self.target.fs.path(env_path)).exists():
36
+ self.target.log.error("Provided path %s does not exist!", path)
37
+
38
+ for file in path.glob("**/*." + extension):
39
+ if not file.is_file():
40
+ continue
41
+
42
+ mtime = file.lstat().st_mtime
43
+
44
+ with file.open("r") as fh:
45
+ parser = Env(comments=True)
46
+ parser.read_file(fh)
47
+
48
+ for key, (value, comment) in parser.parsed_data.items():
49
+ yield EnvironmentFileRecord(
50
+ ts_mtime=mtime,
51
+ key=key,
52
+ value=value,
53
+ comment=comment,
54
+ path=file,
55
+ _target=self.target,
56
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.20.dev26
3
+ Version: 3.20.dev28
4
4
  Summary: This module ties all other Dissect modules together, it provides a programming API and command line tools which allow easy access to various data sources inside disk images or file collections (a.k.a. targets)
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -25,7 +25,7 @@ dissect/target/filesystems/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
25
25
  dissect/target/filesystems/ad1.py,sha256=nEPzaaRsb6bL4ItFo0uLdmdLvrmK9BjqHeD3FOp3WQI,2413
26
26
  dissect/target/filesystems/btrfs.py,sha256=TotOs0-VOmgSijERZb1pOrIH_E7B1J_DRKqws8ttQTk,6569
27
27
  dissect/target/filesystems/cb.py,sha256=6LcoJiwsYu1Han31IUzVpZVDTifhTLTx_gLfNpB_p6k,5329
28
- dissect/target/filesystems/config.py,sha256=GQOtixIIt-Jjtpl3IVqUTujcBFfWaAZeAtvxgNUNetU,11963
28
+ dissect/target/filesystems/config.py,sha256=5ZJfxs1Cidjxr7nKH2_iGKNWFd5SeLORRWkL4oYcZnk,12063
29
29
  dissect/target/filesystems/cpio.py,sha256=ssVCjkAtLn2FqmNxeo6U5boyUdSYFxLWfXpytHYGPqs,641
30
30
  dissect/target/filesystems/dir.py,sha256=rKEreX3A7CI6a3pMssrO9F-9i5pkxCn_Ucs_dMtHxxA,4574
31
31
  dissect/target/filesystems/exfat.py,sha256=PRkZPUVN5NlgB1VetFtywdNgF6Yj5OBtF5a25t-fFvw,5917
@@ -46,7 +46,7 @@ dissect/target/filesystems/zip.py,sha256=BeNj23DOYfWuTm5V1V419ViJiMfBrO1VA5gP6rl
46
46
  dissect/target/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  dissect/target/helpers/cache.py,sha256=TXlJBdFRz6V9zKs903am4Yawr0maYw5kZY0RqklDQJM,8568
48
48
  dissect/target/helpers/config.py,sha256=RMHnIuKJHINHiLrvKN3EyA0jFA1o6-pbeaycG8Pgrp8,2596
49
- dissect/target/helpers/configutil.py,sha256=AEnkMQ0e6PncvCqGa-ACzBQWQBhMGBCzO5qzGJtRu60,27644
49
+ dissect/target/helpers/configutil.py,sha256=mO2XwhzLhGjFQzg_zC8SNi24CQhye1fhkYlH5Q5HFm8,31365
50
50
  dissect/target/helpers/cyber.py,sha256=WnJlk-HqAETmDAgLq92JPxyDLxvzSoFV_WrO-odVKBI,16805
51
51
  dissect/target/helpers/descriptor_extensions.py,sha256=uT8GwznfDAiIgMM7JKKOY0PXKMv2c0GCqJTCkWFgops,2605
52
52
  dissect/target/helpers/docs.py,sha256=J5U65Y3yOTqxDEZRCdrEmO63XQCeDzOJea1PwPM6Cyc,5146
@@ -127,6 +127,7 @@ dissect/target/plugins/apps/browser/firefox.py,sha256=mZBBagFfIdiz9kUyK4Hi989I4g
127
127
  dissect/target/plugins/apps/browser/iexplore.py,sha256=g_xw0toaiyjevxO8g9XPCOqc-CXZp39FVquRhPFGdTE,8801
128
128
  dissect/target/plugins/apps/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
129
129
  dissect/target/plugins/apps/container/docker.py,sha256=LTsZplaECSfO1Ysp_Y-9WsnNocsreu_iHO8fbSif3g0,16221
130
+ dissect/target/plugins/apps/other/env.py,sha256=_I12S_wjyT18WlUJ5cWOy5OTI140AheH6tq743iiyys,1874
130
131
  dissect/target/plugins/apps/remoteaccess/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
131
132
  dissect/target/plugins/apps/remoteaccess/anydesk.py,sha256=IdijK3F6ppaB_IgKL-xDljlEbb8l9S2U0xSWKqK9xRs,4294
132
133
  dissect/target/plugins/apps/remoteaccess/remoteaccess.py,sha256=DWXkRDVUpFr1icK2fYwSXdZD204Xz0yRuO7rcJOwIwc,825
@@ -368,10 +369,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
368
369
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
369
370
  dissect/target/volumes/md.py,sha256=7ShPtusuLGaIv27SvEETtgsuoQyAa4iAAeOR1NEaajI,1689
370
371
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
371
- dissect.target-3.20.dev26.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
372
- dissect.target-3.20.dev26.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
373
- dissect.target-3.20.dev26.dist-info/METADATA,sha256=9ta9bS0OqAMhf6vc8zDqGxpXSkY-YRJMAHWRucXo3f8,12897
374
- dissect.target-3.20.dev26.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
375
- dissect.target-3.20.dev26.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
376
- dissect.target-3.20.dev26.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
377
- dissect.target-3.20.dev26.dist-info/RECORD,,
372
+ dissect.target-3.20.dev28.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
373
+ dissect.target-3.20.dev28.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
374
+ dissect.target-3.20.dev28.dist-info/METADATA,sha256=KaInzlw-bjxrdEkeFM3pb6BDAM8IBEP6atLJ3wjH_d4,12897
375
+ dissect.target-3.20.dev28.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
376
+ dissect.target-3.20.dev28.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
377
+ dissect.target-3.20.dev28.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
378
+ dissect.target-3.20.dev28.dist-info/RECORD,,