- Logo oficial (sabia.png) e conjunto de ícones do app desktop. - Interface em português e inglês: tradutor por observação do DOM, seletor de idioma e checagem de cobertura de tradução no CI. - CI/CD com caches (rust-cache, .deb do Tauri em cache) e pipeline de release multiplataforma (CLI em 5 alvos + app desktop via tauri-action), templates de issue/PR. - Suíte de testes de integração do sabia-core (scan e2e, handshake/crack 4-way e PMKID, pcap, trilha de auditoria) — 46 testes verdes. - Documentação completa pt/en: README, ROADMAP, SECURITY, CONTRIBUTING, CHANGELOG e docs/ (cli, interface, arquitetura), com enquadramento de uso autorizado. - Correções de clippy (-D warnings) e formatação em toda a árvore. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
136 lines
4.0 KiB
Rust
136 lines
4.0 KiB
Rust
//! Diagnóstico do ambiente: privilégios, ferramentas externas, interfaces,
|
|
//! suporte a modo monitor e integridade da trilha de auditoria.
|
|
|
|
use crate::audit::AuditLog;
|
|
use crate::iface::{self, Interface};
|
|
use crate::netlink::{self, command_exists};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
#[derive(Debug, Clone, serde::Serialize)]
|
|
pub struct IfaceDiagnosis {
|
|
pub name: String,
|
|
pub mac: String,
|
|
pub mode: String,
|
|
pub monitor_supported: bool,
|
|
pub driver: Option<String>,
|
|
pub up: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, serde::Serialize)]
|
|
pub struct DoctorReport {
|
|
pub version: String,
|
|
pub platform: String,
|
|
pub root: bool,
|
|
pub iw: bool,
|
|
pub ip: bool,
|
|
pub curl: bool,
|
|
pub capture_backend: String,
|
|
pub audit_path: String,
|
|
pub audit_ok: bool,
|
|
pub interfaces: Vec<IfaceDiagnosis>,
|
|
pub notes: Vec<String>,
|
|
}
|
|
|
|
pub fn diagnose(version: &str, audit_path: impl AsRef<Path>) -> DoctorReport {
|
|
let audit_path = audit_path.as_ref().to_path_buf();
|
|
let root = netlink::is_root();
|
|
let iw = command_exists("iw");
|
|
let ip = command_exists("ip");
|
|
let curl = command_exists("curl");
|
|
let (audit_ok, mut notes) = audit_status(&audit_path);
|
|
|
|
let all: Vec<Interface> = iface::list_all().unwrap_or_default();
|
|
let interfaces = all
|
|
.into_iter()
|
|
.filter(|i| i.phy.is_some())
|
|
.map(|i| IfaceDiagnosis {
|
|
monitor_supported: netlink::monitor_supported(&i.name),
|
|
driver: netlink::driver_name(&i.name),
|
|
name: i.name,
|
|
mac: i.mac,
|
|
mode: format!("{:?}", i.mode).to_lowercase(),
|
|
up: i.up,
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
if !root {
|
|
notes.push(
|
|
"sem root: modo monitor, captura e injeção vão falhar (use sudo ou o app desktop)"
|
|
.into(),
|
|
);
|
|
}
|
|
if !iw {
|
|
notes.push("`iw` não encontrado no PATH — instale iw/iproute2".into());
|
|
}
|
|
if !ip {
|
|
notes.push("`ip` não encontrado no PATH — instale iproute2".into());
|
|
}
|
|
if interfaces.is_empty() {
|
|
notes.push("nenhuma interface Wi-Fi encontrada".into());
|
|
} else if !interfaces.iter().any(|i| i.monitor_supported) {
|
|
notes.push("nenhuma interface anuncia suporte a modo monitor".into());
|
|
}
|
|
if !curl {
|
|
notes.push("`curl` não encontrado — a auto-atualização não funciona".into());
|
|
}
|
|
if !audit_ok {
|
|
notes.push("a trilha de auditoria existente está corrompida".into());
|
|
}
|
|
|
|
DoctorReport {
|
|
version: version.to_string(),
|
|
platform: format!("{}-{}", std::env::consts::OS, std::env::consts::ARCH),
|
|
root,
|
|
iw,
|
|
ip,
|
|
curl,
|
|
capture_backend: if cfg!(target_os = "linux") {
|
|
"AF_PACKET (Linux)".into()
|
|
} else {
|
|
"indisponível nesta plataforma".into()
|
|
},
|
|
audit_path: audit_path.display().to_string(),
|
|
audit_ok,
|
|
interfaces,
|
|
notes,
|
|
}
|
|
}
|
|
|
|
fn audit_status(path: &PathBuf) -> (bool, Vec<String>) {
|
|
let mut notes = Vec::new();
|
|
if !path.exists() {
|
|
notes.push(format!(
|
|
"trilha de auditoria ainda não criada em {}",
|
|
path.display()
|
|
));
|
|
return (true, notes);
|
|
}
|
|
match AuditLog::verify(path) {
|
|
Ok(None) => (true, notes),
|
|
Ok(Some(idx)) => {
|
|
notes.push(format!("cadeia de auditoria rompida na entrada {idx}"));
|
|
(false, notes)
|
|
}
|
|
Err(e) => {
|
|
notes.push(format!("não foi possível ler a trilha de auditoria: {e}"));
|
|
(false, notes)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use tempfile::TempDir;
|
|
|
|
#[test]
|
|
fn reports_environment_without_panicking() {
|
|
let dir = TempDir::new().unwrap();
|
|
let report = diagnose("9.9.9", dir.path().join("nao-existe.log"));
|
|
assert_eq!(report.version, "9.9.9");
|
|
assert!(report.audit_ok);
|
|
assert!(report.platform.contains('-'));
|
|
assert!(!report.capture_backend.is_empty());
|
|
}
|
|
}
|