From 52b66e71d1a11bde30ab3347f210b8f81be7b85c Mon Sep 17 00:00:00 2001 From: RustDesk <71636191+rustdesk@users.noreply.github.com> Date: Wed, 4 Mar 2026 15:48:42 +0800 Subject: [PATCH] Move port mapping afterwards (#14448) * move port mapping after auth in port forwarding * fix(port-forward): try connect after 2fa Signed-off-by: fufesou * fix(security): gate port-forward connect on full auth and clarify login flow semantics Signed-off-by: fufesou * refact(port-forward): comments and logs Signed-off-by: fufesou --------- Signed-off-by: fufesou Co-authored-by: fufesou --- src/server/connection.rs | 130 ++++++++++++++++++++++++++------------- 1 file changed, 87 insertions(+), 43 deletions(-) diff --git a/src/server/connection.rs b/src/server/connection.rs index 1259054cd..033aac0ce 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -560,7 +560,9 @@ impl Connection { match data { ipc::Data::Authorize => { conn.require_2fa.take(); - conn.send_logon_response().await; + if !conn.send_logon_response_and_keep_alive().await { + break; + } if conn.port_forward_socket.is_some() { break; } @@ -1338,9 +1340,66 @@ impl Connection { crate::post_request(url, v.to_string(), "").await } - async fn send_logon_response(&mut self) { + fn normalize_port_forward_target(pf: &mut PortForward) -> (String, bool) { + let mut is_rdp = false; + if pf.host == "RDP" && pf.port == 0 { + pf.host = "localhost".to_owned(); + pf.port = 3389; + is_rdp = true; + } + if pf.host.is_empty() { + pf.host = "localhost".to_owned(); + } + (format!("{}:{}", pf.host, pf.port), is_rdp) + } + + async fn connect_port_forward_if_needed(&mut self) -> bool { + if self.port_forward_socket.is_some() { + return true; + } + let Some(login_request::Union::PortForward(pf)) = self.lr.union.as_ref() else { + return true; + }; + let mut pf = pf.clone(); + let (mut addr, is_rdp) = Self::normalize_port_forward_target(&mut pf); + self.port_forward_address = addr.clone(); + match timeout(3000, TcpStream::connect(&addr)).await { + Ok(Ok(sock)) => { + self.port_forward_socket = Some(Framed::new(sock, BytesCodec::new())); + true + } + Ok(Err(e)) => { + log::warn!("Port forward connect failed for {}: {}", addr, e); + if is_rdp { + addr = "RDP".to_owned(); + } + self.send_login_error(format!( + "Failed to access remote {}. Please make sure it is reachable/open.", + addr + )) + .await; + false + } + Err(e) => { + log::warn!("Port forward connect timed out for {}: {}", addr, e); + if is_rdp { + addr = "RDP".to_owned(); + } + self.send_login_error(format!( + "Failed to access remote {}. Please make sure it is reachable/open.", + addr + )) + .await; + false + } + } + } + + // Returns whether this connection should be kept alive. + // `true` does not necessarily mean authorization succeeded (e.g. REQUIRE_2FA case). + async fn send_logon_response_and_keep_alive(&mut self) -> bool { if self.authorized { - return; + return true; } if self.require_2fa.is_some() && !self.is_recent_session(true) && !self.from_switch { self.require_2fa.as_ref().map(|totp| { @@ -1371,7 +1430,11 @@ impl Connection { } }); self.send_login_error(crate::client::REQUIRE_2FA).await; - return; + // Keep the connection alive so the client can continue with 2FA. + return true; + } + if !self.connect_port_forward_if_needed().await { + return false; } self.authorized = true; let (conn_type, auth_conn_type) = if self.file_transfer.is_some() { @@ -1494,7 +1557,7 @@ impl Connection { res.set_peer_info(pi); msg_out.set_login_response(res); self.send(msg_out).await; - return; + return true; } #[cfg(target_os = "linux")] if self.is_remote() { @@ -1517,7 +1580,7 @@ impl Connection { let mut msg_out = Message::new(); msg_out.set_login_response(res); self.send(msg_out).await; - return; + return true; } } #[allow(unused_mut)] @@ -1671,6 +1734,7 @@ impl Connection { self.try_sub_monitor_services(); } } + true } fn try_sub_camera_displays(&mut self) { @@ -2178,33 +2242,8 @@ impl Connection { sleep(1.).await; return false; } - let mut is_rdp = false; - if pf.host == "RDP" && pf.port == 0 { - pf.host = "localhost".to_owned(); - pf.port = 3389; - is_rdp = true; - } - if pf.host.is_empty() { - pf.host = "localhost".to_owned(); - } - let mut addr = format!("{}:{}", pf.host, pf.port); - self.port_forward_address = addr.clone(); - match timeout(3000, TcpStream::connect(&addr)).await { - Ok(Ok(sock)) => { - self.port_forward_socket = Some(Framed::new(sock, BytesCodec::new())); - } - _ => { - if is_rdp { - addr = "RDP".to_owned(); - } - self.send_login_error(format!( - "Failed to access remote {}, please make sure if it is open", - addr - )) - .await; - return false; - } - } + let (addr, _is_rdp) = Self::normalize_port_forward_target(&mut pf); + self.port_forward_address = addr; } _ => { if !self.check_privacy_mode_on().await { @@ -2235,9 +2274,7 @@ impl Connection { // `is_logon_ui()` is a fallback for logon UI detection on Windows. #[cfg(target_os = "windows")] let is_logon = || { - crate::platform::is_prelogin() - || crate::platform::is_locked() - || { + crate::platform::is_prelogin() || crate::platform::is_locked() || { match crate::platform::is_logon_ui() { Ok(result) => result, Err(e) => { @@ -2276,7 +2313,9 @@ impl Connection { if err_msg.is_empty() { #[cfg(target_os = "linux")] self.linux_headless_handle.wait_desktop_cm_ready().await; - self.send_logon_response().await; + if !self.send_logon_response_and_keep_alive().await { + return false; + } self.try_start_cm(lr.my_id.clone(), lr.my_name.clone(), self.authorized); } else { self.send_login_error(err_msg).await; @@ -2312,7 +2351,9 @@ impl Connection { if err_msg.is_empty() { #[cfg(target_os = "linux")] self.linux_headless_handle.wait_desktop_cm_ready().await; - self.send_logon_response().await; + if !self.send_logon_response_and_keep_alive().await { + return false; + } self.try_start_cm(lr.my_id, lr.my_name, self.authorized); } else { self.send_login_error(err_msg).await; @@ -2330,7 +2371,9 @@ impl Connection { self.update_failure(failure, true, 1); self.require_2fa.take(); raii::AuthedConnID::set_session_2fa(self.session_key()); - self.send_logon_response().await; + if !self.send_logon_response_and_keep_alive().await { + return false; + } self.try_start_cm( self.lr.my_id.to_owned(), self.lr.my_name.to_owned(), @@ -2381,7 +2424,9 @@ impl Connection { if let Some((_instant, uuid_old)) = uuid_old { if uuid == uuid_old { self.from_switch = true; - self.send_logon_response().await; + if !self.send_logon_response_and_keep_alive().await { + return false; + } self.try_start_cm( lr.my_id.clone(), lr.my_name.clone(), @@ -5347,9 +5392,8 @@ mod raii { } pub fn check_wake_lock_on_setting_changed() { - let current = config::Config::get_bool_option( - keys::OPTION_KEEP_AWAKE_DURING_INCOMING_SESSIONS, - ); + let current = + config::Config::get_bool_option(keys::OPTION_KEEP_AWAKE_DURING_INCOMING_SESSIONS); let cached = *WAKELOCK_KEEP_AWAKE_OPTION.lock().unwrap(); if cached != Some(current) { Self::check_wake_lock();