266 lines
8.5 KiB
Rust
266 lines
8.5 KiB
Rust
#![cfg(target_os = "windows")]
|
|
|
|
use crate::RustdeskProvisioningResult;
|
|
use once_cell::sync::Lazy;
|
|
use parking_lot::Mutex;
|
|
use reqwest::blocking::Client;
|
|
use serde::Deserialize;
|
|
use sha2::{Digest, Sha256};
|
|
use std::env;
|
|
use std::fs::{self, File};
|
|
use std::io::{self, Write};
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::{Command, Stdio};
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
use thiserror::Error;
|
|
|
|
const RELEASES_API: &str = "https://api.github.com/repos/rustdesk/rustdesk/releases/latest";
|
|
const USER_AGENT: &str = "RavenDesktop/1.0";
|
|
const SERVER_HOST: &str = "rust.rever.com.br";
|
|
const SERVER_KEY: &str = "0mxocQKmK6GvTZQYKgjrG9tlNkKOqf81gKgqwAmnZuI=";
|
|
const DEFAULT_PASSWORD: &str = "FMQ9MA>e73r.FI<b*34Vmx_8P";
|
|
const SERVICE_NAME: &str = "RustDesk";
|
|
const CACHE_DIR_NAME: &str = "Rever\\RustDeskCache";
|
|
|
|
static PROVISION_MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum RustdeskError {
|
|
#[error("HTTP error: {0}")]
|
|
Http(#[from] reqwest::Error),
|
|
#[error("I/O error: {0}")]
|
|
Io(#[from] io::Error),
|
|
#[error("Release asset não encontrado para Windows x86_64")]
|
|
AssetMissing,
|
|
#[error("Falha ao executar comando {command}: status {status:?}")]
|
|
CommandFailed { command: String, status: Option<i32> },
|
|
#[error("Falha ao detectar ID do RustDesk")]
|
|
MissingId,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct ReleaseAsset {
|
|
name: String,
|
|
browser_download_url: String,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct ReleaseResponse {
|
|
tag_name: String,
|
|
assets: Vec<ReleaseAsset>,
|
|
}
|
|
|
|
pub fn provision(machine_id: &str) -> Result<RustdeskProvisioningResult, RustdeskError> {
|
|
let _guard = PROVISION_MUTEX.lock();
|
|
let exe_path = detect_executable_path();
|
|
let (installed_version, freshly_installed) = ensure_installed(&exe_path)?;
|
|
let config_path = write_config_files()?;
|
|
apply_config(&exe_path, &config_path)?;
|
|
set_password(&exe_path)?;
|
|
let custom_id = set_custom_id(&exe_path, machine_id)?;
|
|
ensure_service_running()?;
|
|
let reported_id = query_id(&exe_path).unwrap_or(custom_id.clone());
|
|
let version = query_version(&exe_path).ok().or(installed_version);
|
|
|
|
Ok(RustdeskProvisioningResult {
|
|
id: reported_id,
|
|
password: DEFAULT_PASSWORD.to_string(),
|
|
installed_version: version,
|
|
updated: freshly_installed,
|
|
})
|
|
}
|
|
|
|
fn detect_executable_path() -> PathBuf {
|
|
let program_files = env::var("PROGRAMFILES").unwrap_or_else(|_| "C:/Program Files".to_string());
|
|
Path::new(&program_files).join("RustDesk").join("rustdesk.exe")
|
|
}
|
|
|
|
fn ensure_installed(exe_path: &Path) -> Result<(Option<String>, bool), RustdeskError> {
|
|
if exe_path.exists() {
|
|
return Ok((None, false));
|
|
}
|
|
|
|
let cache_root = PathBuf::from(env::var("PROGRAMDATA").unwrap_or_else(|_| "C:/ProgramData".to_string()))
|
|
.join(CACHE_DIR_NAME);
|
|
fs::create_dir_all(&cache_root)?;
|
|
|
|
let (installer_path, version_tag) = download_latest_installer(&cache_root)?;
|
|
run_installer(&installer_path)?;
|
|
thread::sleep(Duration::from_secs(20));
|
|
|
|
Ok((Some(version_tag), true))
|
|
}
|
|
|
|
fn download_latest_installer(cache_root: &Path) -> Result<(PathBuf, String), RustdeskError> {
|
|
let client = Client::builder()
|
|
.user_agent(USER_AGENT)
|
|
.timeout(Duration::from_secs(60))
|
|
.build()?;
|
|
let release: ReleaseResponse = client.get(RELEASES_API).send()?.error_for_status()?.json()?;
|
|
let asset = release
|
|
.assets
|
|
.iter()
|
|
.find(|a| a.name.ends_with("x86_64.exe"))
|
|
.ok_or(RustdeskError::AssetMissing)?;
|
|
let target_path = cache_root.join(&asset.name);
|
|
if target_path.exists() {
|
|
return Ok((target_path, release.tag_name));
|
|
}
|
|
|
|
let mut response = client.get(&asset.browser_download_url).send()?.error_for_status()?;
|
|
let mut output = File::create(&target_path)?;
|
|
response.copy_to(&mut output)?;
|
|
Ok((target_path, release.tag_name))
|
|
}
|
|
|
|
fn run_installer(installer_path: &Path) -> Result<(), RustdeskError> {
|
|
let status = Command::new(installer_path)
|
|
.arg("--silent-install")
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null())
|
|
.status()?;
|
|
if !status.success() {
|
|
return Err(RustdeskError::CommandFailed {
|
|
command: format!("{} --silent-install", installer_path.display()),
|
|
status: status.code(),
|
|
});
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn write_config_files() -> Result<PathBuf, RustdeskError> {
|
|
let config_contents = build_config_contents();
|
|
let program_data = PathBuf::from(env::var("PROGRAMDATA").unwrap_or_else(|_| "C:/ProgramData".to_string()));
|
|
let main_path = program_data.join("RustDesk").join("config").join("RustDesk2.toml");
|
|
write_file(&main_path, &config_contents)?;
|
|
|
|
let service_profile = PathBuf::from(r"C:\\Windows\\ServiceProfiles\\LocalService\\AppData\\Roaming\\RustDesk\\config\\RustDesk2.toml");
|
|
write_file(&service_profile, &config_contents).ok();
|
|
|
|
Ok(main_path)
|
|
}
|
|
|
|
fn write_file(path: &Path, contents: &str) -> Result<(), io::Error> {
|
|
if let Some(parent) = path.parent() {
|
|
fs::create_dir_all(parent)?;
|
|
}
|
|
fs::write(path, contents)
|
|
}
|
|
|
|
fn build_config_contents() -> String {
|
|
format!(
|
|
r#"[options]
|
|
key = "{key}"
|
|
relay-server = "{host}"
|
|
custom-rendezvous-server = "{host}"
|
|
api-server = "https://{host}"
|
|
"#,
|
|
host = SERVER_HOST,
|
|
key = SERVER_KEY
|
|
)
|
|
}
|
|
|
|
fn apply_config(exe_path: &Path, config_path: &Path) -> Result<(), RustdeskError> {
|
|
let status = Command::new(exe_path)
|
|
.arg("--import-config")
|
|
.arg(config_path)
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null())
|
|
.status()?;
|
|
if !status.success() {
|
|
return Err(RustdeskError::CommandFailed {
|
|
command: format!("{} --import-config {}", exe_path.display(), config_path.display()),
|
|
status: status.code(),
|
|
});
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn set_password(exe_path: &Path) -> Result<(), RustdeskError> {
|
|
run_with_args(exe_path, &["--password", DEFAULT_PASSWORD])
|
|
}
|
|
|
|
fn set_custom_id(exe_path: &Path, machine_id: &str) -> Result<String, RustdeskError> {
|
|
let custom_id = derive_numeric_id(machine_id);
|
|
run_with_args(exe_path, &["--set-id", &custom_id])?;
|
|
Ok(custom_id)
|
|
}
|
|
|
|
fn derive_numeric_id(machine_id: &str) -> String {
|
|
let mut hasher = Sha256::new();
|
|
hasher.update(machine_id.as_bytes());
|
|
let hash = hasher.finalize();
|
|
let mut bytes = [0u8; 8];
|
|
bytes.copy_from_slice(&hash[..8]);
|
|
let value = u64::from_le_bytes(bytes);
|
|
let num = (value % 900_000_000) + 100_000_000;
|
|
format!("{:09}", num)
|
|
}
|
|
|
|
fn ensure_service_running() -> Result<(), RustdeskError> {
|
|
let _ = run_sc(&["stop", SERVICE_NAME]);
|
|
thread::sleep(Duration::from_secs(2));
|
|
let _ = run_sc(&["config", SERVICE_NAME, &format!("start= {}", "auto")]);
|
|
run_sc(&["start", SERVICE_NAME])
|
|
}
|
|
|
|
fn run_sc(args: &[&str]) -> Result<(), RustdeskError> {
|
|
let status = Command::new("sc")
|
|
.args(args)
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null())
|
|
.status()?;
|
|
if !status.success() {
|
|
return Err(RustdeskError::CommandFailed {
|
|
command: format!("sc {}", args.join(" ")),
|
|
status: status.code(),
|
|
});
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn query_id(exe_path: &Path) -> Result<String, RustdeskError> {
|
|
let output = Command::new(exe_path)
|
|
.arg("--get-id")
|
|
.output()?;
|
|
if !output.status.success() {
|
|
return Err(RustdeskError::CommandFailed {
|
|
command: format!("{} --get-id", exe_path.display()),
|
|
status: output.status.code(),
|
|
});
|
|
}
|
|
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
if stdout.is_empty() {
|
|
return Err(RustdeskError::MissingId);
|
|
}
|
|
Ok(stdout)
|
|
}
|
|
|
|
fn query_version(exe_path: &Path) -> Result<String, RustdeskError> {
|
|
let output = Command::new(exe_path)
|
|
.arg("--version")
|
|
.output()?;
|
|
if !output.status.success() {
|
|
return Err(RustdeskError::CommandFailed {
|
|
command: format!("{} --version", exe_path.display()),
|
|
status: output.status.code(),
|
|
});
|
|
}
|
|
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
|
}
|
|
|
|
fn run_with_args(exe_path: &Path, args: &[&str]) -> Result<(), RustdeskError> {
|
|
let status = Command::new(exe_path)
|
|
.args(args)
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null())
|
|
.status()?;
|
|
if !status.success() {
|
|
return Err(RustdeskError::CommandFailed {
|
|
command: format!("{} {}", exe_path.display(), args.join(" ")),
|
|
status: status.code(),
|
|
});
|
|
}
|
|
Ok(())
|
|
}
|