- engine.rs: expõe Evil Twin (PSK/Enterprise), crack com regras/máscara,
relatório e exportação .hc22000 pros três front-ends (CLI, servidor web
da UI, Tauri). set_monitor() agora solta a interface do NetworkManager
antes de ativar modo monitor e devolve ao desativar — sem isso o NM
reafirma a própria conexão por cima e a captura cai sozinha pouco
depois ("está em modo managed"). Evil Twin grava um arquivo de estado
em disco antes de soltar as interfaces; se o processo morrer no meio
(Ctrl+C sem handler, kill -9, queda de energia), o próximo início do
engine desfaz tudo sozinho (self_heal_eviltwin_leftovers) — sem isso as
duas interfaces ficavam presas em managed=no pro NetworkManager pra
sempre e o Wi-Fi inteiro sumia do painel do sistema. hostapd/hostapd-wpe
que morrem na hora de subir agora reportam a saída real capturada em
log, em vez de um processo "<defunct>" mudo. Novo
ScanSnapshot.warning: avisa quando a sessão roda >12s sem receber
nenhum quadro — sintoma de placa que aceita ativar modo monitor sem
entregar captura de verdade (comum em Wi-Fi interno de notebook).
- sabia-cli (main.rs/web/mod.rs) e o app desktop (Tauri main.rs) ganham
as rotas/comandos equivalentes pra tudo isso.
713 lines
20 KiB
Rust
713 lines
20 KiB
Rust
// Evita a janela de console extra no Windows em release.
|
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|
|
|
use sabia_core::engine::{Engine, ScanOptions};
|
|
use std::sync::Arc;
|
|
|
|
struct State(Arc<Engine>);
|
|
|
|
#[tauri::command]
|
|
fn version(state: tauri::State<'_, State>) -> String {
|
|
state.0.version().to_string()
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn list_ifaces(state: tauri::State<'_, State>) -> Result<serde_json::Value, String> {
|
|
let list = state.0.list_ifaces().map_err(|e| e.to_string())?;
|
|
serde_json::to_value(list).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn set_monitor(state: tauri::State<'_, State>, iface: String, enable: bool) -> Result<(), String> {
|
|
state
|
|
.0
|
|
.set_monitor(&iface, enable)
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn start_scan(state: tauri::State<'_, State>, iface: String) -> Result<(), String> {
|
|
state.0.start_scan(&iface).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn stop_scan(state: tauri::State<'_, State>, iface: String) {
|
|
state.0.stop_scan(&iface);
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn scan_results(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
) -> Result<serde_json::Value, String> {
|
|
serde_json::to_value(state.0.scan_results(&iface)).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn scan_snapshot(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
) -> Result<serde_json::Value, String> {
|
|
let snapshot = state.0.scan_snapshot(&iface).map_err(|e| e.to_string())?;
|
|
serde_json::to_value(snapshot).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn scan_clients(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
) -> Result<serde_json::Value, String> {
|
|
serde_json::to_value(state.0.scan_clients(&iface)).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn handshakes(
|
|
state: tauri::State<'_, State>,
|
|
iface: Option<String>,
|
|
) -> Result<serde_json::Value, String> {
|
|
serde_json::to_value(state.0.handshakes(iface.as_deref())).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn export_handshake(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
bssid: String,
|
|
station: String,
|
|
out: String,
|
|
) -> Result<serde_json::Value, String> {
|
|
let report = state
|
|
.0
|
|
.export_handshake(&iface, &bssid, &station, &out)
|
|
.map_err(|e| e.to_string())?;
|
|
serde_json::to_value(report).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn export_all_handshakes(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
out: String,
|
|
) -> Result<serde_json::Value, String> {
|
|
let report = state
|
|
.0
|
|
.export_all_handshakes(&iface, &out)
|
|
.map_err(|e| e.to_string())?;
|
|
serde_json::to_value(report).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn start_crack(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
wordlist: Option<String>,
|
|
bssid: Option<String>,
|
|
station: Option<String>,
|
|
rules: Option<bool>,
|
|
) -> Result<String, String> {
|
|
let rules = rules.unwrap_or(false);
|
|
let result = match wordlist.filter(|w| !w.trim().is_empty()) {
|
|
Some(wordlist) => state.0.start_crack(
|
|
&iface,
|
|
bssid.as_deref(),
|
|
station.as_deref(),
|
|
&wordlist,
|
|
rules,
|
|
),
|
|
None => state
|
|
.0
|
|
.start_crack_default(&iface, bssid.as_deref(), station.as_deref(), rules),
|
|
};
|
|
result.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn start_crack_mask(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
mask: String,
|
|
bssid: Option<String>,
|
|
station: Option<String>,
|
|
max_candidates: Option<u64>,
|
|
) -> Result<String, String> {
|
|
state
|
|
.0
|
|
.start_crack_mask(
|
|
&iface,
|
|
bssid.as_deref(),
|
|
station.as_deref(),
|
|
&mask,
|
|
max_candidates.unwrap_or(50_000_000),
|
|
)
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn crack_job(state: tauri::State<'_, State>, id: String) -> Result<serde_json::Value, String> {
|
|
match state.0.crack_job(&id) {
|
|
Some(job) => serde_json::to_value(job).map_err(|e| e.to_string()),
|
|
None => Err("job não encontrado".into()),
|
|
}
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn crack_jobs(state: tauri::State<'_, State>) -> Result<serde_json::Value, String> {
|
|
serde_json::to_value(state.0.crack_jobs()).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn deauth(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
bssid: String,
|
|
station: Option<String>,
|
|
count: Option<u32>,
|
|
reason: Option<u16>,
|
|
) -> Result<serde_json::Value, String> {
|
|
let report = state
|
|
.0
|
|
.deauth(
|
|
&iface,
|
|
&bssid,
|
|
station.as_deref(),
|
|
count.unwrap_or(5),
|
|
reason.unwrap_or(7),
|
|
)
|
|
.map_err(|e| e.to_string())?;
|
|
serde_json::to_value(report).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn deauth_directional(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
bssid: String,
|
|
station: Option<String>,
|
|
count: Option<u32>,
|
|
reason: Option<u16>,
|
|
direction: Option<String>,
|
|
frame_kind: Option<String>,
|
|
) -> Result<serde_json::Value, String> {
|
|
let report = state
|
|
.0
|
|
.deauth_directional(
|
|
&iface,
|
|
&bssid,
|
|
station.as_deref(),
|
|
count.unwrap_or(5),
|
|
reason.unwrap_or(7),
|
|
direction.as_deref().unwrap_or("ap"),
|
|
frame_kind.as_deref().unwrap_or("deauth"),
|
|
)
|
|
.map_err(|e| e.to_string())?;
|
|
serde_json::to_value(report).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn start_deauth_job(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
bssid: String,
|
|
station: Option<String>,
|
|
reason: Option<u16>,
|
|
direction: Option<String>,
|
|
frame_kind: Option<String>,
|
|
interval_ms: Option<u64>,
|
|
max_secs: Option<u64>,
|
|
stop_on_handshake: Option<bool>,
|
|
) -> Result<String, String> {
|
|
state
|
|
.0
|
|
.start_deauth_job(
|
|
&iface,
|
|
&bssid,
|
|
station.as_deref(),
|
|
reason.unwrap_or(7),
|
|
direction.as_deref().unwrap_or("both"),
|
|
frame_kind.as_deref().unwrap_or("deauth"),
|
|
interval_ms.unwrap_or(120),
|
|
max_secs,
|
|
stop_on_handshake.unwrap_or(false),
|
|
)
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn stop_deauth_job(state: tauri::State<'_, State>, id: String) -> Result<(), String> {
|
|
state.0.stop_deauth_job(&id).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn deauth_job(state: tauri::State<'_, State>, id: String) -> Result<serde_json::Value, String> {
|
|
match state.0.deauth_job(&id) {
|
|
Some(job) => serde_json::to_value(job).map_err(|e| e.to_string()),
|
|
None => Err("job de deauth não encontrado".into()),
|
|
}
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn deauth_jobs(
|
|
state: tauri::State<'_, State>,
|
|
iface: Option<String>,
|
|
) -> Result<serde_json::Value, String> {
|
|
let jobs = state.0.deauth_jobs();
|
|
let jobs: Vec<_> = match iface {
|
|
Some(iface) => jobs
|
|
.into_iter()
|
|
.filter(|j| j.iface.eq_ignore_ascii_case(&iface))
|
|
.collect(),
|
|
None => jobs,
|
|
};
|
|
serde_json::to_value(jobs).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn capture_handshake(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
bssid: String,
|
|
station: Option<String>,
|
|
seconds: Option<u64>,
|
|
) -> Result<String, String> {
|
|
state
|
|
.0
|
|
.capture_handshake(&iface, &bssid, station.as_deref(), seconds)
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn report(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
) -> Result<serde_json::Value, String> {
|
|
let model = state.0.report(&iface).map_err(|e| e.to_string())?;
|
|
serde_json::to_value(model).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn report_html(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
inline: Option<bool>,
|
|
) -> Result<String, String> {
|
|
let model = state.0.report(&iface).map_err(|e| e.to_string())?;
|
|
let options = sabia_core::report::ReportOptions {
|
|
inline_pagedjs: inline.unwrap_or(false),
|
|
..Default::default()
|
|
};
|
|
Ok(sabia_core::report::render(&model, options))
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn export_hc22000(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
bssid: String,
|
|
station: String,
|
|
out: String,
|
|
) -> Result<serde_json::Value, String> {
|
|
let report = state
|
|
.0
|
|
.export_hc22000(&iface, &bssid, &station, &out)
|
|
.map_err(|e| e.to_string())?;
|
|
serde_json::to_value(report).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn export_all_hc22000(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
out: String,
|
|
) -> Result<serde_json::Value, String> {
|
|
let report = state
|
|
.0
|
|
.export_all_hc22000(&iface, &out)
|
|
.map_err(|e| e.to_string())?;
|
|
serde_json::to_value(report).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn pmkid_hunt(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
bssid: String,
|
|
attempts: Option<u32>,
|
|
) -> Result<serde_json::Value, String> {
|
|
let report = state
|
|
.0
|
|
.pmkid_hunt(&iface, &bssid, attempts.unwrap_or(5))
|
|
.map_err(|e| e.to_string())?;
|
|
serde_json::to_value(report).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn pmf_test(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
bssid: String,
|
|
station: String,
|
|
reason: Option<u16>,
|
|
) -> Result<serde_json::Value, String> {
|
|
let report = state
|
|
.0
|
|
.pmf_test(&iface, &bssid, &station, reason.unwrap_or(7))
|
|
.map_err(|e| e.to_string())?;
|
|
serde_json::to_value(report).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn wps_tools(state: tauri::State<'_, State>) -> Result<serde_json::Value, String> {
|
|
serde_json::to_value(state.0.wps_tools()).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn start_wps(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
bssid: String,
|
|
channel: u32,
|
|
tool: String,
|
|
pin: Option<String>,
|
|
) -> Result<String, String> {
|
|
state
|
|
.0
|
|
.start_wps(&iface, &bssid, channel, &tool, pin.as_deref())
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn wps_job(state: tauri::State<'_, State>, id: String) -> Result<serde_json::Value, String> {
|
|
match state.0.wps_job(&id) {
|
|
Some(job) => serde_json::to_value(job).map_err(|e| e.to_string()),
|
|
None => Err("job WPS não encontrado".into()),
|
|
}
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn wps_jobs(state: tauri::State<'_, State>) -> Result<serde_json::Value, String> {
|
|
serde_json::to_value(state.0.wps_jobs()).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn stop_wps(state: tauri::State<'_, State>, id: String) -> Result<(), String> {
|
|
state.0.stop_wps(&id).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn start_evil_twin(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
ap_iface: String,
|
|
bssid: String,
|
|
portal_port: Option<u16>,
|
|
deauth: Option<bool>,
|
|
) -> Result<String, String> {
|
|
state
|
|
.0
|
|
.start_evil_twin(
|
|
&iface,
|
|
&ap_iface,
|
|
&bssid,
|
|
portal_port.unwrap_or(8080),
|
|
deauth.unwrap_or(false),
|
|
)
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn eviltwin_job(state: tauri::State<'_, State>, id: String) -> Result<serde_json::Value, String> {
|
|
match state.0.eviltwin_job(&id) {
|
|
Some(job) => serde_json::to_value(job).map_err(|e| e.to_string()),
|
|
None => Err("job de Evil Twin não encontrado".into()),
|
|
}
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn eviltwin_jobs(state: tauri::State<'_, State>) -> Result<serde_json::Value, String> {
|
|
serde_json::to_value(state.0.eviltwin_jobs()).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn stop_evil_twin(state: tauri::State<'_, State>, id: String) -> Result<(), String> {
|
|
state.0.stop_evil_twin(&id).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn start_evil_twin_enterprise(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
ap_iface: String,
|
|
bssid: String,
|
|
) -> Result<String, String> {
|
|
state
|
|
.0
|
|
.start_evil_twin_enterprise(&iface, &ap_iface, &bssid)
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn eviltwin_enterprise_job(
|
|
state: tauri::State<'_, State>,
|
|
id: String,
|
|
) -> Result<serde_json::Value, String> {
|
|
match state.0.enterprise_twin_job(&id) {
|
|
Some(job) => serde_json::to_value(job).map_err(|e| e.to_string()),
|
|
None => Err("job de Evil Twin Enterprise não encontrado".into()),
|
|
}
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn eviltwin_enterprise_jobs(state: tauri::State<'_, State>) -> Result<serde_json::Value, String> {
|
|
serde_json::to_value(state.0.enterprise_twin_jobs()).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn stop_evil_twin_enterprise(state: tauri::State<'_, State>, id: String) -> Result<(), String> {
|
|
state
|
|
.0
|
|
.stop_evil_twin_enterprise(&id)
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn start_scan_opts(
|
|
state: tauri::State<'_, State>,
|
|
iface: String,
|
|
channel: Option<u32>,
|
|
band: Option<String>,
|
|
hop_ms: Option<u64>,
|
|
pcap_out: Option<String>,
|
|
seconds: Option<u64>,
|
|
) -> Result<(), String> {
|
|
let options = ScanOptions {
|
|
channel,
|
|
band,
|
|
hop_ms: hop_ms.unwrap_or(350),
|
|
pcap_out: pcap_out.map(Into::into),
|
|
duration_secs: seconds,
|
|
};
|
|
state
|
|
.0
|
|
.start_scan_with(&iface, options)
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn update_check(state: tauri::State<'_, State>) -> Result<serde_json::Value, String> {
|
|
let status = state.0.update_check().map_err(|e| e.to_string())?;
|
|
serde_json::to_value(status).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn update_apply(state: tauri::State<'_, State>) -> Result<String, String> {
|
|
state.0.update_apply().map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// `true` quando o processo já tem privilégio suficiente pra tudo que o
|
|
/// Sabiá faz (modo monitor, captura crua). No Windows o manifesto já
|
|
/// exige administrador antes mesmo do app abrir, então aqui é sempre `true`.
|
|
#[tauri::command]
|
|
fn is_elevated() -> bool {
|
|
#[cfg(unix)]
|
|
{
|
|
unsafe { libc::geteuid() == 0 }
|
|
}
|
|
#[cfg(windows)]
|
|
{
|
|
true
|
|
}
|
|
}
|
|
|
|
/// Reabre o app com privilégios de administrador (Linux/macOS). O app novo
|
|
/// encerra este quando abre; se a autorização falhar ou for cancelada, a
|
|
/// janela continua aqui e a interface mostra o motivo.
|
|
#[tauri::command]
|
|
fn relaunch_elevated(app: tauri::AppHandle) -> Result<(), String> {
|
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
{
|
|
do_relaunch_elevated(app)
|
|
}
|
|
#[cfg(windows)]
|
|
{
|
|
let _ = app;
|
|
Err("o Sabiá já roda como administrador no Windows".into())
|
|
}
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
fn do_relaunch_elevated(app: tauri::AppHandle) -> Result<(), String> {
|
|
use tauri::Emitter;
|
|
let exe = elevated_exe()?;
|
|
let log = std::env::temp_dir().join(format!("sabia-elevate-{}.log", std::process::id()));
|
|
let _ = std::fs::write(&log, b"");
|
|
let out = std::fs::OpenOptions::new()
|
|
.append(true)
|
|
.open(&log)
|
|
.map_err(|e| e.to_string())?;
|
|
let err = out.try_clone().map_err(|e| e.to_string())?;
|
|
let pid = std::process::id().to_string();
|
|
|
|
#[cfg(target_os = "linux")]
|
|
let mut cmd = {
|
|
let mut c = std::process::Command::new("pkexec");
|
|
c.arg("/usr/bin/env");
|
|
for k in [
|
|
"DISPLAY",
|
|
"WAYLAND_DISPLAY",
|
|
"XAUTHORITY",
|
|
"XDG_RUNTIME_DIR",
|
|
"DBUS_SESSION_BUS_ADDRESS",
|
|
"GDK_BACKEND",
|
|
] {
|
|
if let Ok(v) = std::env::var(k) {
|
|
c.arg(format!("{k}={v}"));
|
|
}
|
|
}
|
|
c.arg(format!("SABIA_REPLACE_PID={pid}"));
|
|
c.arg(&exe);
|
|
c
|
|
};
|
|
#[cfg(target_os = "macos")]
|
|
let mut cmd = {
|
|
let shell = format!(
|
|
"SABIA_REPLACE_PID={} '{}' >/dev/null 2>&1 &",
|
|
pid,
|
|
exe.display()
|
|
);
|
|
let script = format!(
|
|
"do shell script \"{}\" with administrator privileges",
|
|
shell.replace('\\', "\\\\").replace('"', "\\\"")
|
|
);
|
|
let mut c = std::process::Command::new("osascript");
|
|
c.args(["-e", &script]);
|
|
c
|
|
};
|
|
cmd.stdout(out).stderr(err);
|
|
let mut child = cmd
|
|
.spawn()
|
|
.map_err(|e| format!("não foi possível pedir privilégios: {e}"))?;
|
|
std::thread::spawn(move || {
|
|
let status = child.wait();
|
|
if matches!(&status, Ok(s) if s.success()) {
|
|
let _ = std::fs::remove_file(&log);
|
|
return;
|
|
}
|
|
let detail = std::fs::read_to_string(&log).unwrap_or_default();
|
|
let msg = match status.ok().and_then(|s| s.code()) {
|
|
Some(126) => "Autorização cancelada.".to_string(),
|
|
Some(127) => "Não foi possível autenticar como administrador.".to_string(),
|
|
_ => format!(
|
|
"Não foi possível reiniciar como administrador. {}",
|
|
detail.lines().last().unwrap_or("").trim()
|
|
),
|
|
};
|
|
let _ = app.emit("elevate-failed", msg);
|
|
let _ = std::fs::remove_file(&log);
|
|
});
|
|
Ok(())
|
|
}
|
|
|
|
/// Caminho que o root consegue executar. No AppImage, `current_exe` aponta
|
|
/// pra dentro de um ponto de montagem FUSE do usuário, inacessível ao root:
|
|
/// usa o próprio arquivo `.AppImage` (variável `APPIMAGE`).
|
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
fn elevated_exe() -> Result<std::path::PathBuf, String> {
|
|
if let Some(p) = std::env::var_os("APPIMAGE")
|
|
.map(std::path::PathBuf::from)
|
|
.filter(|p| p.is_file())
|
|
{
|
|
return Ok(p);
|
|
}
|
|
std::env::current_exe().map_err(|e| e.to_string())
|
|
}
|
|
|
|
fn main() {
|
|
let engine = Arc::new(Engine::new());
|
|
tauri::Builder::default()
|
|
.manage(State(engine))
|
|
.setup(|app| {
|
|
// Quando abrimos elevados a pedido do app normal, ele fecha o app antigo.
|
|
#[cfg(unix)]
|
|
if let Some(pid) = std::env::var("SABIA_REPLACE_PID")
|
|
.ok()
|
|
.and_then(|p| p.parse::<i32>().ok())
|
|
{
|
|
std::thread::spawn(move || {
|
|
std::thread::sleep(std::time::Duration::from_millis(1200));
|
|
// SAFETY: só envia SIGTERM ao processo anterior do próprio usuário.
|
|
unsafe {
|
|
if libc::geteuid() == 0 {
|
|
libc::kill(pid, libc::SIGTERM);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Captura crua e modo monitor exigem root: pede elevação assim
|
|
// que o app abre, sem esperar o usuário procurar um botão. Se
|
|
// a autorização falhar, a janela continua aberta e a interface
|
|
// mostra o aviso (com um botão pra tentar de novo).
|
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
if unsafe { libc::geteuid() != 0 } && std::env::var("SABIA_REPLACE_PID").is_err() {
|
|
let handle = app.handle().clone();
|
|
std::thread::spawn(move || {
|
|
let _ = do_relaunch_elevated(handle);
|
|
});
|
|
}
|
|
Ok(())
|
|
})
|
|
.invoke_handler(tauri::generate_handler![
|
|
version,
|
|
list_ifaces,
|
|
set_monitor,
|
|
start_scan,
|
|
stop_scan,
|
|
scan_results,
|
|
scan_snapshot,
|
|
scan_clients,
|
|
handshakes,
|
|
export_handshake,
|
|
export_all_handshakes,
|
|
start_crack,
|
|
start_crack_mask,
|
|
crack_job,
|
|
crack_jobs,
|
|
deauth,
|
|
deauth_directional,
|
|
start_deauth_job,
|
|
stop_deauth_job,
|
|
deauth_job,
|
|
deauth_jobs,
|
|
capture_handshake,
|
|
report,
|
|
report_html,
|
|
export_hc22000,
|
|
export_all_hc22000,
|
|
pmkid_hunt,
|
|
pmf_test,
|
|
wps_tools,
|
|
start_wps,
|
|
wps_job,
|
|
wps_jobs,
|
|
stop_wps,
|
|
start_evil_twin,
|
|
eviltwin_job,
|
|
eviltwin_jobs,
|
|
stop_evil_twin,
|
|
start_evil_twin_enterprise,
|
|
eviltwin_enterprise_job,
|
|
eviltwin_enterprise_jobs,
|
|
stop_evil_twin_enterprise,
|
|
start_scan_opts,
|
|
update_check,
|
|
update_apply,
|
|
is_elevated,
|
|
relaunch_elevated,
|
|
])
|
|
.run(tauri::generate_context!())
|
|
.expect("erro ao iniciar o Sabiá");
|
|
}
|