Compare commits

..

4 Commits

Author SHA1 Message Date
rustdesk
95758b1a47 no id validation in deploy, so to keep the same behavior in udp register
pk
2026-05-14 10:41:36 +08:00
rustdesk
2685a25e51 fix review 2026-05-13 18:43:36 +08:00
RustDesk
2643f00216 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-13 18:23:13 +08:00
rustdesk
e222127009 --deploy, reuse the device token 2026-05-13 18:11:39 +08:00
3 changed files with 137 additions and 0 deletions

View File

@@ -627,6 +627,98 @@ pub fn core_main() -> Option<Vec<String>> {
println!("Installation and administrative privileges required!"); println!("Installation and administrative privileges required!");
} }
return None; return None;
} else if args[0] == "--deploy" {
if config::Config::no_register_device() {
println!("Cannot deploy an unregistrable device!");
} else if crate::platform::is_installed() && is_root() {
let max = args.len() - 1;
let pos = args.iter().position(|x| x == "--token").unwrap_or(max);
if pos >= max {
println!("--token is required!");
return None;
}
let token = args[pos + 1].to_owned();
let get_value = |c: &str| {
let pos = args.iter().position(|x| x == c).unwrap_or(max);
if pos < max {
Some(args[pos + 1].to_owned())
} else {
None
}
};
let new_id = get_value("--id");
let local_id = crate::ipc::get_id();
let id_to_deploy = new_id.clone().unwrap_or_else(|| local_id.clone());
let uuid = crate::encode64(hbb_common::get_uuid());
let pk = crate::encode64(
hbb_common::config::Config::get_key_pair().1,
);
let body = serde_json::json!({
"id": id_to_deploy,
"uuid": uuid,
"pk": pk,
});
let header = "Authorization: Bearer ".to_owned() + &token;
let url = crate::ui_interface::get_api_server() + "/api/devices/deploy";
match crate::post_request_sync(url, body.to_string(), &header) {
Err(err) => {
println!("Request failed: {}", err);
std::process::exit(1);
}
Ok(text) => {
let parsed: serde_json::Value =
serde_json::from_str(&text).unwrap_or(serde_json::Value::Null);
let result = parsed["result"].as_str().unwrap_or("");
match result {
"OK" => {
if let Some(ref new_id) = new_id {
if *new_id != local_id {
if let Err(err) =
crate::ipc::set_config("id", new_id.clone())
{
println!(
"Failed to persist deployed id locally: {}",
err
);
std::process::exit(1);
}
}
}
if let Err(err) = crate::ipc::notify_deployed() {
log::warn!("Failed to notify deployed state: {}", err);
}
println!("Device deployed.");
}
"NOT_ENABLED" => {
println!("Server does not require deployment.");
std::process::exit(3);
}
"INVALID_INPUT" => {
println!("Invalid input.");
std::process::exit(5);
}
"ID_TAKEN" => {
println!(
"Id `{}` is already used by another machine on the server.",
id_to_deploy
);
std::process::exit(6);
}
_ => {
if text.is_empty() {
println!("Unknown response.");
} else {
println!("{}", text);
}
std::process::exit(1);
}
}
}
}
} else {
println!("Installation and administrative privileges required!");
}
return None;
} else if args[0] == "--check-hwcodec-config" { } else if args[0] == "--check-hwcodec-config" {
#[cfg(feature = "hwcodec")] #[cfg(feature = "hwcodec")]
crate::ipc::hwcodec_process(); crate::ipc::hwcodec_process();

View File

@@ -312,6 +312,7 @@ pub enum Data {
ClipboardNonFile(Option<(String, Vec<ClipboardNonFile>)>), ClipboardNonFile(Option<(String, Vec<ClipboardNonFile>)>),
PrivacyModeState((i32, PrivacyModeState, String)), PrivacyModeState((i32, PrivacyModeState, String)),
TestRendezvousServer, TestRendezvousServer,
Deployed,
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
Keyboard(DataKeyboard), Keyboard(DataKeyboard),
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -929,6 +930,10 @@ async fn handle(data: Data, stream: &mut Connection) {
Data::TestRendezvousServer => { Data::TestRendezvousServer => {
crate::test_rendezvous_server(); crate::test_rendezvous_server();
} }
Data::Deployed => {
crate::rendezvous_mediator::NEEDS_DEPLOY.store(false, Ordering::SeqCst);
crate::rendezvous_mediator::RendezvousMediator::restart();
}
#[cfg(feature = "flutter")] #[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
Data::SwitchSidesRequest(id) => { Data::SwitchSidesRequest(id) => {
@@ -1737,6 +1742,13 @@ pub async fn test_rendezvous_server() -> ResultType<()> {
Ok(()) Ok(())
} }
#[tokio::main(flavor = "current_thread")]
pub async fn notify_deployed() -> ResultType<()> {
let mut c = connect(1000, "").await?;
c.send(&Data::Deployed).await?;
Ok(())
}
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
pub async fn send_url_scheme(url: String) -> ResultType<()> { pub async fn send_url_scheme(url: String) -> ResultType<()> {
connect(1_000, "_url") connect(1_000, "_url")

View File

@@ -41,6 +41,12 @@ lazy_static::lazy_static! {
static SHOULD_EXIT: AtomicBool = AtomicBool::new(false); static SHOULD_EXIT: AtomicBool = AtomicBool::new(false);
static MANUAL_RESTARTED: AtomicBool = AtomicBool::new(false); static MANUAL_RESTARTED: AtomicBool = AtomicBool::new(false);
static SENT_REGISTER_PK: AtomicBool = AtomicBool::new(false); static SENT_REGISTER_PK: AtomicBool = AtomicBool::new(false);
pub(crate) static NEEDS_DEPLOY: AtomicBool = AtomicBool::new(false);
// register_pk retry interval (ms) when device is awaiting deployment
const DEPLOY_RETRY_INTERVAL: i64 = 30_000;
lazy_static::lazy_static! {
static ref LAST_NOT_DEPLOYED_REGISTER: Mutex<Option<Instant>> = Mutex::new(None);
}
#[derive(Clone)] #[derive(Clone)]
pub struct RendezvousMediator { pub struct RendezvousMediator {
@@ -289,10 +295,22 @@ impl RendezvousMediator {
Config::set_key_confirmed(true); Config::set_key_confirmed(true);
Config::set_host_key_confirmed(&self.host_prefix, true); Config::set_host_key_confirmed(&self.host_prefix, true);
*SOLVING_PK_MISMATCH.lock().await = "".to_owned(); *SOLVING_PK_MISMATCH.lock().await = "".to_owned();
NEEDS_DEPLOY.store(false, Ordering::SeqCst);
} }
Ok(register_pk_response::Result::UUID_MISMATCH) => { Ok(register_pk_response::Result::UUID_MISMATCH) => {
self.handle_uuid_mismatch(sink).await?; self.handle_uuid_mismatch(sink).await?;
} }
Ok(register_pk_response::Result::NOT_DEPLOYED) => {
if !NEEDS_DEPLOY.load(Ordering::SeqCst) {
log::warn!("Server requires deployment. Run `rustdesk --deploy --token <api_token>` on this device.");
}
NEEDS_DEPLOY.store(true, Ordering::SeqCst);
// Clear key_confirmed so the UI reflects the truth: this device is
// not currently registered. Covers the case where an online device
// was deleted by an admin while running.
Config::set_key_confirmed(false);
Config::set_host_key_confirmed(&self.host_prefix, false);
}
_ => { _ => {
log::error!("unknown RegisterPkResponse"); log::error!("unknown RegisterPkResponse");
} }
@@ -678,6 +696,21 @@ impl RendezvousMediator {
} }
async fn register_pk(&mut self, socket: Sink<'_>) -> ResultType<()> { async fn register_pk(&mut self, socket: Sink<'_>) -> ResultType<()> {
// Throttle register_pk when the device is awaiting deployment: server
// already told us we're not in its db; sending more often than every
// DEPLOY_RETRY_INTERVAL ms is wasted traffic until the operator runs
// `rustdesk --deploy --token <api_token>`.
if NEEDS_DEPLOY.load(Ordering::SeqCst) {
let mut last = LAST_NOT_DEPLOYED_REGISTER.lock().await;
if let Some(t) = *last {
if (t.elapsed().as_millis() as i64) < DEPLOY_RETRY_INTERVAL {
return Ok(());
}
}
*last = Some(Instant::now());
} else {
*LAST_NOT_DEPLOYED_REGISTER.lock().await = None;
}
let mut msg_out = Message::new(); let mut msg_out = Message::new();
let pk = Config::get_key_pair().1; let pk = Config::get_key_pair().1;
let uuid = hbb_common::get_uuid(); let uuid = hbb_common::get_uuid();