mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-11 15:01:02 +03:00
server: do not lock the screen for a connection a reconnect replaced (#16124)
A controlling peer whose link dies without a close reconnects, while the
connection it left behind runs on here until its own 30s inactivity timeout.
That one then ends with `on_close("Timeout", true)`, and the lock is gated
only on `lock_after_session_end` and this connection's own `keyboard` - both
set by the very controller that is at that moment working in the session its
reconnect re-established. Nothing anywhere asks whether the session is still
being controlled, so the screen locks under a peer that came back twenty-odd
seconds earlier, and the operator's desk locks itself in front of them.
The lock now also requires that no newer remote control connection of this
session is authorized.
Newer, not merely other. A connection stays in `AUTHED_CONNS` until its
`AuthedConnID` drops, which is well after `on_close` returns, so a symmetric
test would have two of one session ending together each see the other and
neither lock. Ids come from a counter, so `>` orders them: of a session's
connections the last still locks, whether they end one after another or at
once.
Remote control only, and this session only. The other kinds do not keep a
screen in use, and `send_logon_response` clears `keyboard` for a file
transfer, a terminal and a camera view, so none of those reaches the gate at
all - a port forward keeps it, and is kept out only by the client not sending
`lock_after_session_end` on one. Another peer's
session is left exactly as it is: whether its ending locks the screen while
this one is connected is a separate question, and not one a timeout on this
side should start answering.
Every close that asked to lock, not only the timeout. A connection its own
peer has already replaced should not lock the session that replaced it
however it ends, and singling out one reason would leave the same race
reachable through the others.
Two things it does not cover. A reconnect that has not authorized yet is not
in `AUTHED_CONNS`, so a timeout landing while one waits at the accept prompt
or on 2FA locks as before. And the lock is skipped, not handed on: if the
connection that replaced this one later ends through a path that asks not to
lock - a failed send, a stopped service - nothing locks. That is what those
paths already choose for a connection dying of a network error, on the
assumption that the peer will retry, and it is the same assumption this makes.
Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5097,7 +5097,11 @@ impl Connection {
|
|||||||
// But it's not necessary now and we have to consider two audio services(client, server).
|
// But it's not necessary now and we have to consider two audio services(client, server).
|
||||||
crate::audio_service::set_voice_call_input_device(None, true);
|
crate::audio_service::set_voice_call_input_device(None, true);
|
||||||
log::info!("#{} Connection closed: {}", self.inner.id(), reason);
|
log::info!("#{} Connection closed: {}", self.inner.id(), reason);
|
||||||
if lock && self.lock_after_session_end && self.keyboard {
|
if lock
|
||||||
|
&& self.lock_after_session_end
|
||||||
|
&& self.keyboard
|
||||||
|
&& !raii::AuthedConnID::session_reconnected(self.inner.id(), &self.session_key())
|
||||||
|
{
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
lock_screen().await;
|
lock_screen().await;
|
||||||
}
|
}
|
||||||
@@ -6658,6 +6662,21 @@ mod raii {
|
|||||||
pub struct AuthedConnID(i32, AuthConnType);
|
pub struct AuthedConnID(i32, AuthConnType);
|
||||||
|
|
||||||
impl AuthedConnID {
|
impl AuthedConnID {
|
||||||
|
pub(super) fn is_newer_session_remote(c: &AuthedConn, id: i32, key: &SessionKey) -> bool {
|
||||||
|
c.conn_id > id && c.conn_type == AuthConnType::Remote && &c.session_key == key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether a newer remote control connection of this session has replaced this one. A
|
||||||
|
/// controlling peer whose link dies reconnects while the connection it left behind runs
|
||||||
|
/// on here until its own timeout; locking for that one would lock a session that has
|
||||||
|
/// already resumed on its replacement.
|
||||||
|
pub fn session_reconnected(id: i32, key: &SessionKey) -> bool {
|
||||||
|
let conns = AUTHED_CONNS.lock().unwrap();
|
||||||
|
conns
|
||||||
|
.iter()
|
||||||
|
.any(|c| Self::is_newer_session_remote(c, id, key))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
conn_id: i32,
|
conn_id: i32,
|
||||||
conn_type: AuthConnType,
|
conn_type: AuthConnType,
|
||||||
@@ -7547,4 +7566,38 @@ mod test {
|
|||||||
Ok(BoolOption::NotSet)
|
Ok(BoolOption::NotSet)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn only_a_newer_remote_control_of_the_same_session_keeps_the_screen_unlocked() {
|
||||||
|
let replaced_by = super::raii::AuthedConnID::is_newer_session_remote;
|
||||||
|
|
||||||
|
let key = |session_id, peer: &str| SessionKey {
|
||||||
|
peer_id: peer.to_owned(),
|
||||||
|
name: "".to_owned(),
|
||||||
|
session_id,
|
||||||
|
};
|
||||||
|
let conn = |conn_id, conn_type, session_key| AuthedConn {
|
||||||
|
conn_id,
|
||||||
|
conn_type,
|
||||||
|
session_key,
|
||||||
|
sender: mpsc::unbounded_channel().0,
|
||||||
|
printer: false,
|
||||||
|
};
|
||||||
|
let mine = key(7, "peer");
|
||||||
|
let remote = AuthConnType::Remote;
|
||||||
|
|
||||||
|
assert!(replaced_by(&conn(3, remote, mine.clone()), 2, &mine));
|
||||||
|
// An older one, and itself: of connections ending at once only the last still locks.
|
||||||
|
assert!(!replaced_by(&conn(1, remote, mine.clone()), 2, &mine));
|
||||||
|
assert!(!replaced_by(&conn(2, remote, mine.clone()), 2, &mine));
|
||||||
|
// A kind that keeps no screen in use.
|
||||||
|
assert!(!replaced_by(
|
||||||
|
&conn(3, AuthConnType::Terminal, mine.clone()),
|
||||||
|
2,
|
||||||
|
&mine
|
||||||
|
));
|
||||||
|
// Another session of this peer, and another peer on the same session id: `SessionKey`
|
||||||
|
// is all three fields, and either of those is someone else's screen to lock.
|
||||||
|
assert!(!replaced_by(&conn(3, remote, key(8, "peer")), 2, &mine));
|
||||||
|
assert!(!replaced_by(&conn(3, remote, key(7, "other")), 2, &mine));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user