dissect.target 3.14.dev28__py3-none-any.whl → 3.15__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. dissect/target/containers/ewf.py +1 -1
  2. dissect/target/containers/vhd.py +5 -2
  3. dissect/target/filesystem.py +36 -18
  4. dissect/target/filesystems/dir.py +10 -4
  5. dissect/target/filesystems/jffs.py +122 -0
  6. dissect/target/helpers/compat/path_310.py +506 -0
  7. dissect/target/helpers/compat/path_311.py +539 -0
  8. dissect/target/helpers/compat/path_312.py +443 -0
  9. dissect/target/helpers/compat/path_39.py +545 -0
  10. dissect/target/helpers/compat/path_common.py +223 -0
  11. dissect/target/helpers/cyber.py +512 -0
  12. dissect/target/helpers/fsutil.py +128 -666
  13. dissect/target/helpers/hashutil.py +17 -57
  14. dissect/target/helpers/keychain.py +9 -3
  15. dissect/target/helpers/loaderutil.py +1 -1
  16. dissect/target/helpers/mount.py +47 -4
  17. dissect/target/helpers/polypath.py +73 -0
  18. dissect/target/helpers/record_modifier.py +100 -0
  19. dissect/target/loader.py +2 -1
  20. dissect/target/loaders/asdf.py +2 -0
  21. dissect/target/loaders/cyber.py +37 -0
  22. dissect/target/loaders/log.py +14 -3
  23. dissect/target/loaders/raw.py +2 -0
  24. dissect/target/loaders/remote.py +12 -0
  25. dissect/target/loaders/tar.py +13 -0
  26. dissect/target/loaders/targetd.py +2 -0
  27. dissect/target/loaders/velociraptor.py +12 -3
  28. dissect/target/loaders/vmwarevm.py +2 -0
  29. dissect/target/plugin.py +272 -143
  30. dissect/target/plugins/apps/ssh/openssh.py +11 -54
  31. dissect/target/plugins/apps/ssh/opensshd.py +4 -3
  32. dissect/target/plugins/apps/ssh/putty.py +236 -0
  33. dissect/target/plugins/apps/ssh/ssh.py +58 -0
  34. dissect/target/plugins/apps/vpn/openvpn.py +6 -0
  35. dissect/target/plugins/apps/webserver/apache.py +309 -95
  36. dissect/target/plugins/apps/webserver/caddy.py +5 -2
  37. dissect/target/plugins/apps/webserver/citrix.py +82 -0
  38. dissect/target/plugins/apps/webserver/iis.py +9 -12
  39. dissect/target/plugins/apps/webserver/nginx.py +5 -2
  40. dissect/target/plugins/apps/webserver/webserver.py +25 -41
  41. dissect/target/plugins/child/wsl.py +1 -1
  42. dissect/target/plugins/filesystem/ntfs/mft.py +10 -0
  43. dissect/target/plugins/filesystem/ntfs/mft_timeline.py +10 -0
  44. dissect/target/plugins/filesystem/ntfs/usnjrnl.py +10 -0
  45. dissect/target/plugins/filesystem/ntfs/utils.py +28 -5
  46. dissect/target/plugins/filesystem/resolver.py +6 -4
  47. dissect/target/plugins/general/default.py +0 -2
  48. dissect/target/plugins/general/example.py +0 -1
  49. dissect/target/plugins/general/loaders.py +3 -5
  50. dissect/target/plugins/os/unix/_os.py +3 -3
  51. dissect/target/plugins/os/unix/bsd/citrix/_os.py +68 -28
  52. dissect/target/plugins/os/unix/bsd/citrix/history.py +130 -0
  53. dissect/target/plugins/os/unix/generic.py +17 -10
  54. dissect/target/plugins/os/unix/linux/fortios/__init__.py +0 -0
  55. dissect/target/plugins/os/unix/linux/fortios/_os.py +534 -0
  56. dissect/target/plugins/os/unix/linux/fortios/generic.py +30 -0
  57. dissect/target/plugins/os/unix/linux/fortios/locale.py +109 -0
  58. dissect/target/plugins/os/windows/log/evt.py +1 -1
  59. dissect/target/plugins/os/windows/log/schedlgu.py +155 -0
  60. dissect/target/plugins/os/windows/regf/firewall.py +1 -1
  61. dissect/target/plugins/os/windows/regf/shimcache.py +1 -1
  62. dissect/target/plugins/os/windows/regf/trusteddocs.py +1 -1
  63. dissect/target/plugins/os/windows/registry.py +1 -1
  64. dissect/target/plugins/os/windows/sam.py +3 -0
  65. dissect/target/plugins/os/windows/sru.py +41 -28
  66. dissect/target/plugins/os/windows/tasks.py +5 -2
  67. dissect/target/target.py +7 -3
  68. dissect/target/tools/dd.py +7 -1
  69. dissect/target/tools/fs.py +8 -1
  70. dissect/target/tools/info.py +22 -15
  71. dissect/target/tools/mount.py +28 -3
  72. dissect/target/tools/query.py +146 -117
  73. dissect/target/tools/reg.py +21 -16
  74. dissect/target/tools/shell.py +30 -6
  75. dissect/target/tools/utils.py +28 -0
  76. dissect/target/volumes/bde.py +14 -10
  77. dissect/target/volumes/luks.py +18 -10
  78. {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/METADATA +4 -3
  79. {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/RECORD +85 -67
  80. dissect/target/plugins/os/unix/linux/fortigate/_os.py +0 -175
  81. /dissect/target/{plugins/os/unix/linux/fortigate → helpers/compat}/__init__.py +0 -0
  82. {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/COPYRIGHT +0 -0
  83. {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/LICENSE +0 -0
  84. {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/WHEEL +0 -0
  85. {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/entry_points.txt +0 -0
  86. {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,443 @@
1
+ """A pathlib.Path compatible implementation for dissect.target.
2
+
3
+ This allows for the majority of the pathlib.Path API to "just work" on dissect.target filesystems.
4
+
5
+ Most of this consists of subclassed internal classes with dissect.target specific patches,
6
+ but sometimes the change to a function is small, so the entire internal function is copied
7
+ and only a small part changed. To ease updating this code, the order of functions, comments
8
+ and code style is kept largely the same as the original pathlib.py.
9
+
10
+ Yes, we know, this is playing with fire and it can break on new CPython releases.
11
+
12
+ The implementation is split up in multiple files, one for each CPython version.
13
+ You're currently looking at the CPython 3.12 implementation.
14
+
15
+ Commit hash we're in sync with:
16
+
17
+ Notes:
18
+ - CPython 3.12 changed a lot in preparation of proper subclassing, so our patches differ
19
+ a lot from previous versions
20
+ - Flavours don't really exist anymore, but since we kind of "multi-flavour" we need to emulate it
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import posixpath
26
+ import sys
27
+ from pathlib import Path, PurePath
28
+ from stat import S_ISBLK, S_ISCHR, S_ISFIFO, S_ISSOCK
29
+ from typing import IO, TYPE_CHECKING, Iterator, Optional
30
+
31
+ from dissect.target import filesystem
32
+ from dissect.target.exceptions import FilesystemError, SymlinkRecursionError
33
+ from dissect.target.helpers import polypath
34
+ from dissect.target.helpers.compat.path_common import (
35
+ io_open,
36
+ isjunction,
37
+ realpath,
38
+ scandir,
39
+ )
40
+
41
+ if TYPE_CHECKING:
42
+ from dissect.target.filesystem import Filesystem, FilesystemEntry
43
+ from dissect.target.helpers.compat.path_common import _DissectScandirIterator
44
+ from dissect.target.helpers.fsutil import stat_result
45
+
46
+
47
+ class _DissectFlavour:
48
+ sep = "/"
49
+ altsep = ""
50
+ case_sensitive = False
51
+
52
+ __variant_instances = {}
53
+
54
+ def __new__(cls, case_sensitive: bool = False, alt_separator: str = ""):
55
+ idx = (case_sensitive, alt_separator)
56
+ instance = cls.__variant_instances.get(idx, None)
57
+ if instance is None:
58
+ instance = object.__new__(cls)
59
+ cls.__variant_instances[idx] = instance
60
+
61
+ return instance
62
+
63
+ def __init__(self, case_sensitive: bool = False, alt_separator: str = ""):
64
+ self.altsep = alt_separator
65
+ self.case_sensitive = case_sensitive
66
+
67
+ def normcase(self, s: str) -> str:
68
+ return s if self.case_sensitive else s.lower()
69
+
70
+ splitdrive = staticmethod(posixpath.splitdrive)
71
+
72
+ def splitroot(self, part: str) -> tuple[str, str]:
73
+ return polypath.splitroot(part, alt_separator=self.altsep)
74
+
75
+ def join(self, *args) -> str:
76
+ return polypath.join(*args, alt_separator=self.altsep)
77
+
78
+ # NOTE: Fallback implementation from older versions of pathlib.py
79
+ def ismount(self, path: TargetPath) -> bool:
80
+ # Need to exist and be a dir
81
+ if not path.exists() or not path.is_dir():
82
+ return False
83
+
84
+ try:
85
+ parent_dev = path.parent.stat().st_dev
86
+ except FilesystemError:
87
+ return False
88
+
89
+ dev = path.stat().st_dev
90
+ if dev != parent_dev:
91
+ return True
92
+ ino = path.stat().st_ino
93
+ parent_ino = path.parent.stat().st_ino
94
+ return ino == parent_ino
95
+
96
+ isjunction = staticmethod(isjunction)
97
+
98
+ samestat = staticmethod(posixpath.samestat)
99
+
100
+ def isabs(self, path: str) -> bool:
101
+ return polypath.isabs(path, alt_separator=self.altsep)
102
+
103
+ realpath = staticmethod(realpath)
104
+
105
+
106
+ class PureDissectPath(PurePath):
107
+ _fs: Filesystem
108
+ _flavour = _DissectFlavour(case_sensitive=False)
109
+
110
+ def __reduce__(self) -> tuple:
111
+ raise TypeError("TargetPath pickling is currently not supported")
112
+
113
+ def __init__(self, fs: Filesystem, *pathsegments):
114
+ if not isinstance(fs, filesystem.Filesystem):
115
+ raise TypeError(
116
+ "invalid PureDissectPath initialization: missing filesystem, "
117
+ "got %r (this might be a bug, please report)" % pathsegments
118
+ )
119
+
120
+ alt_separator = fs.alt_separator
121
+ path_args = []
122
+ for arg in pathsegments:
123
+ if isinstance(arg, str):
124
+ arg = polypath.normalize(arg, alt_separator=alt_separator)
125
+ path_args.append(arg)
126
+
127
+ super().__init__(*path_args)
128
+ self._fs = fs
129
+ self._flavour = _DissectFlavour(alt_separator=fs.alt_separator, case_sensitive=fs.case_sensitive)
130
+
131
+ def with_segments(self, *pathsegments) -> TargetPath:
132
+ return type(self)(self._fs, *pathsegments)
133
+
134
+ # NOTE: This is copied from pathlib.py but turned into an instance method so we get access to the correct flavour
135
+ def _parse_path(self, path: str) -> tuple[str, str, list[str]]:
136
+ if not path:
137
+ return "", "", []
138
+ sep = self._flavour.sep
139
+ altsep = self._flavour.altsep
140
+ if altsep:
141
+ path = path.replace(altsep, sep)
142
+ drv, root, rel = self._flavour.splitroot(path)
143
+ if not root and drv.startswith(sep) and not drv.endswith(sep):
144
+ drv_parts = drv.split(sep)
145
+ if len(drv_parts) == 4 and drv_parts[2] not in "?.":
146
+ # e.g. //server/share
147
+ root = sep
148
+ elif len(drv_parts) == 6:
149
+ # e.g. //?/unc/server/share
150
+ root = sep
151
+ parsed = [sys.intern(str(x)) for x in rel.split(sep) if x and x != "."]
152
+ return drv, root, parsed
153
+
154
+ def is_reserved(self) -> bool:
155
+ """Return True if the path contains one of the special names reserved
156
+ by the system, if any."""
157
+ return False
158
+
159
+
160
+ class TargetPath(Path, PureDissectPath):
161
+ __slots__ = ("_entry",)
162
+
163
+ def get(self) -> FilesystemEntry:
164
+ try:
165
+ return self._entry
166
+ except AttributeError:
167
+ self._entry = self._fs.get(str(self))
168
+ return self._entry
169
+
170
+ def stat(self, *, follow_symlinks: bool = True) -> stat_result:
171
+ """
172
+ Return the result of the stat() system call on this path, like
173
+ os.stat() does.
174
+ """
175
+ if follow_symlinks:
176
+ return self.get().stat()
177
+ else:
178
+ return self.get().lstat()
179
+
180
+ def exists(self, *, follow_symlinks: bool = True) -> bool:
181
+ """
182
+ Whether this path exists.
183
+
184
+ This method normally follows symlinks; to check whether a symlink exists,
185
+ add the argument follow_symlinks=False.
186
+ """
187
+ try:
188
+ # .exists() must resolve possible symlinks
189
+ self.stat(follow_symlinks=follow_symlinks)
190
+ return True
191
+ except (FilesystemError, ValueError):
192
+ return False
193
+
194
+ def is_dir(self) -> bool:
195
+ """
196
+ Whether this path is a directory.
197
+ """
198
+ try:
199
+ return self.get().is_dir()
200
+ except (FilesystemError, ValueError):
201
+ return False
202
+
203
+ def is_file(self) -> bool:
204
+ """
205
+ Whether this path is a regular file (also True for symlinks pointing
206
+ to regular files).
207
+ """
208
+ try:
209
+ return self.get().is_file()
210
+ except (FilesystemError, ValueError):
211
+ return False
212
+
213
+ def is_symlink(self) -> bool:
214
+ """
215
+ Whether this path is a symbolic link.
216
+ """
217
+ try:
218
+ return self.get().is_symlink()
219
+ except (FilesystemError, ValueError):
220
+ return False
221
+
222
+ def is_block_device(self) -> bool:
223
+ """
224
+ Whether this path is a block device.
225
+ """
226
+ try:
227
+ return S_ISBLK(self.stat().st_mode)
228
+ except (FilesystemError, ValueError):
229
+ return False
230
+
231
+ def is_char_device(self) -> bool:
232
+ """
233
+ Whether this path is a character device.
234
+ """
235
+ try:
236
+ return S_ISCHR(self.stat().st_mode)
237
+ except (FilesystemError, ValueError):
238
+ return False
239
+
240
+ def is_fifo(self) -> bool:
241
+ """
242
+ Whether this path is a FIFO.
243
+ """
244
+ try:
245
+ return S_ISFIFO(self.stat().st_mode)
246
+ except (FilesystemError, ValueError):
247
+ return False
248
+
249
+ def is_socket(self) -> bool:
250
+ """
251
+ Whether this path is a socket.
252
+ """
253
+ try:
254
+ return S_ISSOCK(self.stat().st_mode)
255
+ except (FilesystemError, ValueError):
256
+ return False
257
+
258
+ def open(
259
+ self,
260
+ mode: str = "rb",
261
+ buffering: int = 0,
262
+ encoding: Optional[str] = None,
263
+ errors: Optional[str] = None,
264
+ newline: Optional[str] = None,
265
+ ) -> IO:
266
+ """Open file and return a stream.
267
+
268
+ Supports a subset of features of the real pathlib.open/io.open.
269
+
270
+ Note: in contrast to regular Python, the mode is binary by default. Text mode
271
+ has to be explicitly specified. Buffering is also disabled by default.
272
+ """
273
+ return io_open(self, mode, buffering, encoding, errors, newline)
274
+
275
+ def write_bytes(self, data: bytes) -> int:
276
+ """
277
+ Open the file in bytes mode, write to it, and close the file.
278
+ """
279
+ raise NotImplementedError("TargetPath.write_bytes() is unsupported")
280
+
281
+ def write_text(
282
+ self, data: str, encoding: Optional[str] = None, errors: Optional[str] = None, newline: Optional[str] = None
283
+ ) -> int:
284
+ """
285
+ Open the file in text mode, write to it, and close the file.
286
+ """
287
+ raise NotImplementedError("TargetPath.write_text() is unsupported")
288
+
289
+ def iterdir(self) -> Iterator[TargetPath]:
290
+ """Iterate over the files in this directory. Does not yield any
291
+ result for the special paths '.' and '..'.
292
+ """
293
+ for entry in scandir(self):
294
+ if entry.name in {".", ".."}:
295
+ # Yielding a path object for these makes little sense
296
+ continue
297
+ child_path = self._make_child_relpath(entry.name)
298
+ child_path._entry = entry
299
+ yield child_path
300
+
301
+ def _scandir(self) -> _DissectScandirIterator:
302
+ return scandir(self)
303
+
304
+ @classmethod
305
+ def cwd(cls) -> TargetPath:
306
+ """Return a new path pointing to the current working directory."""
307
+ raise NotImplementedError("TargetPath.cwd() is unsupported")
308
+
309
+ @classmethod
310
+ def home(cls) -> TargetPath:
311
+ """Return a new path pointing to the user's home directory (as
312
+ returned by os.path.expanduser('~')).
313
+ """
314
+ raise NotImplementedError("TargetPath.home() is unsupported")
315
+
316
+ def absolute(self) -> TargetPath:
317
+ """Return an absolute version of this path by prepending the current
318
+ working directory. No normalization or symlink resolution is performed.
319
+
320
+ Use resolve() to get the canonical path to a file.
321
+ """
322
+ raise NotImplementedError("TargetPath.absolute() is unsupported in Dissect")
323
+
324
+ # NOTE: We changed some of the error handling here to deal with our own exception types
325
+ def resolve(self, strict: bool = False) -> TargetPath:
326
+ """
327
+ Make the path absolute, resolving all symlinks on the way and also
328
+ normalizing it.
329
+ """
330
+
331
+ s = self._flavour.realpath(self, strict=strict)
332
+ p = self.with_segments(s)
333
+
334
+ # In non-strict mode, realpath() doesn't raise on symlink loops.
335
+ # Ensure we get an exception by calling stat()
336
+ if not strict:
337
+ try:
338
+ p.stat()
339
+ except FilesystemError as e:
340
+ if isinstance(e, SymlinkRecursionError):
341
+ raise
342
+ return p
343
+
344
+ def owner(self) -> str:
345
+ """
346
+ Return the login name of the file owner.
347
+ """
348
+ raise NotImplementedError("TargetPath.owner() is unsupported")
349
+
350
+ def group(self) -> str:
351
+ """
352
+ Return the group name of the file gid.
353
+ """
354
+ raise NotImplementedError("TargetPath.group() is unsupported")
355
+
356
+ def readlink(self) -> TargetPath:
357
+ """
358
+ Return the path to which the symbolic link points.
359
+ """
360
+ return self.with_segments(self.get().readlink())
361
+
362
+ def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None:
363
+ """
364
+ Create this file with the given access mode, if it doesn't exist.
365
+ """
366
+ raise NotImplementedError("TargetPath.touch() is unsupported")
367
+
368
+ def mkdir(self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False) -> None:
369
+ """
370
+ Create a new directory at this given path.
371
+ """
372
+ raise NotImplementedError("TargetPath.mkdir() is unsupported")
373
+
374
+ def chmod(self, mode: int, *, follow_symlinks: bool = True) -> None:
375
+ """
376
+ Change the permissions of the path, like os.chmod().
377
+ """
378
+ raise NotImplementedError("TargetPath.chmod() is unsupported")
379
+
380
+ def lchmod(self, mode: int) -> None:
381
+ """
382
+ Like chmod(), except if the path points to a symlink, the symlink's
383
+ permissions are changed, rather than its target's.
384
+ """
385
+ raise NotImplementedError("TargetPath.lchmod() is unsupported")
386
+
387
+ def unlink(self, missing_ok: bool = False) -> None:
388
+ """
389
+ Remove this file or link.
390
+ If the path is a directory, use rmdir() instead.
391
+ """
392
+ raise NotImplementedError("TargetPath.unlink() is unsupported")
393
+
394
+ def rmdir(self) -> None:
395
+ """
396
+ Remove this directory. The directory must be empty.
397
+ """
398
+ raise NotImplementedError("TargetPath.rmdir() is unsupported")
399
+
400
+ def rename(self, target: str) -> TargetPath:
401
+ """
402
+ Rename this path to the target path.
403
+
404
+ The target path may be absolute or relative. Relative paths are
405
+ interpreted relative to the current working directory, *not* the
406
+ directory of the Path object.
407
+
408
+ Returns the new Path instance pointing to the target path.
409
+ """
410
+ raise NotImplementedError("TargetPath.rename() is unsupported")
411
+
412
+ def replace(self, target: str) -> TargetPath:
413
+ """
414
+ Rename this path to the target path, overwriting if that path exists.
415
+
416
+ The target path may be absolute or relative. Relative paths are
417
+ interpreted relative to the current working directory, *not* the
418
+ directory of the Path object.
419
+
420
+ Returns the new Path instance pointing to the target path.
421
+ """
422
+ raise NotImplementedError("TargetPath.replace() is unsupported")
423
+
424
+ def symlink_to(self, target: str, target_is_directory: bool = False) -> None:
425
+ """
426
+ Make this path a symlink pointing to the target path.
427
+ Note the order of arguments (link, target) is the reverse of os.symlink.
428
+ """
429
+ raise NotImplementedError("TargetPath.symlink_to() is unsupported")
430
+
431
+ def hardlink_to(self, target: str) -> None:
432
+ """
433
+ Make this path a hard link pointing to the same file as *target*.
434
+
435
+ Note the order of arguments (self, target) is the reverse of os.link's.
436
+ """
437
+ raise NotImplementedError("TargetPath.hardlink_to() is unsupported")
438
+
439
+ def expanduser(self) -> TargetPath:
440
+ """Return a new path with expanded ~ and ~user constructs
441
+ (as returned by os.path.expanduser)
442
+ """
443
+ raise NotImplementedError("TargetPath.expanduser() is unsupported")