107 lines
2.9 KiB
Python
107 lines
2.9 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
|
|
CONFIG_NAME = "config.json"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ClientConfig:
|
|
hashcat_exe: Path
|
|
potfile_path: Path
|
|
client_root: Path
|
|
wordlists: dict[str, Path]
|
|
poll_seconds: int
|
|
max_parallel_jobs: int
|
|
|
|
|
|
def load_config(script_dir: Path) -> ClientConfig:
|
|
path = script_dir / CONFIG_NAME
|
|
|
|
if not path.is_file():
|
|
raise FileNotFoundError(
|
|
f"Missing {path}. "
|
|
"Copy config.example.json to config.json and edit it."
|
|
)
|
|
|
|
try:
|
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
except json.JSONDecodeError as exc:
|
|
raise ValueError(f"Invalid JSON in {path}: {exc}") from exc
|
|
|
|
if not isinstance(data, dict):
|
|
raise ValueError("Client config must contain a JSON object.")
|
|
|
|
required = (
|
|
"hashcat_exe",
|
|
"potfile_path",
|
|
"client_root",
|
|
"wordlists",
|
|
"poll_seconds",
|
|
"max_parallel_jobs",
|
|
)
|
|
|
|
missing = [name for name in required if name not in data]
|
|
if missing:
|
|
raise ValueError(
|
|
"Missing required config fields: "
|
|
+ ", ".join(missing)
|
|
)
|
|
|
|
for name in (
|
|
"hashcat_exe",
|
|
"potfile_path",
|
|
"client_root",
|
|
):
|
|
if not isinstance(data[name], str) or not data[name].strip():
|
|
raise ValueError(
|
|
f"Config field {name!r} must be a non-empty string."
|
|
)
|
|
|
|
if not isinstance(data["wordlists"], dict) or not data["wordlists"]:
|
|
raise ValueError(
|
|
"Config field 'wordlists' must be a non-empty object."
|
|
)
|
|
|
|
for name, path_value in data["wordlists"].items():
|
|
if not isinstance(name, str) or not name.strip():
|
|
raise ValueError(
|
|
"Each wordlists key must be a non-empty string."
|
|
)
|
|
|
|
if not isinstance(path_value, str) or not path_value.strip():
|
|
raise ValueError(
|
|
f"Wordlist {name!r} path must be a non-empty string."
|
|
)
|
|
|
|
for name in (
|
|
"poll_seconds",
|
|
"max_parallel_jobs",
|
|
):
|
|
value = data[name]
|
|
|
|
if isinstance(value, bool) or not isinstance(value, int):
|
|
raise ValueError(
|
|
f"Config field {name!r} must be an integer."
|
|
)
|
|
|
|
if value <= 0:
|
|
raise ValueError(
|
|
f"Config field {name!r} must be greater than zero."
|
|
)
|
|
|
|
return ClientConfig(
|
|
hashcat_exe=Path(data["hashcat_exe"]).expanduser().resolve(),
|
|
potfile_path=Path(data["potfile_path"]).expanduser().resolve(),
|
|
client_root=Path(data["client_root"]).expanduser().resolve(),
|
|
wordlists={
|
|
name: Path(path_value).expanduser().resolve()
|
|
for name, path_value in data["wordlists"].items()
|
|
},
|
|
poll_seconds=data["poll_seconds"],
|
|
max_parallel_jobs=data["max_parallel_jobs"],
|
|
)
|