Implement all 6 universal filesystem metadata properties inside inspect command
This commit is contained in:
@@ -2,10 +2,19 @@
|
||||
# Library for forensic analysis
|
||||
|
||||
import click
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
import stat
|
||||
import os
|
||||
import sys
|
||||
from typing import List, Dict
|
||||
|
||||
try:
|
||||
import pwd
|
||||
import grp
|
||||
except ImportError:
|
||||
pwd = None
|
||||
grp = None
|
||||
|
||||
@click.group(name="forensics")
|
||||
def forensics_group(): pass
|
||||
@@ -19,28 +28,118 @@ class FileMetadata:
|
||||
size: int
|
||||
magic: str
|
||||
extension: str
|
||||
|
||||
# Task 1: POSIX Permissions
|
||||
permissions_octal: str
|
||||
permissions_symbolic: str
|
||||
|
||||
# Task 2: Ownership Identity
|
||||
owner_uid: int
|
||||
owner_username: str
|
||||
owner_gid: int
|
||||
owner_groupname: str
|
||||
|
||||
# Task 3: Allocation Metrics
|
||||
allocated_size: int
|
||||
|
||||
# Task 4: Hard Links
|
||||
hard_links: int
|
||||
|
||||
# Task 5: Inode & Device Identifiers
|
||||
inode: int
|
||||
device: int
|
||||
|
||||
# Task 6: Extended Attributes
|
||||
extended_attributes: Dict[str, str] = field(default_factory=dict)
|
||||
|
||||
@forensics_group.command()
|
||||
@click.argument('path', type=click.Path(exists=True))
|
||||
def inspect(path):
|
||||
p = Path(path)
|
||||
size = p.stat().st_size
|
||||
stat_info = p.stat()
|
||||
|
||||
# Apparent size & extension
|
||||
size = stat_info.st_size
|
||||
extension = p.suffix
|
||||
|
||||
# Magic bytes
|
||||
try:
|
||||
with open(p, 'rb') as f:
|
||||
magic = f.read(4).hex().upper()
|
||||
except Exception:
|
||||
magic = "UNKNOWN"
|
||||
magic = ""
|
||||
|
||||
# Task 1: POSIX Permissions
|
||||
mode = stat_info.st_mode
|
||||
permissions_octal = oct(stat.S_IMODE(mode))
|
||||
permissions_symbolic = stat.filemode(mode)
|
||||
|
||||
# Task 2: Ownership Identity
|
||||
owner_uid = stat_info.st_uid
|
||||
owner_gid = stat_info.st_gid
|
||||
owner_username = str(owner_uid)
|
||||
owner_groupname = str(owner_gid)
|
||||
|
||||
if pwd is not None:
|
||||
try:
|
||||
owner_username = pwd.getpwuid(owner_uid).pw_name
|
||||
except KeyError:
|
||||
pass
|
||||
if grp is not None:
|
||||
try:
|
||||
owner_groupname = grp.getgrgid(owner_gid).gr_name
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# Task 3: Allocation Metrics
|
||||
if hasattr(stat_info, "st_blocks"):
|
||||
allocated_size = stat_info.st_blocks * 512
|
||||
else:
|
||||
allocated_size = size
|
||||
|
||||
# Task 4: Hard Links
|
||||
hard_links = stat_info.st_nlink
|
||||
|
||||
# Task 5: Inode & Device Identifiers
|
||||
inode = stat_info.st_ino
|
||||
device = stat_info.st_dev
|
||||
|
||||
# Task 6: Extended Attributes
|
||||
extended_attributes = {}
|
||||
if hasattr(os, "listxattr") and hasattr(os, "getxattr"):
|
||||
try:
|
||||
attrs = os.listxattr(p)
|
||||
for attr in attrs:
|
||||
try:
|
||||
val = os.getxattr(p, attr)
|
||||
extended_attributes[attr] = val.decode("utf-8", errors="ignore")
|
||||
except OSError:
|
||||
pass
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
meta = FileMetadata(
|
||||
filename=p.name,
|
||||
size=size,
|
||||
magic=magic,
|
||||
extension=extension
|
||||
extension=extension,
|
||||
permissions_octal=permissions_octal,
|
||||
permissions_symbolic=permissions_symbolic,
|
||||
owner_uid=owner_uid,
|
||||
owner_username=owner_username,
|
||||
owner_gid=owner_gid,
|
||||
owner_groupname=owner_groupname,
|
||||
allocated_size=allocated_size,
|
||||
hard_links=hard_links,
|
||||
inode=inode,
|
||||
device=device,
|
||||
extended_attributes=extended_attributes
|
||||
)
|
||||
|
||||
# Present using Rich Table
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
console = Console()
|
||||
table = Table(title=f"Metadata: {meta.filename}", show_header=False)
|
||||
table.add_column("Key", style="bold cyan")
|
||||
@@ -50,6 +149,17 @@ def inspect(path):
|
||||
table.add_row("Size", f"{meta.size} bytes")
|
||||
table.add_row("Magic Bytes (Hex)", meta.magic)
|
||||
table.add_row("Extension", meta.extension)
|
||||
table.add_row("Permissions", f"{meta.permissions_symbolic} ({meta.permissions_octal})")
|
||||
table.add_row("Owner", f"{meta.owner_username} (UID: {meta.owner_uid})")
|
||||
table.add_row("Group", f"{meta.owner_groupname} (GID: {meta.owner_gid})")
|
||||
table.add_row("Allocated Space", f"{meta.allocated_size} bytes")
|
||||
table.add_row("Hard Links", str(meta.hard_links))
|
||||
table.add_row("Inode", str(meta.inode))
|
||||
table.add_row("Device", str(meta.device))
|
||||
|
||||
if meta.extended_attributes:
|
||||
xattr_str = ", ".join(f"{k}={v}" for k, v in meta.extended_attributes.items())
|
||||
table.add_row("Extended Attributes", xattr_str)
|
||||
|
||||
console.print(table)
|
||||
return meta
|
||||
|
||||
Reference in New Issue
Block a user