Purge legacy RustDesk configs before provisioning
This commit is contained in:
parent
ddcff6768d
commit
daca17a93d
2 changed files with 148 additions and 2 deletions
|
|
@ -34,6 +34,14 @@ const ACL_FLAG_FILENAME: &str = "rustdesk_acl_unlocked.flag";
|
||||||
const RUSTDESK_ACL_STORE_KEY: &str = "rustdeskAclUnlockedAt";
|
const RUSTDESK_ACL_STORE_KEY: &str = "rustdeskAclUnlockedAt";
|
||||||
const SECURITY_VERIFICATION_VALUE: &str = "use-permanent-password";
|
const SECURITY_VERIFICATION_VALUE: &str = "use-permanent-password";
|
||||||
const SECURITY_APPROVE_MODE_VALUE: &str = "password";
|
const SECURITY_APPROVE_MODE_VALUE: &str = "password";
|
||||||
|
const RUSTDESK_CONFIG_FILES: [&str; 6] = [
|
||||||
|
"RustDesk.toml",
|
||||||
|
"RustDesk_local.toml",
|
||||||
|
"RustDesk2.toml",
|
||||||
|
"password",
|
||||||
|
"passwd",
|
||||||
|
"passwd.txt",
|
||||||
|
];
|
||||||
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(()));
|
||||||
|
|
@ -93,6 +101,13 @@ pub fn ensure_rustdesk(
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match purge_existing_rustdesk_profiles() {
|
||||||
|
Ok(_) => log_event("Configurações antigas do RustDesk limpas antes da reaplicação"),
|
||||||
|
Err(error) => log_event(&format!(
|
||||||
|
"Aviso: não foi possível limpar completamente os perfis existentes do RustDesk ({error})"
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(value) = config_string.and_then(|raw| {
|
if let Some(value) = config_string.and_then(|raw| {
|
||||||
let trimmed = raw.trim();
|
let trimmed = raw.trim();
|
||||||
if trimmed.is_empty() { None } else { Some(trimmed) }
|
if trimmed.is_empty() { None } else { Some(trimmed) }
|
||||||
|
|
@ -137,7 +152,10 @@ pub fn ensure_rustdesk(
|
||||||
}
|
}
|
||||||
|
|
||||||
match ensure_password_files(&password) {
|
match ensure_password_files(&password) {
|
||||||
Ok(_) => log_event("Senha e flags de segurança gravadas em todos os perfis do RustDesk"),
|
Ok(_) => {
|
||||||
|
log_event("Senha e flags de segurança gravadas em todos os perfis do RustDesk");
|
||||||
|
log_password_replication(&password);
|
||||||
|
}
|
||||||
Err(error) => log_event(&format!("Falha ao persistir senha nos perfis: {error}")),
|
Err(error) => log_event(&format!("Falha ao persistir senha nos perfis: {error}")),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -707,6 +725,57 @@ fn replicate_password_artifacts() -> io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn purge_existing_rustdesk_profiles() -> Result<(), String> {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
let mut cleaned_any = false;
|
||||||
|
|
||||||
|
for dir in remote_id_directories() {
|
||||||
|
match purge_config_dir(&dir) {
|
||||||
|
Ok(true) => {
|
||||||
|
cleaned_any = true;
|
||||||
|
log_event(&format!(
|
||||||
|
"Perfis antigos removidos em {}",
|
||||||
|
dir.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(false) => {}
|
||||||
|
Err(error) => errors.push(format!("{} -> {error}", dir.display())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cleaned_any {
|
||||||
|
Ok(())
|
||||||
|
} else if errors.is_empty() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(errors.join(" | "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn purge_config_dir(dir: &Path) -> Result<bool, io::Error> {
|
||||||
|
if !dir.exists() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut removed = false;
|
||||||
|
fs::create_dir_all(dir)?;
|
||||||
|
|
||||||
|
for name in RUSTDESK_CONFIG_FILES {
|
||||||
|
let path = dir.join(name);
|
||||||
|
if path.is_dir() {
|
||||||
|
fs::remove_dir_all(&path)?;
|
||||||
|
removed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if path.exists() {
|
||||||
|
fs::remove_file(&path)?;
|
||||||
|
removed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(removed)
|
||||||
|
}
|
||||||
|
|
||||||
fn run_powershell_elevated(script: &str) -> Result<(), String> {
|
fn run_powershell_elevated(script: &str) -> Result<(), String> {
|
||||||
let temp_dir = env::temp_dir();
|
let temp_dir = env::temp_dir();
|
||||||
let payload = temp_dir.join("raven_payload.ps1");
|
let payload = temp_dir.join("raven_payload.ps1");
|
||||||
|
|
@ -991,6 +1060,65 @@ fn run_with_args(exe_path: &Path, args: &[&str]) -> Result<(), RustdeskError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn log_password_replication(secret: &str) {
|
||||||
|
for dir in remote_id_directories() {
|
||||||
|
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()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_password_from_file(path: &Path) -> Option<String> {
|
||||||
|
let content = fs::read_to_string(path).ok()?;
|
||||||
|
for line in content.lines() {
|
||||||
|
if let Some(value) = parse_assignment(line, "password") {
|
||||||
|
return Some(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mask_secret(secret: &str) -> String {
|
||||||
|
if secret.is_empty() {
|
||||||
|
return "<vazio>".to_string();
|
||||||
|
}
|
||||||
|
let chars: Vec<char> = secret.chars().collect();
|
||||||
|
if chars.len() <= 4 {
|
||||||
|
return "*".repeat(chars.len());
|
||||||
|
}
|
||||||
|
let prefix: String = chars.iter().take(2).copied().collect();
|
||||||
|
let suffix: String = chars
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.take(2)
|
||||||
|
.copied()
|
||||||
|
.collect::<Vec<char>>()
|
||||||
|
.into_iter()
|
||||||
|
.rev()
|
||||||
|
.collect();
|
||||||
|
format!("{}***{}", prefix, suffix)
|
||||||
|
}
|
||||||
|
|
||||||
fn hidden_command(program: impl AsRef<OsStr>) -> Command {
|
fn hidden_command(program: impl AsRef<OsStr>) -> Command {
|
||||||
let mut cmd = Command::new(program);
|
let mut cmd = Command::new(program);
|
||||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,25 @@ 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`, copiamos `RustDesk.toml` e `RustDesk_local.toml` do `%APPDATA%` para `C:\ProgramData\RustDesk\config`, `LocalService` e `LocalSystem`. Em seguida, escrevemos (via `write_toml_kv`) `password`, `verification-method = "use-permanent-password"` e `approve-mode = "password"` em **todos** os perfis, além de replicar `password/passwd/passwd.txt`.
|
- **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`.
|
||||||
|
- **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_local.toml",
|
||||||
|
"$env:ProgramData\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_local.toml"
|
||||||
|
)
|
||||||
|
foreach ($path in $targets) {
|
||||||
|
if (Test-Path $path) {
|
||||||
|
"`n$path"
|
||||||
|
Select-String -Path $path -Pattern '^password'
|
||||||
|
} else {
|
||||||
|
"`n$path (inexistente)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
- **Reforço contínuo**: toda nova execução do botão “Preparar” repete o ciclo (kill → copiar → gravar flags → `sc start`). Se alguém voltar manualmente para “Use both”, o Raven mata o processo, reescreve os TOML e reinicia o serviço — o RustDesk volta travado na senha permanente com o PIN registrado nos logs (`rustdesk.log`).
|
- **Reforço contínuo**: toda nova execução do botão “Preparar” repete o ciclo (kill → copiar → gravar flags → `sc start`). Se alguém voltar manualmente para “Use both”, o Raven mata o processo, reescreve os TOML e reinicia o serviço — o RustDesk volta travado na senha permanente com o PIN registrado nos logs (`rustdesk.log`).
|
||||||
- **Sincronização**: `syncRustdeskAccess(machineToken, info)` chama `/api/machines/remote-access`. Há retries automáticos:
|
- **Sincronização**: `syncRustdeskAccess(machineToken, info)` chama `/api/machines/remote-access`. Há retries automáticos:
|
||||||
```ts
|
```ts
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue