Files
cyber-security/lab2/access.py
2026-03-03 10:21:13 +03:00

245 lines
6.8 KiB
Python

#!/usr/bin/env python3
import getpass
import hashlib
import shutil
import signal
import sys
from datetime import datetime
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from config import CONFDATA_DIR, LOG_DIR, PASSWD_FILE
HELP_TEXT = """\
Commands:
create <file> create new empty file [requires: w]
read <file> print file contents [requires: r]
append <file> <text> append text line to file [requires: w]
copy <src> <dst> copy file into confdata [requires: r, w]
remove <file> delete file from confdata [requires: d]
help show this help
exit exit"""
def hash_password(password: str) -> str:
return hashlib.sha256(password.encode("ascii")).hexdigest()
def log_action(login: str, action: str) -> None:
LOG_DIR.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().isoformat(sep=" ", timespec="seconds")
with open(LOG_DIR / "access.log", "a") as f:
f.write(f"{timestamp} [{login}] {action}\n")
def read_users() -> dict[str, dict]:
users: dict[str, dict] = {}
if not PASSWD_FILE.exists():
return users
with open(PASSWD_FILE) as f:
for line in f:
line = line.strip()
if not line:
continue
parts = line.split(":", 4)
if len(parts) != 5:
continue
users[parts[0]] = {
"password_hash": parts[1],
"id": parts[2],
"permissions": parts[3],
"full_name": parts[4],
}
return users
def authenticate() -> tuple[str, dict]:
users = read_users()
while True:
try:
login = input("Login: ").strip()
password = getpass.getpass("Password: ")
except (EOFError, KeyboardInterrupt):
print("\nBye.")
sys.exit(0)
user = users.get(login)
if user and user["password_hash"] == hash_password(password):
return login, user
log_action(login if login else "-", "LOGIN_FAILED")
print("Invalid credentials. Try again.")
def confdata_path(arg: str) -> Path:
p = Path(arg)
if not p.is_absolute():
p = CONFDATA_DIR / p
return p.resolve()
def is_in_confdata(path: Path) -> bool:
try:
path.relative_to(CONFDATA_DIR.resolve())
return True
except ValueError:
return False
def cmd_read(args: list[str], login: str, perms: str) -> None:
if "r" not in perms:
print("Permission denied (requires: r)")
return
if len(args) != 1:
print("Usage: read <file>")
return
path = confdata_path(args[0])
if not is_in_confdata(path):
print("Access denied: file must be inside confdata")
return
if not path.exists():
print(f"File not found: {path.name}")
return
print(path.read_text(), end="")
log_action(login, f"READ {path}")
def cmd_create(args: list[str], login: str, perms: str) -> None:
if "w" not in perms:
print("Permission denied (requires: w)")
return
if len(args) != 1:
print("Usage: create <file>")
return
path = confdata_path(args[0])
if not is_in_confdata(path):
print("Access denied: file must be inside confdata")
return
if path.exists():
print(f"File already exists: {path.name}")
return
path.parent.mkdir(parents=True, exist_ok=True)
path.touch()
log_action(login, f"CREATE {path}")
print("Done.")
def cmd_append(args: list[str], login: str, perms: str) -> None:
if "w" not in perms:
print("Permission denied (requires: w)")
return
if len(args) < 2:
print("Usage: append <file> <text>")
return
path = confdata_path(args[0])
if not is_in_confdata(path):
print("Access denied: file must be inside confdata")
return
if not path.exists():
print(f"File not found: {path.name}. Use 'create' first.")
return
text = " ".join(args[1:])
with open(path, "a") as f:
f.write(text + "\n")
log_action(login, f"APPEND {path}")
print("Done.")
def cmd_copy(args: list[str], login: str, perms: str) -> None:
if "r" not in perms or "w" not in perms:
print("Permission denied (requires: r, w)")
return
if len(args) != 2:
print("Usage: copy <src> <dst>")
return
src = Path(args[0]).resolve()
dst = confdata_path(args[1])
if not is_in_confdata(dst):
print("Access denied: destination must be inside confdata")
return
if dst.is_dir():
dst = dst / src.name
if dst.exists():
print(f"Destination already exists: {dst.name}")
return
if not src.exists():
print(f"Source not found: {args[0]}")
return
if src.is_dir():
print("Copying directories is not supported")
return
shutil.copy2(src, dst)
log_action(login, f"COPY {src} -> {dst}")
print("Done.")
def cmd_remove(args: list[str], login: str, perms: str) -> None:
if "d" not in perms:
print("Permission denied (requires: d)")
return
if len(args) != 1:
print("Usage: remove <file>")
return
path = confdata_path(args[0])
if not is_in_confdata(path):
print("Access denied: file must be inside confdata")
return
if not path.exists():
print(f"File not found: {path.name}")
return
path.unlink()
log_action(login, f"REMOVE {path}")
print("Done.")
def main() -> None:
signal.signal(signal.SIGINT, lambda _s, _f: (print("\nBye."), sys.exit(0)))
login, user = authenticate()
perms = user["permissions"]
full_name = user["full_name"]
log_action(login, "LOGIN")
print(f"\nПривет, {full_name}")
print(HELP_TEXT)
while True:
try:
line = input(f"\n{login}> ").strip()
except (EOFError, KeyboardInterrupt):
log_action(login, "EXIT")
print("\nBye.")
break
if not line:
continue
parts = line.split()
command, args = parts[0], parts[1:]
if command == "exit":
log_action(login, "EXIT")
print("Bye.")
break
elif command == "help":
print(HELP_TEXT)
elif command == "create":
cmd_create(args, login, perms)
elif command == "read":
cmd_read(args, login, perms)
elif command == "append":
cmd_append(args, login, perms)
elif command == "copy":
cmd_copy(args, login, perms)
elif command == "remove":
cmd_remove(args, login, perms)
else:
print(f"Unknown command: {command!r}. Type 'help' for available commands.")
if __name__ == "__main__":
main()