diff --git a/apps/desktop/src-tauri/src/rustdesk.rs b/apps/desktop/src-tauri/src/rustdesk.rs index 00b0e40..b7b3493 100644 --- a/apps/desktop/src-tauri/src/rustdesk.rs +++ b/apps/desktop/src-tauri/src/rustdesk.rs @@ -42,6 +42,11 @@ const RUSTDESK_CONFIG_FILES: [&str; 6] = [ "passwd", "passwd.txt", ]; +const PROPAGATION_FILES: [&str; 3] = [ + "RustDesk.toml", + "RustDesk_local.toml", + "RustDesk2.toml", +]; const CREATE_NO_WINDOW: u32 = 0x08000000; static PROVISION_MUTEX: Lazy> = Lazy::new(|| Mutex::new(())); @@ -355,9 +360,13 @@ key = "{key}" relay-server = "{host}" custom-rendezvous-server = "{host}" api-server = "https://{host}" +verification-method = "{verification}" +approve-mode = "{approve}" "#, host = SERVER_HOST, - key = SERVER_KEY + key = SERVER_KEY, + verification = SECURITY_VERIFICATION_VALUE, + approve = SECURITY_APPROVE_MODE_VALUE, ) } @@ -432,11 +441,10 @@ fn ensure_service_running(exe_path: &Path) -> Result<(), RustdeskError> { } fn stop_rustdesk_processes() -> Result<(), RustdeskError> { - if let Err(error) = run_sc(&["stop", SERVICE_NAME]) { - match error { - RustdeskError::CommandFailed { status: Some(code), .. } if code == 1062 || code == 1060 => {} - _ => log_event(&format!("Não foi possível parar o serviço RustDesk antes da sincronização: {error}")), - } + if let Err(error) = try_stop_service() { + log_event(&format!( + "Não foi possível parar o serviço RustDesk antes da sincronização: {error}" + )); } let status = hidden_command("taskkill") @@ -455,6 +463,23 @@ fn stop_rustdesk_processes() -> Result<(), RustdeskError> { } } +fn try_stop_service() -> Result<(), RustdeskError> { + match run_sc(&["stop", SERVICE_NAME]) { + Ok(_) => { + thread::sleep(Duration::from_secs(2)); + Ok(()) + } + Err(RustdeskError::CommandFailed { status: Some(code), .. }) if code == 1060 || code == 1062 => Ok(()), + Err(RustdeskError::CommandFailed { status: Some(5), .. }) => { + stop_service_elevated().map_err(|error| RustdeskError::CommandFailed { + command: format!("stop_service_elevated ({error})"), + status: Some(5), + }) + } + Err(error) => Err(error), + } +} + fn run_sc(args: &[&str]) -> Result<(), RustdeskError> { let status = hidden_command("sc") .args(args) @@ -600,6 +625,14 @@ fn ensure_password_files(secret: &str) -> Result<(), String> { )); } + let rustdesk2_path = dir.join("RustDesk2.toml"); + if let Err(error) = enforce_security_in_rustdesk2(&rustdesk2_path) { + log_event(&format!( + "Falha ao ajustar flags no RustDesk2.toml em {}: {error}", + rustdesk2_path.display() + )); + } + if let Err(error) = write_toml_kv(&local_path, "approve-mode", SECURITY_APPROVE_MODE_VALUE) { log_event(&format!( "Falha ao ajustar approve-mode em {}: {error}", @@ -653,16 +686,21 @@ fn enforce_security_flags() -> Result<(), String> { } } +fn enforce_security_in_rustdesk2(path: &Path) -> io::Result<()> { + write_toml_kv(path, "verification-method", SECURITY_VERIFICATION_VALUE)?; + write_toml_kv(path, "approve-mode", SECURITY_APPROVE_MODE_VALUE)?; + Ok(()) +} + fn propagate_password_profile() -> io::Result { let Some(src_dir) = user_appdata_config_dir() else { log_event("AppData do usuário não disponível para copiar RustDesk.toml (propagação ignorada)"); return Ok(false); }; - let files = ["RustDesk.toml", "RustDesk_local.toml"]; let mut propagated = false; - for filename in files { + for filename in PROPAGATION_FILES { let src_path = src_dir.join(filename); if !src_path.exists() { continue; @@ -916,6 +954,18 @@ fn ensure_service_profiles_writable_preflight() -> Result<(), String> { } } +fn stop_service_elevated() -> Result<(), String> { + let script = r#" +$ErrorActionPreference='Stop' +$service = Get-Service -Name 'RustDesk' -ErrorAction SilentlyContinue +if ($service -and $service.Status -ne 'Stopped') { + Stop-Service -Name 'RustDesk' -Force -ErrorAction Stop + $service.WaitForStatus('Stopped','00:00:10') +} +"#; + run_powershell_elevated(script) +} + fn can_write_dir(dir: &Path) -> bool { if fs::create_dir_all(dir).is_err() { return false; @@ -1062,28 +1112,35 @@ fn run_with_args(exe_path: &Path, args: &[&str]) -> Result<(), RustdeskError> { fn log_password_replication(secret: &str) { for dir in remote_id_directories() { + let primary = dir.join("RustDesk.toml"); + log_password_match(&primary, secret); + let local_path = dir.join("RustDesk_local.toml"); - match read_password_from_file(&local_path) { - Some(value) if value == secret => { - log_event(&format!( - "Senha confirmada em {} ({})", - local_path.display(), - mask_secret(&value) - )); - } - Some(value) => { - log_event(&format!( - "Aviso: senha divergente ({}) em {}", - mask_secret(&value), - local_path.display() - )); - } - None => { - log_event(&format!( - "Aviso: chave 'password' não encontrada em {}", - local_path.display() - )); - } + log_password_match(&local_path, secret); + } +} + +fn log_password_match(path: &Path, secret: &str) { + match read_password_from_file(path) { + Some(value) if value == secret => { + log_event(&format!( + "Senha confirmada em {} ({})", + path.display(), + mask_secret(&value) + )); + } + Some(value) => { + log_event(&format!( + "Aviso: senha divergente ({}) em {}", + mask_secret(&value), + path.display() + )); + } + None => { + log_event(&format!( + "Aviso: chave 'password' não encontrada em {}", + path.display() + )); } } } diff --git a/docs/RUSTDESK-PROVISIONING.md b/docs/RUSTDESK-PROVISIONING.md index 4c9853e..01e32dc 100644 --- a/docs/RUSTDESK-PROVISIONING.md +++ b/docs/RUSTDESK-PROVISIONING.md @@ -54,14 +54,18 @@ Fluxo ideal: - Reinicia serviço `RustDesk` (`sc start RustDesk` — pode falhar com `status Some(5)` se não há privilégio admin). Mesmo com falha, a CLI continua e grava o ID nos arquivos. - **Autoelevação única**: na primeira execução do botão “Preparar”, o Raven dispara um PowerShell elevado (`takeown + icacls`) para liberar ACL dos perfis `LocalService` e `LocalSystem`. O sucesso grava `rustdeskAclUnlockedAt` dentro de `%LOCALAPPDATA%\br.com.esdrasrenan.sistemadechamados\machine-agent.json` e cria o flag `rustdesk_acl_unlocked.flag`, evitando novos prompts de UAC nas execuções seguintes. - **Kill/restart seguro**: antes de tocar nos TOML, o Raven roda `sc stop RustDesk` + `taskkill /F /T /IM rustdesk.exe`. Isso garante que nenhum cliente sobrescreva o `RustDesk_local.toml` enquanto aplicamos a senha. - - **Replicação completa de perfis**: após aplicar `--password`, limpamos quaisquer arquivos antigos (`RustDesk.toml`, `RustDesk_local.toml`, `password`, `passwd*`) em `%APPDATA%`, `ProgramData`, `LocalService` e `LocalSystem` e, em seguida, reescrevemos tudo (via `write_toml_kv`). `verification-method = "use-permanent-password"` e `approve-mode = "password"` são aplicados em todos os perfis, junto com a cópia dos artefatos `password/passwd/passwd.txt`. + - **Replicação completa de perfis**: após aplicar `--password`, limpamos quaisquer arquivos antigos (`RustDesk*.toml`, `password`, `passwd*`) em `%APPDATA%`, `ProgramData`, `LocalService` e `LocalSystem` e, em seguida, reescrevemos tudo. Agora os `RustDesk2.toml` herdados recebem `verification-method = "use-permanent-password"` e `approve-mode = "password"` no bloco `[options]` antes mesmo do serviço subir, evitando que o RustDesk volte para "Use both". - **Validação por arquivo**: o Raven agora registra no `rustdesk.log` se o `password` gravado em cada `RustDesk_local.toml` coincide com o PIN configurado. Para inspecionar manualmente, rode no PowerShell (e valide que todas as linhas exibem o PIN esperado): ```powershell $targets = @( + "$env:APPDATA\RustDesk\config\RustDesk.toml", "$env:APPDATA\RustDesk\config\RustDesk_local.toml", + "$env:ProgramData\RustDesk\config\RustDesk.toml", "$env:ProgramData\RustDesk\config\RustDesk_local.toml", + "C:\\Windows\\ServiceProfiles\\LocalService\\AppData\\Roaming\\RustDesk\\config\\RustDesk.toml", "C:\\Windows\\ServiceProfiles\\LocalService\\AppData\\Roaming\\RustDesk\\config\\RustDesk_local.toml", + "C:\\Windows\\System32\\config\\systemprofile\\AppData\\Roaming\\RustDesk\\config\\RustDesk.toml", "C:\\Windows\\System32\\config\\systemprofile\\AppData\\Roaming\\RustDesk\\config\\RustDesk_local.toml" ) foreach ($path in $targets) {