From 3a9084006f769308645aa4d1dc3ecf79766c180b Mon Sep 17 00:00:00 2001 From: 21pages Date: Fri, 9 Jan 2026 00:21:28 +0800 Subject: [PATCH] Allow configuring remote control permissions for different users (#13974) Signed-off-by: 21pages --- flutter/lib/common.dart | 15 ++++- src/common.rs | 22 ++++++ src/flutter_ffi.rs | 7 ++ src/ipc.rs | 34 +++++++++- src/rendezvous_mediator.rs | 70 ++++++++++++++++--- src/server.rs | 47 ++++++++++--- src/server/connection.rs | 134 +++++++++++++++++++++++++++++++++---- src/ui.rs | 10 +++ src/ui/index.tis | 11 ++- src/ui_cm_interface.rs | 9 ++- src/ui_interface.rs | 54 ++++++++------- 11 files changed, 353 insertions(+), 60 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index b4c9c6e82..bd7948de0 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -3039,10 +3039,21 @@ Future start_service(bool is_start) async { } Future canBeBlocked() async { - var access_mode = await bind.mainGetOption(key: kOptionAccessMode); + // First check control permission + final controlPermission = await bind.mainGetCommon( + key: "is-remote-modify-enabled-by-control-permissions"); + if (controlPermission == "true") { + return false; + } else if (controlPermission == "false") { + return true; + } + + // Check local settings + var accessMode = await bind.mainGetOption(key: kOptionAccessMode); + var isCustomAccessMode = accessMode != 'full' && accessMode != 'view'; var option = option2bool(kOptionAllowRemoteConfigModification, await bind.mainGetOption(key: kOptionAllowRemoteConfigModification)); - return access_mode == 'view' || (access_mode.isEmpty && !option); + return accessMode == 'view' || (isCustomAccessMode && !option); } // to-do: web not implemented diff --git a/src/common.rs b/src/common.rs index 0dc944d83..66a12994d 100644 --- a/src/common.rs +++ b/src/common.rs @@ -2277,6 +2277,28 @@ pub fn str2color(s: &str, alpha: u8) -> u32 { (alpha as u32) << 24 | rgb } +/// Check control permission state from a u64 bitmap. +/// Each permission uses 2 bits: 0 = not set, 1 = disable, 2 = enable, 3 = invalid (treated as not set) +/// Returns: Some(true) = enabled, Some(false) = disabled, None = not set or invalid +pub fn get_control_permission( + permissions: u64, + permission: hbb_common::rendezvous_proto::control_permissions::Permission, +) -> Option { + use hbb_common::protobuf::Enum; + let index = permission.value(); + if index >= 0 && index < 32 { + let shift = index * 2; + let value = (permissions >> shift) & 0b11; + match value { + 1 => Some(false), // disable + 2 => Some(true), // enable + _ => None, // 0 = not set, 3 = invalid + } + } else { + None + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index ff74b8b79..f2d3e34ef 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -2600,6 +2600,13 @@ pub fn main_get_common(key: String) -> String { return false.to_string(); } else if key == "transfer-job-id" { return hbb_common::fs::get_next_job_id().to_string(); + } else if key == "is-remote-modify-enabled-by-control-permissions" { + return match is_remote_modify_enabled_by_control_permissions() { + Some(true) => "true", + Some(false) => "false", + None => "", + } + .to_string(); } else { if key.starts_with("download-data-") { let id = key.replace("download-data-", ""); diff --git a/src/ipc.rs b/src/ipc.rs index e5f163c2e..a5d27ba8a 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -23,7 +23,11 @@ pub use clipboard::ClipboardFile; use hbb_common::{ allow_err, bail, bytes, bytes_codec::BytesCodec, - config::{self, keys::OPTION_ALLOW_WEBSOCKET, Config, Config2}, + config::{ + self, + keys::{self, OPTION_ALLOW_WEBSOCKET}, + Config, Config2, + }, futures::StreamExt as _, futures_util::sink::SinkExt, log, password_security as password, timeout, @@ -384,6 +388,9 @@ pub enum Data { SocksWs(Option, String)>>), #[cfg(not(any(target_os = "android", target_os = "ios")))] Whiteboard((String, crate::whiteboard::CustomEvent)), + ControlPermissionsRemoteModify(Option), + #[cfg(target_os = "windows")] + FileTransferEnabledState(Option), } #[tokio::main(flavor = "current_thread")] @@ -862,6 +869,31 @@ async fn handle(data: Data, stream: &mut Connection) { // Port forward session count is only a get value. } }, + Data::ControlPermissionsRemoteModify(_) => { + use hbb_common::rendezvous_proto::control_permissions::Permission; + let state = + crate::server::get_control_permission_state(Permission::remote_modify, true); + allow_err!( + stream + .send(&Data::ControlPermissionsRemoteModify(state)) + .await + ); + } + #[cfg(target_os = "windows")] + Data::FileTransferEnabledState(_) => { + use hbb_common::rendezvous_proto::control_permissions::Permission; + let state = crate::server::get_control_permission_state(Permission::file, false); + let enabled = state.unwrap_or_else(|| { + crate::server::Connection::is_permission_enabled_locally( + config::keys::OPTION_ENABLE_FILE_TRANSFER, + ) + }); + allow_err!( + stream + .send(&Data::FileTransferEnabledState(Some(enabled))) + .await + ); + } _ => {} } } diff --git a/src/rendezvous_mediator.rs b/src/rendezvous_mediator.rs index e17920c8a..5d26d3389 100644 --- a/src/rendezvous_mediator.rs +++ b/src/rendezvous_mediator.rs @@ -427,6 +427,7 @@ impl RendezvousMediator { rr.secure, false, Default::default(), + rr.control_permissions.clone().into_option(), ) .await } @@ -440,6 +441,7 @@ impl RendezvousMediator { secure: bool, initiate: bool, socket_addr_v6: bytes::Bytes, + control_permissions: Option, ) -> ResultType<()> { let peer_addr = AddrMangle::decode(&socket_addr); log::info!( @@ -473,6 +475,7 @@ impl RendezvousMediator { peer_addr, secure, is_ipv4(&self.addr), + control_permissions, ) .await; Ok(()) @@ -491,7 +494,13 @@ impl RendezvousMediator { let relay = use_ws() || Config::is_proxy(); let mut socket_addr_v6 = Default::default(); if peer_addr_v6.port() > 0 && !relay { - socket_addr_v6 = start_ipv6(peer_addr_v6, addr, server.clone()).await; + socket_addr_v6 = start_ipv6( + peer_addr_v6, + addr, + server.clone(), + fla.control_permissions.clone().into_option(), + ) + .await; } if is_ipv4(&self.addr) && !relay && !config::is_disable_tcp_listen() { if let Err(err) = self @@ -517,6 +526,7 @@ impl RendezvousMediator { true, true, socket_addr_v6, + fla.control_permissions.into_option(), ) .await } @@ -547,7 +557,14 @@ impl RendezvousMediator { }); let bytes = msg_out.write_to_bytes()?; socket.send_raw(bytes).await?; - crate::accept_connection(server.clone(), socket, peer_addr, true).await; + crate::accept_connection( + server.clone(), + socket, + peer_addr, + true, + fla.control_permissions.into_option(), + ) + .await; Ok(()) } @@ -562,8 +579,15 @@ impl RendezvousMediator { let peer_addr_v6 = hbb_common::AddrMangle::decode(&ph.socket_addr_v6); let relay = use_ws() || Config::is_proxy() || ph.force_relay; let mut socket_addr_v6 = Default::default(); + let control_permissions = ph.control_permissions.into_option(); if peer_addr_v6.port() > 0 && !relay { - socket_addr_v6 = start_ipv6(peer_addr_v6, peer_addr, server.clone()).await; + socket_addr_v6 = start_ipv6( + peer_addr_v6, + peer_addr, + server.clone(), + control_permissions.clone(), + ) + .await; } let relay_server = self.get_relay_server(ph.relay_server); // for ensure, websocket go relay directly @@ -582,6 +606,7 @@ impl RendezvousMediator { true, true, socket_addr_v6.clone(), + control_permissions, ) .await; } @@ -598,7 +623,8 @@ impl RendezvousMediator { }; if ph.udp_port > 0 { peer_addr.set_port(ph.udp_port as u16); - self.punch_udp_hole(peer_addr, server, msg_punch).await?; + self.punch_udp_hole(peer_addr, server, msg_punch, control_permissions) + .await?; return Ok(()); } log::debug!("Punch tcp hole to {:?}", peer_addr); @@ -614,7 +640,8 @@ impl RendezvousMediator { msg_out.set_punch_hole_sent(msg_punch); let bytes = msg_out.write_to_bytes()?; socket.send_raw(bytes).await?; - crate::accept_connection(server.clone(), socket, peer_addr, true).await; + crate::accept_connection(server.clone(), socket, peer_addr, true, control_permissions) + .await; Ok(()) } @@ -623,6 +650,7 @@ impl RendezvousMediator { peer_addr: SocketAddr, server: ServerPtr, msg_punch: PunchHoleSent, + control_permissions: Option, ) -> ResultType<()> { let mut msg_out = Message::new(); msg_out.set_punch_hole_sent(msg_punch); @@ -637,7 +665,14 @@ impl RendezvousMediator { socket.send_to(&data, addr).await.ok(); } }); - udp_nat_listen(socket_cloned.clone(), peer_addr, peer_addr, server).await?; + udp_nat_listen( + socket_cloned.clone(), + peer_addr, + peer_addr, + server, + control_permissions, + ) + .await?; Ok(()) } @@ -778,6 +813,7 @@ async fn direct_server(server: ServerPtr) { hbb_common::Stream::from(stream, local_addr), addr, false, + None, // Direct connections don't have control_permissions ) .await ); @@ -809,12 +845,22 @@ async fn start_ipv6( peer_addr_v6: SocketAddr, peer_addr_v4: SocketAddr, server: ServerPtr, + control_permissions: Option, ) -> bytes::Bytes { crate::test_ipv6().await; if let Some((socket, local_addr_v6)) = crate::get_ipv6_socket().await { let server = server.clone(); tokio::spawn(async move { - allow_err!(udp_nat_listen(socket.clone(), peer_addr_v6, peer_addr_v4, server).await); + allow_err!( + udp_nat_listen( + socket.clone(), + peer_addr_v6, + peer_addr_v4, + server, + control_permissions + ) + .await + ); }); return local_addr_v6; } @@ -826,6 +872,7 @@ async fn udp_nat_listen( peer_addr: SocketAddr, peer_addr_v4: SocketAddr, server: ServerPtr, + control_permissions: Option, ) -> ResultType<()> { let tm = Instant::now(); let socket_cloned = socket.clone(); @@ -838,7 +885,14 @@ async fn udp_nat_listen( res, ) .await?; - crate::server::create_tcp_connection(server, stream.1, peer_addr_v4, true).await?; + crate::server::create_tcp_connection( + server, + stream.1, + peer_addr_v4, + true, + control_permissions, + ) + .await?; Ok(()) }; func.await.map_err(|e: anyhow::Error| { diff --git a/src/server.rs b/src/server.rs index 9d2e4b804..5dc504fe9 100644 --- a/src/server.rs +++ b/src/server.rs @@ -154,18 +154,30 @@ pub fn new() -> ServerPtr { Arc::new(RwLock::new(server)) } -async fn accept_connection_(server: ServerPtr, socket: Stream, secure: bool) -> ResultType<()> { +async fn accept_connection_( + server: ServerPtr, + socket: Stream, + secure: bool, + control_permissions: Option, +) -> ResultType<()> { let local_addr = socket.local_addr(); drop(socket); // even we drop socket, below still may fail if not use reuse_addr, // there is TIME_WAIT before socket really released, so sometimes we - // see “Only one usage of each socket address is normally permitted” on windows sometimes, + // see "Only one usage of each socket address is normally permitted" on windows sometimes, let listener = new_listener(local_addr, true).await?; log::info!("Server listening on: {}", &listener.local_addr()?); if let Ok((stream, addr)) = timeout(CONNECT_TIMEOUT, listener.accept()).await? { stream.set_nodelay(true).ok(); let stream_addr = stream.local_addr()?; - create_tcp_connection(server, Stream::from(stream, stream_addr), addr, secure).await?; + create_tcp_connection( + server, + Stream::from(stream, stream_addr), + addr, + secure, + control_permissions, + ) + .await?; } Ok(()) } @@ -175,6 +187,7 @@ pub async fn create_tcp_connection( stream: Stream, addr: SocketAddr, secure: bool, + control_permissions: Option, ) -> ResultType<()> { let mut stream = stream; let id = server.write().unwrap().get_new_id(); @@ -242,7 +255,14 @@ pub async fn create_tcp_connection( } log::info!("wake up macos"); } - Connection::start(addr, stream, id, Arc::downgrade(&server)).await; + Connection::start( + addr, + stream, + id, + Arc::downgrade(&server), + control_permissions, + ) + .await; Ok(()) } @@ -251,8 +271,9 @@ pub async fn accept_connection( socket: Stream, peer_addr: SocketAddr, secure: bool, + control_permissions: Option, ) { - if let Err(err) = accept_connection_(server, socket, secure).await { + if let Err(err) = accept_connection_(server, socket, secure, control_permissions).await { log::warn!("Failed to accept connection from {}: {}", peer_addr, err); } } @@ -264,9 +285,18 @@ pub async fn create_relay_connection( peer_addr: SocketAddr, secure: bool, ipv4: bool, + control_permissions: Option, ) { - if let Err(err) = - create_relay_connection_(server, relay_server, uuid.clone(), peer_addr, secure, ipv4).await + if let Err(err) = create_relay_connection_( + server, + relay_server, + uuid.clone(), + peer_addr, + secure, + ipv4, + control_permissions, + ) + .await { log::error!( "Failed to create relay connection for {} with uuid {}: {}", @@ -284,6 +314,7 @@ async fn create_relay_connection_( peer_addr: SocketAddr, secure: bool, ipv4: bool, + control_permissions: Option, ) -> ResultType<()> { let mut stream = socket_client::connect_tcp( socket_client::ipv4_to_ipv6(crate::check_port(relay_server, RELAY_PORT), ipv4), @@ -298,7 +329,7 @@ async fn create_relay_connection_( ..Default::default() }); stream.send(&msg_out).await?; - create_tcp_connection(server, stream, peer_addr, secure).await?; + create_tcp_connection(server, stream, peer_addr, secure, control_permissions).await?; Ok(()) } diff --git a/src/server/connection.rs b/src/server/connection.rs index ee8cad591..1e7758887 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -71,6 +71,7 @@ lazy_static::lazy_static! { static ref SESSIONS: Arc::>> = Default::default(); static ref ALIVE_CONNS: Arc::>> = Default::default(); pub static ref AUTHED_CONNS: Arc::>> = Default::default(); + pub static ref CONTROL_PERMISSIONS_ARRAY: Arc::>> = Default::default(); static ref SWITCH_SIDES_UUID: Arc::>> = Default::default(); static ref WAKELOCK_SENDER: Arc::>> = Arc::new(Mutex::new(start_wakelock_thread())); } @@ -226,6 +227,7 @@ pub struct Connection { restart: bool, recording: bool, block_input: bool, + control_permissions: Option, last_test_delay: Option, network_delay: u32, lock_after_session_end: bool, @@ -349,8 +351,14 @@ impl Connection { stream: super::Stream, id: i32, server: super::ServerPtrWeak, + control_permissions: Option, ) { + // Android is not supported yet, so we always set control_permissions to None. + #[cfg(target_os = "android")] + let control_permissions = None; let _raii_id = raii::ConnectionID::new(id); + let _raii_control_permissions_id = + raii::ControlPermissionsID::new(id, &control_permissions); let hash = Hash { salt: Config::get_salt(), challenge: Config::get_auto_password(6), @@ -401,14 +409,15 @@ impl Connection { port_forward_address: "".to_owned(), tx_to_cm, authorized: false, - keyboard: Connection::permission("enable-keyboard"), - clipboard: Connection::permission("enable-clipboard"), - audio: Connection::permission("enable-audio"), + keyboard: Self::permission(keys::OPTION_ENABLE_KEYBOARD, &control_permissions), + clipboard: Self::permission(keys::OPTION_ENABLE_CLIPBOARD, &control_permissions), + audio: Self::permission(keys::OPTION_ENABLE_AUDIO, &control_permissions), // to-do: make sure is the option correct here - file: Connection::permission(keys::OPTION_ENABLE_FILE_TRANSFER), - restart: Connection::permission("enable-remote-restart"), - recording: Connection::permission("enable-record-session"), - block_input: Connection::permission("enable-block-input"), + file: Self::permission(keys::OPTION_ENABLE_FILE_TRANSFER, &control_permissions), + restart: Self::permission(keys::OPTION_ENABLE_REMOTE_RESTART, &control_permissions), + recording: Self::permission(keys::OPTION_ENABLE_RECORD_SESSION, &control_permissions), + block_input: Self::permission(keys::OPTION_ENABLE_BLOCK_INPUT, &control_permissions), + control_permissions, last_test_delay: None, network_delay: 0, lock_after_session_end: false, @@ -885,7 +894,7 @@ impl Connection { match data { #[cfg(all(target_os = "windows", feature = "flutter"))] ipc::Data::PrinterData(data) => { - if config::Config::get_bool_option(config::keys::OPTION_ENABLE_REMOTE_PRINTER) { + if Self::permission(keys::OPTION_ENABLE_REMOTE_PRINTER, &conn.control_permissions) { conn.send_printer_request(data).await; } else { conn.send_remote_printing_disallowed().await; @@ -1942,7 +1951,8 @@ impl Connection { false } - pub fn permission(enable_prefix_option: &str) -> bool { + #[inline] + pub fn is_permission_enabled_locally(enable_prefix_option: &str) -> bool { #[cfg(feature = "flutter")] #[cfg(not(any(target_os = "android", target_os = "ios")))] { @@ -1959,6 +1969,37 @@ impl Connection { ) } + fn permission( + enable_prefix_option: &str, + control_permissions: &Option, + ) -> bool { + use hbb_common::rendezvous_proto::control_permissions::Permission; + if let Some(control_permissions) = control_permissions { + let permission = match enable_prefix_option { + keys::OPTION_ENABLE_KEYBOARD => Some(Permission::keyboard), + keys::OPTION_ENABLE_REMOTE_PRINTER => Some(Permission::remote_printer), + keys::OPTION_ENABLE_CLIPBOARD => Some(Permission::clipboard), + keys::OPTION_ENABLE_FILE_TRANSFER => Some(Permission::file), + keys::OPTION_ENABLE_AUDIO => Some(Permission::audio), + keys::OPTION_ENABLE_CAMERA => Some(Permission::camera), + keys::OPTION_ENABLE_TERMINAL => Some(Permission::terminal), + keys::OPTION_ENABLE_TUNNEL => Some(Permission::tunnel), + keys::OPTION_ENABLE_REMOTE_RESTART => Some(Permission::restart), + keys::OPTION_ENABLE_RECORD_SESSION => Some(Permission::recording), + keys::OPTION_ENABLE_BLOCK_INPUT => Some(Permission::block_input), + _ => None, + }; + if let Some(permission) = permission { + if let Some(enabled) = + crate::get_control_permission(control_permissions.permissions, permission) + { + return enabled; + } + } + } + Self::is_permission_enabled_locally(enable_prefix_option) + } + fn update_codec_on_login(&self) { use scrap::codec::{Encoder, EncodingUpdate::*}; if let Some(o) = self.lr.clone().option.as_ref() { @@ -2054,7 +2095,10 @@ impl Connection { } match lr.union { Some(login_request::Union::FileTransfer(ft)) => { - if !Connection::permission(keys::OPTION_ENABLE_FILE_TRANSFER) { + if !Self::permission( + keys::OPTION_ENABLE_FILE_TRANSFER, + &self.control_permissions, + ) { self.send_login_error("No permission of file transfer") .await; sleep(1.).await; @@ -2063,7 +2107,7 @@ impl Connection { self.file_transfer = Some((ft.dir, ft.show_hidden)); } Some(login_request::Union::ViewCamera(_vc)) => { - if !Connection::permission(keys::OPTION_ENABLE_CAMERA) { + if !Self::permission(keys::OPTION_ENABLE_CAMERA, &self.control_permissions) { self.send_login_error("No permission of viewing camera") .await; sleep(1.).await; @@ -2072,7 +2116,7 @@ impl Connection { self.view_camera = true; } Some(login_request::Union::Terminal(terminal)) => { - if !Connection::permission(keys::OPTION_ENABLE_TERMINAL) { + if !Self::permission(keys::OPTION_ENABLE_TERMINAL, &self.control_permissions) { self.send_login_error("No permission of terminal").await; sleep(1.).await; return false; @@ -2120,7 +2164,7 @@ impl Connection { } } Some(login_request::Union::PortForward(mut pf)) => { - if !Connection::permission("enable-tunnel") { + if !Self::permission(keys::OPTION_ENABLE_TUNNEL, &self.control_permissions) { self.send_login_error("No permission of IP tunneling").await; sleep(1.).await; return false; @@ -5167,6 +5211,41 @@ impl Retina { } } +/// Get control permission state from CONTROL_PERMISSIONS_ARRAY. +/// Returns: Some(false) if any disable, Some(true) if any enable (and no disable), None if not set. +pub fn get_control_permission_state( + permission: hbb_common::rendezvous_proto::control_permissions::Permission, + disable_if_has_disabled: bool, +) -> Option { + let control_permissions = CONTROL_PERMISSIONS_ARRAY.lock().unwrap(); + let mut has_enable = false; + let mut has_disable = false; + for (_, cp) in control_permissions.iter() { + match crate::get_control_permission(cp.permissions, permission) { + Some(false) => has_disable = true, + Some(true) => has_enable = true, + None => {} + } + } + if disable_if_has_disabled { + if has_disable { + Some(false) + } else if has_enable { + Some(true) + } else { + None + } + } else { + if has_enable { + Some(true) + } else if has_disable { + Some(false) + } else { + None + } + } +} + pub struct AuthedConn { pub conn_id: i32, pub conn_type: AuthConnType, @@ -5178,6 +5257,7 @@ pub struct AuthedConn { mod raii { // ALIVE_CONNS: all connections, including unauthorized connections // AUTHED_CONNS: all authorized connections + // CONTROL_PERMISSIONS_ARRAY: all non-None control permissions use super::*; pub struct ConnectionID(i32); @@ -5368,6 +5448,34 @@ mod raii { } } } + + pub struct ControlPermissionsID { + id: i32, + control_permissions: Option, + } + + impl Drop for ControlPermissionsID { + fn drop(&mut self) { + if self.control_permissions.is_some() { + let mut lock = CONTROL_PERMISSIONS_ARRAY.lock().unwrap(); + lock.retain(|(conn_id, _)| *conn_id != self.id); + } + } + } + impl ControlPermissionsID { + pub fn new(id: i32, control_permissions: &Option) -> Self { + if let Some(s) = control_permissions { + CONTROL_PERMISSIONS_ARRAY + .lock() + .unwrap() + .push((id, s.clone())); + } + Self { + id, + control_permissions: control_permissions.clone(), + } + } + } } mod test { diff --git a/src/ui.rs b/src/ui.rs index 2a0f6e918..fc59cffd2 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -699,6 +699,15 @@ impl UI { fn get_builtin_option(&self, key: String) -> String { crate::ui_interface::get_builtin_option(&key) } + + fn is_remote_modify_enabled_by_control_permissions(&self) -> String { + match crate::ui_interface::is_remote_modify_enabled_by_control_permissions() { + Some(true) => "true", + Some(false) => "false", + None => "", + } + .to_string() + } } impl sciter::EventHandler for UI { @@ -801,6 +810,7 @@ impl sciter::EventHandler for UI { fn verify_login(String, String); fn is_option_fixed(String); fn get_builtin_option(String); + fn is_remote_modify_enabled_by_control_permissions(); } } diff --git a/src/ui/index.tis b/src/ui/index.tis index 20cbb7ba2..8dd4da3d4 100644 --- a/src/ui/index.tis +++ b/src/ui/index.tis @@ -1396,7 +1396,16 @@ function self.onMouse(evt) { } function check_if_overlay() { - if (handler.get_option('allow-remote-config-modification') != 'Y') { + var enabled; + var is_enabled_by_control_permissions = handler.is_remote_modify_enabled_by_control_permissions(); + if (is_enabled_by_control_permissions == "true") { + enabled = true; + } else if (is_enabled_by_control_permissions == "false") { + enabled = false; + } else { + enabled = handler.get_option('allow-remote-config-modification') == 'Y'; + } + if (!enabled) { var time0 = getTime(); handler.check_mouse_time(); self.timer(120ms, function() { diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index d6792c111..4e688429f 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -772,7 +772,14 @@ impl IpcTaskRunner { #[tokio::main(flavor = "current_thread")] pub async fn start_ipc(cm: ConnectionManager) { #[cfg(target_os = "windows")] - ContextSend::enable(crate::Connection::permission(OPTION_ENABLE_FILE_TRANSFER)); + { + let enabled = crate::Connection::is_permission_enabled_locally(OPTION_ENABLE_FILE_TRANSFER); + let mut lock = crate::ui_interface::IS_FILE_TRANSFER_ENABLED + .lock() + .unwrap(); + ContextSend::enable(enabled); + *lock = Some(enabled); + } match ipc::new_listener("_cm").await { Ok(mut incoming) => { while let Some(result) = incoming.next().await { diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 549337aea..c5f158c9d 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -69,6 +69,7 @@ lazy_static::lazy_static! { static ref ASYNC_JOB_STATUS : Arc> = Default::default(); static ref ASYNC_HTTP_STATUS : Arc>> = Arc::new(Mutex::new(HashMap::new())); static ref TEMPORARY_PASSWD : Arc> = Arc::new(Mutex::new("".to_owned())); + static ref IS_REMOTE_MODIFY_ENABLED_BY_CONTROL_PERMISSIONS : Arc>> = Arc::new(Mutex::new(None)); } #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -79,6 +80,11 @@ lazy_static::lazy_static! { static ref CHILDREN : Children = Default::default(); } +#[cfg(target_os = "windows")] +lazy_static::lazy_static! { + pub static ref IS_FILE_TRANSFER_ENABLED: Arc>> = Arc::new(Mutex::new(None)); +} + const INIT_ASYNC_JOB_STATUS: &str = " "; #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] @@ -1166,10 +1172,6 @@ async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver { *OPTIONS.lock().unwrap() = v; *OPTION_SYNCED.lock().unwrap() = true; - - #[cfg(target_os = "windows")] - { - let (ft, am) = { - let lock = OPTIONS.lock().unwrap(); - ( - lock.get(OPTION_ENABLE_FILE_TRANSFER).map(|x| x.to_string()).unwrap_or_default(), - lock.get(OPTION_ACCESS_MODE).map(|x| x.to_string()).unwrap_or_default(), - ) - }; - if ft != enable_file_transfer || am != access_mode { - let access_mode_enabled = match am.as_str() { - "full" => Some(true), - "view" => Some(false), - _ => None, - }; - let enabled = access_mode_enabled.unwrap_or(config::option2bool(OPTION_ENABLE_FILE_TRANSFER, &ft)); - clipboard::ContextSend::enable(enabled); - enable_file_transfer = ft; - access_mode = am; - } - } } Ok(Some(ipc::Data::Config((name, Some(value))))) => { if name == "id" { @@ -1251,6 +1231,19 @@ async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver { + *IS_REMOTE_MODIFY_ENABLED_BY_CONTROL_PERMISSIONS.lock().unwrap() = v; + } + #[cfg(target_os = "windows")] + Ok(Some(ipc::Data::FileTransferEnabledState(v))) => { + if let Some(enabled) = v { + let mut lock = IS_FILE_TRANSFER_ENABLED.lock().unwrap(); + if *lock != v { + clipboard::ContextSend::enable(enabled); + *lock = v; + } + } + } _ => {} } } @@ -1264,6 +1257,9 @@ async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver usize { hbb_common::config::ENCRYPT_MAX_LEN } + +pub fn is_remote_modify_enabled_by_control_permissions() -> Option { + *IS_REMOTE_MODIFY_ENABLED_BY_CONTROL_PERMISSIONS + .lock() + .unwrap() +}