#!/usr/bin/env python3
"""AIDE (Advanced Intrusion Detection Environment) configuration generator and check runner."""

import os
import json
import re
import subprocess
import argparse
from datetime import datetime

AIDE_RULE_SETS = {
    "critical_binaries": {
        "paths": ["/bin", "/sbin", "/usr/bin", "/usr/sbin"],
        "rule": "p+i+n+u+g+s+b+m+c+sha256+xattrs",
        "description": "Monitor all attributes and SHA-256 hash for system binaries",
    },
    "config_files": {
        "paths": ["/etc"],
        "rule": "p+i+u+g+sha256",
        "description": "Monitor permissions, inode, ownership, and hash for config files",
    },
    "boot_files": {
        "paths": ["/boot"],
        "rule": "p+i+n+u+g+s+b+m+c+sha256",
        "description": "Full attribute monitoring for boot files and kernel images",
    },
    "library_files": {
        "paths": ["/lib", "/lib64", "/usr/lib"],
        "rule": "p+i+n+u+g+s+b+sha256",
        "description": "Monitor shared libraries for unauthorized modifications",
    },
    "log_files": {
        "paths": ["/var/log"],
        "rule": "p+u+g+i",
        "description": "Monitor log file permissions and ownership (not content)",
    },
}

AIDE_EXCLUSIONS = [
    "!/var/log/.*", "!/var/spool/.*", "!/var/cache/.*",
    "!/proc", "!/sys", "!/dev", "!/run", "!/tmp",
    "!/var/lib/dpkg/.*", "!/var/lib/apt/.*",
]


def generate_aide_config(rule_sets=None, db_path="/var/lib/aide/aide.db",
                         db_new_path="/var/lib/aide/aide.db.new",
                         log_path="/var/log/aide/aide.log"):
    """Generate aide.conf configuration file content."""
    if rule_sets is None:
        rule_sets = list(AIDE_RULE_SETS.keys())

    lines = [
        "# AIDE Configuration - Generated by agent.py",
        f"# Generated: {datetime.utcnow().isoformat()}",
        "",
        f"database_in=file:{db_path}",
        f"database_out=file:{db_new_path}",
        f"database_new=file:{db_new_path}",
        "",
        "# Gzip output database",
        "gzip_dbout=yes",
        "",
        "# Report settings",
        "report_url=stdout",
        f"report_url=file:{log_path}",
        "",
        "# Rule definitions",
        "NORMAL = p+i+n+u+g+s+b+m+c+sha256",
        "PERMS = p+i+u+g",
        "LOG = p+u+g+i",
        "FULL = p+i+n+u+g+s+b+m+c+sha256+xattrs",
        "",
        "# Exclusions",
    ]

    for excl in AIDE_EXCLUSIONS:
        lines.append(excl)

    lines.append("")
    lines.append("# Monitored paths")

    for rs_name in rule_sets:
        rs = AIDE_RULE_SETS.get(rs_name)
        if not rs:
            continue
        lines.append(f"# {rs['description']}")
        for path in rs["paths"]:
            lines.append(f"{path} {rs['rule']}")
        lines.append("")

    return "\n".join(lines)


def initialize_baseline():
    """Run aide --init to create the baseline database."""
    result = subprocess.run(
        ["aide", "--init", "--config=/etc/aide/aide.conf"],
        capture_output=True, text=True, timeout=600
    )
    if result.returncode == 0:
        subprocess.run(
            ["cp", "/var/lib/aide/aide.db.new", "/var/lib/aide/aide.db"],
            capture_output=True, text=True,
            timeout=120,
        )
        return {"status": "success", "output": result.stdout[:500]}
    return {"status": "error", "stderr": result.stderr[:500]}


def run_integrity_check():
    """Run aide --check and parse the results."""
    result = subprocess.run(
        ["aide", "--check", "--config=/etc/aide/aide.conf"],
        capture_output=True, text=True, timeout=600
    )
    return parse_aide_output(result.stdout, result.returncode)


