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 SECURITY_VERIFICATION_VALUE: &str = "use-permanent-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;
|
||||
|
||||
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| {
|
||||
let trimmed = raw.trim();
|
||||
if trimmed.is_empty() { None } else { Some(trimmed) }
|
||||
|
|
@ -137,7 +152,10 @@ pub fn ensure_rustdesk(
|
|||
}
|
||||
|
||||
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}")),
|
||||
}
|
||||
|
||||
|
|
@ -707,6 +725,57 @@ fn replicate_password_artifacts() -> io::Result<()> {
|
|||
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> {
|
||||
let temp_dir = env::temp_dir();
|
||||
let payload = temp_dir.join("raven_payload.ps1");
|
||||
|
|
@ -991,6 +1060,65 @@ fn run_with_args(exe_path: &Path, args: &[&str]) -> Result<(), RustdeskError> {
|
|||
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 {
|
||||
let mut cmd = Command::new(program);
|
||||
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.
|
||||
- **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`, 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`).
|
||||
- **Sincronização**: `syncRustdeskAccess(machineToken, info)` chama `/api/machines/remote-access`. Há retries automáticos:
|
||||
```ts
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue