Compare commits

...

5 Commits

Author SHA1 Message Date
fufesou
a563976239 refact(port-forward): comments and logs
Signed-off-by: fufesou <linlong1266@gmail.com>
2026-03-03 20:47:56 +08:00
fufesou
3b225b7c6c fix(security): gate port-forward connect on full auth and clarify login flow semantics
Signed-off-by: fufesou <linlong1266@gmail.com>
2026-03-03 17:35:04 +08:00
fufesou
fc80106a8b fix(port-forward): try connect after 2fa
Signed-off-by: fufesou <linlong1266@gmail.com>
2026-03-03 16:21:43 +08:00
rustdesk
24deed80f5 move port mapping after auth in port forwarding 2026-03-03 09:31:55 +08:00
fufesou
732b250815 fix(keyboard): legacy mode (#14435)
* fix(keyboard): legacy mode

Signed-off-by: fufesou <linlong1266@gmail.com>

* Simple refactor

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(keyboard): legacy mode, chr to seq

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(keyboard): legacy mode, early return if (!hotkey)&down

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(keyboard): legacy mode, pair down/up

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-03-02 19:07:09 +08:00
3 changed files with 134 additions and 48 deletions

View File

@@ -269,7 +269,7 @@ impl KeyboardControllable for Enigo {
for pos in 0..mod_len {
let rpos = mod_len - 1 - pos;
if flag & (0x0001 << rpos) != 0 {
self.key_up(modifiers[pos]);
self.key_up(modifiers[rpos]);
}
}
@@ -298,7 +298,18 @@ impl KeyboardControllable for Enigo {
}
fn key_up(&mut self, key: Key) {
keybd_event(KEYEVENTF_KEYUP, self.key_to_keycode(key), 0);
match key {
Key::Layout(c) => {
let code = self.get_layoutdependent_keycode(c);
if code as u16 != 0xFFFF {
let vk = code & 0x00FF;
keybd_event(KEYEVENTF_KEYUP, vk, 0);
}
}
_ => {
keybd_event(KEYEVENTF_KEYUP, self.key_to_keycode(key), 0);
}
}
}
fn get_key_state(&mut self, key: Key) -> bool {

View File

@@ -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();

View File

@@ -809,7 +809,7 @@ fn record_key_is_control_key(record_key: u64) -> bool {
#[inline]
fn record_key_is_chr(record_key: u64) -> bool {
record_key < KEY_CHAR_START
record_key >= KEY_CHAR_START
}
#[inline]
@@ -1513,6 +1513,27 @@ fn get_control_key_value(key_event: &KeyEvent) -> i32 {
}
}
#[inline]
fn has_hotkey_modifiers(key_event: &KeyEvent) -> bool {
key_event.modifiers.iter().any(|ck| {
let v = ck.value();
v == ControlKey::Control.value()
|| v == ControlKey::RControl.value()
|| v == ControlKey::Meta.value()
|| v == ControlKey::RWin.value()
|| {
#[cfg(any(target_os = "windows", target_os = "linux"))]
{
v == ControlKey::Alt.value() || v == ControlKey::RAlt.value()
}
#[cfg(target_os = "macos")]
{
false
}
}
})
}
fn release_unpressed_modifiers(en: &mut Enigo, key_event: &KeyEvent) {
let ck_value = get_control_key_value(key_event);
fix_modifiers(&key_event.modifiers[..], en, ck_value);
@@ -1572,7 +1593,7 @@ fn need_to_uppercase(en: &mut Enigo) -> bool {
get_modifier_state(Key::Shift, en) || get_modifier_state(Key::CapsLock, en)
}
fn process_chr(en: &mut Enigo, chr: u32, down: bool) {
fn process_chr(en: &mut Enigo, chr: u32, down: bool, _hotkey: bool) {
// On Wayland with uinput mode, use clipboard for character input
#[cfg(target_os = "linux")]
if !crate::platform::linux::is_x11() && wayland_use_uinput() {
@@ -1587,6 +1608,16 @@ fn process_chr(en: &mut Enigo, chr: u32, down: bool) {
}
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
if !_hotkey {
if down {
if let Ok(chr) = char::try_from(chr) {
en.key_sequence(&chr.to_string());
}
}
return;
}
let key = char_value_to_key(chr);
if down {
@@ -1856,7 +1887,7 @@ fn legacy_keyboard_mode(evt: &KeyEvent) {
let record_key = chr as u64 + KEY_CHAR_START;
record_pressed_key(KeysDown::EnigoKey(record_key), down);
process_chr(&mut en, chr, down)
process_chr(&mut en, chr, down, has_hotkey_modifiers(evt))
}
Some(key_event::Union::Unicode(chr)) => {
// Same as Chr: release Shift for Unicode input