def parse_aide_output(output, return_code):
    """Parse AIDE check output into structured findings."""
    findings = {"added": [], "removed": [], "changed": [], "summary": {}}

    section = None
    for line in output.splitlines():
        line = line.strip()
        if "Added entries:" in line or "added:" in line.lower():
            section = "added"
        elif "Removed entries:" in line or "removed:" in line.lower():
            section = "removed"
        elif "Changed entries:" in line or "changed:" in line.lower():
            section = "changed"
        elif line.startswith("f") or line.startswith("d") or line.startswith("l"):
            match = re.match(r"^[fdl!]\s+(.+)", line)
            if match and section:
                filepath = match.group(1).strip()
                severity = _classify_severity(filepath, section)
                findings[section].append({
                    "path": filepath, "severity": severity, "change_type": section,
                })
        elif "Number of entries" in line:
            match = re.match(r"Number of entries:\s*(\d+)", line)
            if match:
                findings["summary"]["total_entries"] = int(match.group(1))

    findings["summary"]["added_count"] = len(findings["added"])
    findings["summary"]["removed_count"] = len(findings["removed"])
    findings["summary"]["changed_count"] = len(findings["changed"])
    findings["summary"]["return_code"] = return_code
    findings["summary"]["integrity_status"] = "clean" if return_code == 0 else "changes_detected"
    return findings


def _classify_severity(filepath, change_type):
    """Classify severity based on file path and change type."""
    critical_paths = ["/bin/", "/sbin/", "/usr/bin/", "/usr/sbin/", "/boot/", "/etc/shadow",
                      "/etc/passwd", "/etc/sudoers", "/etc/ssh/"]
    high_paths = ["/etc/", "/lib/", "/lib64/", "/usr/lib/"]

    if change_type == "removed" and any(filepath.startswith(p) for p in critical_paths):
        return "critical"
    if any(filepath.startswith(p) for p in critical_paths):
        return "critical" if change_type != "added" else "high"
    if any(filepath.startswith(p) for p in high_paths):
        return "high"
    return "medium"


def generate_cron_config(schedule="0 5 * * *", email=None):
    """Generate cron job for automated AIDE integrity checks."""
    cron_script = "#!/bin/bash\n"
    cron_script += "# AIDE automated integrity check\n"
    cron_script += "LOGFILE=/var/log/aide/aide-check-$(date +%Y%m%d).log\n"
    cron_script += "aide --check --config=/etc/aide/aide.conf > $LOGFILE 2>&1\n"
    cron_script += "EXIT_CODE=$?\n"
    cron_script += "if [ $EXIT_CODE -ne 0 ]; then\n"
    if email:
        cron_script += f'  mail -s "AIDE Alert: Integrity changes detected" {email} < $LOGFILE\n'
    cron_script += '  logger -t aide "Integrity check found changes (exit code $EXIT_CODE)"\n'
    cron_script += "fi\n"

    cron_entry = f"{schedule} root /usr/local/bin/aide-check.sh"
    return {"script": cron_script, "cron_entry": cron_entry, "schedule": schedule}


def generate_report(config_content, baseline_status, check_findings, cron_config):
    """Generate AIDE deployment and check report."""
    return {
        "report_time": datetime.utcnow().isoformat(),
        "config_lines": len(config_content.splitlines()),
        "monitored_rule_sets": list(AIDE_RULE_SETS.keys()),
        "exclusion_count": len(AIDE_EXCLUSIONS),
        "baseline_status": baseline_status,
        "integrity_check": check_findings,
        "cron_schedule": cron_config,
    }


def main():
    parser = argparse.ArgumentParser(description="AIDE File Integrity Monitoring Agent")
    parser.add_argument("--action", choices=["generate", "init", "check", "full"],
                        default="generate", help="Action to perform")
    parser.add_argument("--config-output", default="/etc/aide/aide.conf", help="Config output path")
    parser.add_argument("--alert-email", help="Email for cron alert notifications")
    parser.add_argument("--output", default="aide_report.json")
    parser.add_argument("--rule-sets", nargs="+", default=None,
                        choices=list(AIDE_RULE_SETS.keys()), help="Rule sets to enable")
    args = parser.parse_args()

    config_content = generate_aide_config(args.rule_sets)
    baseline_status = {}
    check_findings = {}

    if args.action in ("generate", "full"):
        os.makedirs(os.path.dirname(args.config_output), exist_ok=True)
        with open(args.config_output, "w") as f:
            f.write(config_content)
        print(f"[+] AIDE config written to {args.config_output}")

    if args.action in ("init", "full"):
        baseline_status = initialize_baseline()
        print(f"[+] Baseline init: {baseline_status['status']}")

    if args.action in ("check", "full"):
        check_findings = run_integrity_check()
        s = check_findings.get("summary", {})
        print(f"[+] Check: added={s.get('added_count', 0)} removed={s.get('removed_count', 0)} "
              f"changed={s.get('changed_count', 0)}")

    cron_config = generate_cron_config(email=args.alert_email)
    report = generate_report(config_content, baseline_status, check_findings, cron_config)
    with open(args.output, "w") as f:
        json.dump(report, f, indent=2, default=str)
    print(f"[+] Report saved to {args.output}")


if __name__ == "__main__":
    main()
