From a8cbfee03b5086fe82848bedd50c454bc0eeabee Mon Sep 17 00:00:00 2001 From: Esdras Renan Date: Wed, 12 Nov 2025 10:26:46 -0300 Subject: [PATCH] refactor: preflight ACL repair for RustDesk --- apps/desktop/src-tauri/src/rustdesk.rs | 117 +++++++++---------------- 1 file changed, 43 insertions(+), 74 deletions(-) diff --git a/apps/desktop/src-tauri/src/rustdesk.rs b/apps/desktop/src-tauri/src/rustdesk.rs index 9e9f97d..d407c46 100644 --- a/apps/desktop/src-tauri/src/rustdesk.rs +++ b/apps/desktop/src-tauri/src/rustdesk.rs @@ -64,6 +64,12 @@ pub fn ensure_rustdesk( let _guard = PROVISION_MUTEX.lock(); log_event("Iniciando preparo do RustDesk"); + if let Err(error) = ensure_localservice_writable_preflight() { + log_event(&format!( + "Aviso: não foi possível preparar ACL do LocalService ({error}). Continuando mesmo assim; o serviço pode não aplicar a senha." + )); + } + let exe_path = detect_executable_path(); let (installed_version, freshly_installed) = ensure_installed(&exe_path)?; log_event(if freshly_installed { @@ -478,16 +484,11 @@ fn remote_id_directories() -> Vec { fn ensure_password_files(secret: &str) -> Result<(), String> { let mut errors = Vec::new(); - let mut need_acl_fix = false; for dir in remote_id_directories() { let password_path = dir.join("RustDesk.toml"); if let Err(error) = write_toml_kv(&password_path, "password", secret) { - if is_localservice_permission_issue(&dir, &error) { - need_acl_fix = true; - } else { - errors.push(format!("{} -> {}", password_path.display(), error)); - } + errors.push(format!("{} -> {}", password_path.display(), error)); } else { log_event(&format!( "Senha escrita via fallback em {}", @@ -501,9 +502,6 @@ fn ensure_password_files(secret: &str) -> Result<(), String> { "Falha ao ajustar verification-method em {}: {error}", local_path.display() )); - if is_localservice_permission_issue(&dir, &error) { - need_acl_fix = true; - } } else { log_event(&format!( "verification-method atualizado para use-both-passwords em {}", @@ -512,40 +510,6 @@ fn ensure_password_files(secret: &str) -> Result<(), String> { } } - if need_acl_fix { - log_event("Tentando corrigir ACL do perfil LocalService via UAC..."); - fix_localservice_acl().map_err(|error| format!("fix acl: {error}"))?; - - for dir in remote_id_directories() { - if !is_localservice_dir(&dir) { - continue; - } - let password_path = dir.join("RustDesk.toml"); - if let Err(error) = write_toml_kv(&password_path, "password", secret) { - errors.push(format!("{} -> {}", password_path.display(), error)); - } else { - log_event(&format!( - "Senha escrita via fallback em {}", - password_path.display() - )); - } - - let local_path = dir.join("RustDesk_local.toml"); - if let Err(error) = write_toml_kv(&local_path, "verification-method", "use-both-passwords") { - errors.push(format!("{} -> {}", local_path.display(), error)); - log_event(&format!( - "Falha ao ajustar verification-method em {} mesmo após ACL: {error}", - local_path.display() - )); - } else { - log_event(&format!( - "verification-method atualizado para use-both-passwords em {}", - local_path.display() - )); - } - } - } - if errors.is_empty() { Ok(()) } else { @@ -620,20 +584,44 @@ fn replicate_password_artifacts() -> io::Result<()> { } fn run_powershell_elevated(script: &str) -> Result<(), String> { - let tmp = env::temp_dir().join("raven_fix_acl.ps1"); - fs::write(&tmp, script).map_err(|error| format!("write ps1: {error}"))?; - let args = format!( - "-NoProfile -ExecutionPolicy Bypass -Command \"Start-Process -FilePath PowerShell -Verb RunAs -WindowStyle Hidden -Wait -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','{}'\"", - tmp.display() + use std::io::Write; + + let temp_dir = env::temp_dir(); + let payload = temp_dir.join("raven_payload.ps1"); + fs::write(&payload, script).map_err(|error| format!("write payload: {error}"))?; + + let launcher = temp_dir.join("raven_launcher.ps1"); + let launcher_body = format!( + r#" +$ErrorActionPreference='Stop' +$psi = New-Object System.Diagnostics.ProcessStartInfo +$psi.FileName = 'powershell.exe' +$psi.Arguments = '-NoProfile -ExecutionPolicy Bypass -File "{payload}"' +$psi.Verb = 'runas' +$psi.WindowStyle = 'Hidden' +$process = [System.Diagnostics.Process]::Start($psi) +$process.WaitForExit() +exit $process.ExitCode +"#, + payload = payload.display() ); + fs::write(&launcher, launcher_body).map_err(|error| format!("write launcher: {error}"))?; + let status = Command::new("powershell") - .arg(args) .creation_flags(CREATE_NO_WINDOW) - .stdout(Stdio::null()) - .stderr(Stdio::null()) + .args([ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + &launcher.to_string_lossy(), + ]) .status() .map_err(|error| format!("spawn ps: {error}"))?; - let _ = fs::remove_file(&tmp); + + let _ = fs::remove_file(&launcher); + let _ = fs::remove_file(&payload); + if status.success() { Ok(()) } else { @@ -647,8 +635,10 @@ fn fix_localservice_acl() -> Result<(), String> { r#" $ErrorActionPreference = 'Stop' if (-not (Test-Path '{target}')) {{ New-Item -ItemType Directory -Force -Path '{target}' | Out-Null }} +$admins = (New-Object Security.Principal.SecurityIdentifier('S-1-5-32-544')).Translate([Security.Principal.NTAccount]).Value +$localSvc = (New-Object Security.Principal.SecurityIdentifier('S-1-5-19')).Translate([Security.Principal.NTAccount]).Value takeown /F '{target}' /R /D Y | Out-Null -icacls '{target}' /grant *S-1-5-32-544:(OI)(CI)F /T /C | Out-Null +icacls '{target}' /grant "${{admins}}":(OI)(CI)F "${{localSvc}}":(OI)(CI)F /T /C | Out-Null "# ); run_powershell_elevated(&script) @@ -772,27 +762,6 @@ fn run_with_args(exe_path: &Path, args: &[&str]) -> Result<(), RustdeskError> { Ok(()) } -fn is_localservice_dir(path: &Path) -> bool { - path.as_os_str() - .to_string_lossy() - .to_lowercase() - .contains("serviceprofiles\\localservice") -} - -fn is_localservice_permission_issue(dir: &Path, error: &io::Error) -> bool { - if !is_localservice_dir(dir) { - return false; - } - if let Some(code) = error.raw_os_error() { - return code == 5 || code == 183; - } - let message = error.to_string().to_lowercase(); - message.contains("permission denied") - || message.contains("acesso negado") - || message.contains("os error 5") - || message.contains("os error 183") -} - fn hidden_command(program: impl AsRef) -> Command { let mut cmd = Command::new(program); cmd.creation_flags(CREATE_NO_WINDOW);