fix: self-heal RustDesk ACL and restart
This commit is contained in:
parent
c1d8181abf
commit
7972ac207d
1 changed files with 140 additions and 34 deletions
|
|
@ -364,10 +364,25 @@ fn derive_numeric_id(machine_id: &str) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_service_running() -> Result<(), RustdeskError> {
|
fn ensure_service_running() -> Result<(), RustdeskError> {
|
||||||
let _ = run_sc(&["stop", SERVICE_NAME]);
|
fn start_sequence() -> Result<(), RustdeskError> {
|
||||||
thread::sleep(Duration::from_secs(2));
|
let _ = run_sc(&["stop", SERVICE_NAME]);
|
||||||
let _ = run_sc(&["config", SERVICE_NAME, &format!("start= {}", "auto")]);
|
thread::sleep(Duration::from_secs(2));
|
||||||
run_sc(&["start", SERVICE_NAME])
|
let _ = run_sc(&["config", SERVICE_NAME, &format!("start= {}", "auto")]);
|
||||||
|
run_sc(&["start", SERVICE_NAME])
|
||||||
|
}
|
||||||
|
|
||||||
|
match start_sequence() {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(RustdeskError::CommandFailed { command, status: Some(5), .. }) => {
|
||||||
|
log_event("SC retornou acesso negado; tentando ajustar ACL do LocalService...");
|
||||||
|
fix_localservice_acl().map_err(|error| RustdeskError::CommandFailed {
|
||||||
|
command: format!("fix_acl ({error})"),
|
||||||
|
status: Some(5),
|
||||||
|
})?;
|
||||||
|
start_sequence()
|
||||||
|
}
|
||||||
|
Err(error) => Err(error),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_sc(args: &[&str]) -> Result<(), RustdeskError> {
|
fn run_sc(args: &[&str]) -> Result<(), RustdeskError> {
|
||||||
|
|
@ -463,10 +478,16 @@ fn remote_id_directories() -> Vec<PathBuf> {
|
||||||
|
|
||||||
fn ensure_password_files(secret: &str) -> Result<(), String> {
|
fn ensure_password_files(secret: &str) -> Result<(), String> {
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
|
let mut need_acl_fix = false;
|
||||||
|
|
||||||
for dir in remote_id_directories() {
|
for dir in remote_id_directories() {
|
||||||
let password_path = dir.join("RustDesk.toml");
|
let password_path = dir.join("RustDesk.toml");
|
||||||
if let Err(error) = write_toml_kv(&password_path, "password", secret) {
|
if let Err(error) = write_toml_kv(&password_path, "password", secret) {
|
||||||
errors.push(format!("{} -> {}", password_path.display(), error));
|
if is_localservice_permission_issue(&dir, &error) {
|
||||||
|
need_acl_fix = true;
|
||||||
|
} else {
|
||||||
|
errors.push(format!("{} -> {}", password_path.display(), error));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log_event(&format!(
|
log_event(&format!(
|
||||||
"Senha escrita via fallback em {}",
|
"Senha escrita via fallback em {}",
|
||||||
|
|
@ -480,6 +501,9 @@ fn ensure_password_files(secret: &str) -> Result<(), String> {
|
||||||
"Falha ao ajustar verification-method em {}: {error}",
|
"Falha ao ajustar verification-method em {}: {error}",
|
||||||
local_path.display()
|
local_path.display()
|
||||||
));
|
));
|
||||||
|
if is_localservice_permission_issue(&dir, &error) {
|
||||||
|
need_acl_fix = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log_event(&format!(
|
log_event(&format!(
|
||||||
"verification-method atualizado para use-both-passwords em {}",
|
"verification-method atualizado para use-both-passwords em {}",
|
||||||
|
|
@ -488,6 +512,40 @@ 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() {
|
if errors.is_empty() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -561,6 +619,41 @@ fn replicate_password_artifacts() -> io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
);
|
||||||
|
let status = Command::new("powershell")
|
||||||
|
.arg(args)
|
||||||
|
.creation_flags(CREATE_NO_WINDOW)
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.status()
|
||||||
|
.map_err(|error| format!("spawn ps: {error}"))?;
|
||||||
|
let _ = fs::remove_file(&tmp);
|
||||||
|
if status.success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("elevated ps exit {:?}", status.code()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fix_localservice_acl() -> Result<(), String> {
|
||||||
|
let target = r"C:\\Windows\\ServiceProfiles\\LocalService\\AppData\\Roaming\\RustDesk\\config";
|
||||||
|
let script = format!(
|
||||||
|
r#"
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
if (-not (Test-Path '{target}')) {{ New-Item -ItemType Directory -Force -Path '{target}' | Out-Null }}
|
||||||
|
takeown /F '{target}' /R /D Y | Out-Null
|
||||||
|
icacls '{target}' /grant *S-1-5-32-544:(OI)(CI)F /T /C | Out-Null
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
run_powershell_elevated(&script)
|
||||||
|
}
|
||||||
|
|
||||||
fn write_remote_id_value(path: &Path, id: &str) -> io::Result<()> {
|
fn write_remote_id_value(path: &Path, id: &str) -> io::Result<()> {
|
||||||
if let Some(parent) = path.parent() {
|
if let Some(parent) = path.parent() {
|
||||||
fs::create_dir_all(parent)?;
|
fs::create_dir_all(parent)?;
|
||||||
|
|
@ -601,38 +694,30 @@ fn write_toml_kv(path: &Path, key: &str, value: &str) -> io::Result<()> {
|
||||||
if let Some(parent) = path.parent() {
|
if let Some(parent) = path.parent() {
|
||||||
fs::create_dir_all(parent)?;
|
fs::create_dir_all(parent)?;
|
||||||
}
|
}
|
||||||
let sanitized = value.replace('\'', "''");
|
let sanitized = value.replace('\\', "\\\\").replace('"', "\\\"");
|
||||||
let replacement = format!("{key} = '{}'\n", sanitized);
|
let replacement = format!("{key} = \"{sanitized}\"\n");
|
||||||
if let Ok(existing) = fs::read_to_string(path) {
|
let existing = fs::read_to_string(path).unwrap_or_default();
|
||||||
let mut replaced = false;
|
let mut replaced = false;
|
||||||
let mut buffer = String::with_capacity(existing.len() + replacement.len());
|
let mut buffer = String::with_capacity(existing.len() + replacement.len());
|
||||||
for line in existing.lines() {
|
for line in existing.lines() {
|
||||||
let trimmed = line.trim_start();
|
let trimmed = line.trim_start();
|
||||||
if trimmed.starts_with(&format!("{key} ")) || trimmed.starts_with(&format!("{key}=")) {
|
if trimmed.starts_with(&format!("{key} ")) || trimmed.starts_with(&format!("{key}=")) {
|
||||||
buffer.push_str(&replacement);
|
|
||||||
replaced = true;
|
|
||||||
} else {
|
|
||||||
buffer.push_str(line);
|
|
||||||
buffer.push('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !replaced {
|
|
||||||
buffer.push_str(&replacement);
|
buffer.push_str(&replacement);
|
||||||
|
replaced = true;
|
||||||
|
} else {
|
||||||
|
buffer.push_str(line);
|
||||||
|
buffer.push('\n');
|
||||||
}
|
}
|
||||||
let mut file = OpenOptions::new()
|
|
||||||
.create(true)
|
|
||||||
.write(true)
|
|
||||||
.truncate(true)
|
|
||||||
.open(path)?;
|
|
||||||
file.write_all(buffer.as_bytes())
|
|
||||||
} else {
|
|
||||||
let mut file = OpenOptions::new()
|
|
||||||
.create(true)
|
|
||||||
.write(true)
|
|
||||||
.truncate(true)
|
|
||||||
.open(path)?;
|
|
||||||
file.write_all(replacement.as_bytes())
|
|
||||||
}
|
}
|
||||||
|
if !replaced {
|
||||||
|
buffer.push_str(&replacement);
|
||||||
|
}
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.write(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(path)?;
|
||||||
|
file.write_all(buffer.as_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_remote_id_from_profiles() -> Option<String> {
|
fn read_remote_id_from_profiles() -> Option<String> {
|
||||||
|
|
@ -687,6 +772,27 @@ fn run_with_args(exe_path: &Path, args: &[&str]) -> Result<(), RustdeskError> {
|
||||||
Ok(())
|
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<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);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue