#!/usr/bin/env python3
"""snmp_apply — minimal privileged 'apply' step for SNMP configuration.

SECURITY / PRIVILEGE MODEL
--------------------------
This is the ONLY part of the SNMP set-configuration flow that runs as root
(granted NOPASSWD sudo to the csl service user). Following the get_machine_uuid.sh
precedent, it is intentionally tiny and its behaviour is fully compiled-in:

  * It takes NO path or command arguments (argv is ignored).
  * It reads a small JSON object on STDIN with a single allowed key, `snmpd_conf`,
    whose value is the fully-rendered snmpd.conf *content* produced (and validated)
    by the unprivileged front-end (setting_snmp_configuration.py, run as the
    service user).
  * It writes that content to the HARDCODED target /etc/snmp/snmpd.conf and runs a
    HARDCODED systemctl/rm sequence. No target path or command is caller-controlled,
    so the NOPASSWD grant cannot become an arbitrary write/exec-as-root primitive.

All validation, YAML parsing and rendering live in the unprivileged front-end; this
helper deliberately contains none of it.
"""
import json
import os
import subprocess
import sys
import tempfile

SNMPD_CONF = "/etc/snmp/snmpd.conf"
SNMPD_CACHE = "/var/lib/snmp/snmpd.conf"
ALLOWED_KEYS = {"snmpd_conf"}


def _die(msg, code=1):
    print(f"snmp_apply: {msg}", file=sys.stderr)
    sys.exit(code)


def _write_root_file(path, content, mode=0o644):
    """Atomically write `content` to `path` (root:root, given mode)."""
    directory = os.path.dirname(path)
    fd, tmp = tempfile.mkstemp(dir=directory, prefix=".snmp_apply.")
    try:
        with os.fdopen(fd, "w") as fh:
            fh.write(content)
        os.chmod(tmp, mode)
        os.replace(tmp, path)
    except Exception:
        try:
            os.unlink(tmp)
        except OSError:
            pass
        raise


def _snmpd_service_exists():
    try:
        result = subprocess.run(
            ["systemctl", "list-unit-files", "snmpd.service", "--no-legend", "--no-pager"],
            stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
        )
    except OSError:
        return False
    return result.returncode == 0 and "snmpd.service" in result.stdout


def _run(cmd):
    try:
        result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    except OSError as exc:
        _die(f"command failed: {' '.join(cmd)}: {exc}")
    if result.returncode != 0:
        _die(f"command failed: {' '.join(cmd)}: {result.stderr.strip()}")


def main():
    try:
        payload = json.loads(sys.stdin.read())
    except (json.JSONDecodeError, ValueError) as exc:
        _die(f"invalid JSON on stdin: {exc}", code=2)

    if not isinstance(payload, dict):
        _die("stdin JSON must be an object", code=2)
    unknown = set(payload) - ALLOWED_KEYS
    if unknown:
        _die(f"unknown key(s) in payload: {sorted(unknown)}", code=2)
    content = payload.get("snmpd_conf")
    if not isinstance(content, str):
        _die("payload must contain string 'snmpd_conf'", code=2)

    # Write the rendered config to the fixed target.
    try:
        _write_root_file(SNMPD_CONF, content, 0o644)
    except OSError as exc:
        _die(f"failed to write {SNMPD_CONF}: {exc}")
    print(f"snmp_apply: wrote {SNMPD_CONF}")

    if not _snmpd_service_exists():
        print("snmp_apply: snmpd.service not installed; skipping service restart.")
        return

    _run(["systemctl", "stop", "snmpd"])
    if os.path.exists(SNMPD_CACHE):
        _run(["rm", "-f", SNMPD_CACHE])
    _run(["systemctl", "restart", "snmpd"])
    print("snmp_apply: snmpd reconfigured and restarted.")


if __name__ == "__main__":
    main()
