from __future__ import annotations import ipaddress import json import os from dataclasses import dataclass from .intelligence import canonical_service_name, enrich_host from .models import Host, PortInfo from .naabu_runtime import BUNDLED_NAABU_PATH, NAABU_VERSION, naabu_version, trusted_root_binary SYSTEM_NAABU_CANDIDATES = (BUNDLED_NAABU_PATH,) @dataclass(slots=True, frozen=True) class NaabuDiagnostic: user_path: str | None admin_path: str | None user_detail: str admin_detail: str def find_naabu() -> str | None: """Retourne exclusivement le moteur Naabu provisionné par LibreNet. Le paquet peut provisionner un Naabu 2.6.1 dans l'espace privé de LibreNet comme accélérateur optionnel pour les grands ensembles d'hôtes. Ne jamais choisir silencieusement un ``naabu`` du PATH évite qu'une version différente modifie les options ou le comportement du profil Standard. Si le moteur intégré est absent ou corrompu, le scanner reste pleinement utilisable avec Nmap au lieu d'utiliser un binaire inconnu. """ candidate = BUNDLED_NAABU_PATH if os.path.isfile(candidate) and os.access(candidate, os.X_OK): if naabu_version(candidate) == NAABU_VERSION: return candidate return None def _trusted_system_naabu(path: str) -> bool: """Même règle de confiance que le helper root, sans exécuter de privilèges.""" return trusted_root_binary(path) def find_admin_naabu() -> str | None: """Retourne le moteur LibreNet exact et sûr acceptable par le helper Polkit.""" candidate = BUNDLED_NAABU_PATH if _trusted_system_naabu(candidate) and naabu_version(candidate) == NAABU_VERSION: return candidate return None def naabu_diagnostic() -> NaabuDiagnostic: user_path = find_naabu() admin_path = find_admin_naabu() if user_path: version = naabu_version(user_path) suffix = f" — v{version}" if version else " — version non confirmée" source = "moteur LibreNet intégré" if user_path == BUNDLED_NAABU_PATH else "moteur externe" user_detail = f"OK — {source}{suffix}" else: user_detail = "INDISPONIBLE — Nmap sera utilisé (fonctionnement normal)" if admin_path: version = naabu_version(admin_path) suffix = f", v{version}" if version else ", version non confirmée" source = "moteur LibreNet intégré" if admin_path == BUNDLED_NAABU_PATH else "moteur externe" admin_detail = f"OK — {source}, root, non modifiable par groupe/autres{suffix}" elif user_path: admin_detail = ( "INDISPONIBLE — un Naabu utilisateur existe, mais le mode Admin exige " "le moteur LibreNet ou un Naabu système appartenant à root" ) else: admin_detail = "INDISPONIBLE — aucun Naabu système de confiance" return NaabuDiagnostic(user_path, admin_path, user_detail, admin_detail) def _naabu_payload(line: str) -> dict | None: try: payload = json.loads(line) except (TypeError, json.JSONDecodeError): return None return payload if isinstance(payload, dict) else None def _payload_ipv4(payload: dict) -> str | None: value = str(payload.get("ip") or payload.get("host") or "").strip() try: address = ipaddress.ip_address(value) except ValueError: return None if address.version != 4: return None return str(address) def parse_naabu_host_json_line(line: str) -> Host | None: """Parse une ligne JSONL issue de ``naabu -sn -json``. Depuis Naabu 2.4, un scan de découverte JSON émet aussi les résultats sans port. LibreNet n'a besoin ici que de l'IPv4 : l'équipement est marqué actif et sera enrichi par ARP/neighbor/ports lors des phases suivantes. """ payload = _naabu_payload(line) if payload is None: return None ip = _payload_ipv4(payload) if not ip: return None return Host(ip=ip, status="up") def parse_naabu_json_line(line: str) -> Host | None: """Transforme une ligne JSONL de scan de ports Naabu en observation LibreNet.""" payload = _naabu_payload(line) if payload is None: return None ip = _payload_ipv4(payload) if not ip: return None try: port = int(payload.get("port")) except (ValueError, TypeError): return None if not 1 <= port <= 65535: return None protocol = str(payload.get("protocol") or "tcp").strip().lower() if protocol not in {"tcp", "udp"}: protocol = "tcp" service = str(payload.get("service") or "").strip() host = Host( ip=ip, status="up", ports=[ PortInfo( port=port, protocol=protocol, state="open", service=canonical_service_name(port, protocol, service), ) ], ) return enrich_host(host)