143 lines
4.8 KiB
Python
143 lines
4.8 KiB
Python
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)
|