Stop RustDesk service via UAC and sync RustDesk2 security

This commit is contained in:
Esdras Renan 2025-11-12 14:53:40 -03:00
parent daca17a93d
commit 05a273466a
2 changed files with 91 additions and 30 deletions

View file

@ -42,6 +42,11 @@ const RUSTDESK_CONFIG_FILES: [&str; 6] = [
"passwd", "passwd",
"passwd.txt", "passwd.txt",
]; ];
const PROPAGATION_FILES: [&str; 3] = [
"RustDesk.toml",
"RustDesk_local.toml",
"RustDesk2.toml",
];
const CREATE_NO_WINDOW: u32 = 0x08000000; const CREATE_NO_WINDOW: u32 = 0x08000000;
static PROVISION_MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(())); static PROVISION_MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
@ -355,9 +360,13 @@ key = "{key}"
relay-server = "{host}" relay-server = "{host}"
custom-rendezvous-server = "{host}" custom-rendezvous-server = "{host}"
api-server = "https://{host}" api-server = "https://{host}"
verification-method = "{verification}"
approve-mode = "{approve}"
"#, "#,
host = SERVER_HOST, 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> { fn stop_rustdesk_processes() -> Result<(), RustdeskError> {
if let Err(error) = run_sc(&["stop", SERVICE_NAME]) { if let Err(error) = try_stop_service() {
match error { log_event(&format!(
RustdeskError::CommandFailed { status: Some(code), .. } if code == 1062 || code == 1060 => {} "Não foi possível parar o serviço RustDesk antes da sincronização: {error}"
_ => log_event(&format!("Não foi possível parar o serviço RustDesk antes da sincronização: {error}")), ));
}
} }
let status = hidden_command("taskkill") 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> { fn run_sc(args: &[&str]) -> Result<(), RustdeskError> {
let status = hidden_command("sc") let status = hidden_command("sc")
.args(args) .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) { if let Err(error) = write_toml_kv(&local_path, "approve-mode", SECURITY_APPROVE_MODE_VALUE) {
log_event(&format!( log_event(&format!(
"Falha ao ajustar approve-mode em {}: {error}", "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<bool> { fn propagate_password_profile() -> io::Result<bool> {
let Some(src_dir) = user_appdata_config_dir() else { 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)"); log_event("AppData do usuário não disponível para copiar RustDesk.toml (propagação ignorada)");
return Ok(false); return Ok(false);
}; };
let files = ["RustDesk.toml", "RustDesk_local.toml"];
let mut propagated = false; let mut propagated = false;
for filename in files { for filename in PROPAGATION_FILES {
let src_path = src_dir.join(filename); let src_path = src_dir.join(filename);
if !src_path.exists() { if !src_path.exists() {
continue; 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 { fn can_write_dir(dir: &Path) -> bool {
if fs::create_dir_all(dir).is_err() { if fs::create_dir_all(dir).is_err() {
return false; return false;
@ -1062,28 +1112,35 @@ fn run_with_args(exe_path: &Path, args: &[&str]) -> Result<(), RustdeskError> {
fn log_password_replication(secret: &str) { fn log_password_replication(secret: &str) {
for dir in remote_id_directories() { 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"); let local_path = dir.join("RustDesk_local.toml");
match read_password_from_file(&local_path) { log_password_match(&local_path, secret);
Some(value) if value == secret => { }
log_event(&format!( }
"Senha confirmada em {} ({})",
local_path.display(), fn log_password_match(path: &Path, secret: &str) {
mask_secret(&value) match read_password_from_file(path) {
)); Some(value) if value == secret => {
} log_event(&format!(
Some(value) => { "Senha confirmada em {} ({})",
log_event(&format!( path.display(),
"Aviso: senha divergente ({}) em {}", mask_secret(&value)
mask_secret(&value), ));
local_path.display() }
)); Some(value) => {
} log_event(&format!(
None => { "Aviso: senha divergente ({}) em {}",
log_event(&format!( mask_secret(&value),
"Aviso: chave 'password' não encontrada em {}", path.display()
local_path.display() ));
)); }
} None => {
log_event(&format!(
"Aviso: chave 'password' não encontrada em {}",
path.display()
));
} }
} }
} }

View file

@ -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. - 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. - **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. - **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): - **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 ```powershell
$targets = @( $targets = @(
"$env:APPDATA\RustDesk\config\RustDesk.toml",
"$env:APPDATA\RustDesk\config\RustDesk_local.toml", "$env:APPDATA\RustDesk\config\RustDesk_local.toml",
"$env:ProgramData\RustDesk\config\RustDesk.toml",
"$env:ProgramData\RustDesk\config\RustDesk_local.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\\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" "C:\\Windows\\System32\\config\\systemprofile\\AppData\\Roaming\\RustDesk\\config\\RustDesk_local.toml"
) )
foreach ($path in $targets) { foreach ($path in $targets) {