From 16a98d578ac6116e3db516dc5c7bb4478b91d843 Mon Sep 17 00:00:00 2001 From: lamco-office Date: Tue, 23 Dec 2025 13:20:02 +0200 Subject: [PATCH 1/4] fix(server): enable reqwest feature for ironrdp-tokio dependency The server uses `ironrdp_tokio::reqwest::ReqwestNetworkClient` in accept_credssp but the reqwest feature was not enabled, causing compilation to fail. Fixes #1062 --- crates/ironrdp-server/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ironrdp-server/Cargo.toml b/crates/ironrdp-server/Cargo.toml index b1e4650cd..964a83f6f 100644 --- a/crates/ironrdp-server/Cargo.toml +++ b/crates/ironrdp-server/Cargo.toml @@ -39,7 +39,7 @@ ironrdp-svc = { path = "../ironrdp-svc", version = "0.5" } # public ironrdp-cliprdr = { path = "../ironrdp-cliprdr", version = "0.5" } # public ironrdp-displaycontrol = { path = "../ironrdp-displaycontrol", version = "0.4" } # public ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.4" } # public -ironrdp-tokio = { path = "../ironrdp-tokio", version = "0.8" } +ironrdp-tokio = { path = "../ironrdp-tokio", version = "0.8", features = ["reqwest"] } ironrdp-acceptor = { path = "../ironrdp-acceptor", version = "0.8" } # public ironrdp-graphics = { path = "../ironrdp-graphics", version = "0.7" } # public ironrdp-rdpsnd = { path = "../ironrdp-rdpsnd", version = "0.6" } # public From c455f2e6fe174ed3f3c5bed853e017ad8c3cdd3e Mon Sep 17 00:00:00 2001 From: lamco-office Date: Tue, 23 Dec 2025 13:21:32 +0200 Subject: [PATCH 2/4] feat(cliprdr): add clipboard data locking methods Per MS-RDPECLIP sections 2.2.4.6 and 2.2.4.7, the Local Clipboard Owner may lock the Shared Clipboard Owner's clipboard data before requesting file contents. This ensures data stability during multi-request file transfers. Adds lock_clipboard() and unlock_clipboard() methods with corresponding ClipboardMessage variants for server integration. --- crates/ironrdp-client/src/rdp.rs | 8 ++++++++ crates/ironrdp-cliprdr/src/backend.rs | 10 ++++++++++ crates/ironrdp-cliprdr/src/lib.rs | 28 ++++++++++++++++++++++++++- crates/ironrdp-server/src/server.rs | 4 ++++ crates/ironrdp-web/src/session.rs | 8 ++++++++ ffi/src/clipboard/message.rs | 8 ++++++++ 6 files changed, 65 insertions(+), 1 deletion(-) diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index a6693fba2..7ab427215 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -565,6 +565,14 @@ async fn active_session( Some(cliprdr.initiate_paste(format) .map_err(|e| session::custom_err!("CLIPRDR", e))?) } + ClipboardMessage::SendLockClipboard { clip_data_id } => { + Some(cliprdr.lock_clipboard(clip_data_id) + .map_err(|e| session::custom_err!("CLIPRDR", e))?) + } + ClipboardMessage::SendUnlockClipboard { clip_data_id } => { + Some(cliprdr.unlock_clipboard(clip_data_id) + .map_err(|e| session::custom_err!("CLIPRDR", e))?) + } ClipboardMessage::Error(e) => { error!("Clipboard backend error: {}", e); None diff --git a/crates/ironrdp-cliprdr/src/backend.rs b/crates/ironrdp-cliprdr/src/backend.rs index 7d2bd59c3..1f97747fc 100644 --- a/crates/ironrdp-cliprdr/src/backend.rs +++ b/crates/ironrdp-cliprdr/src/backend.rs @@ -33,6 +33,16 @@ pub enum ClipboardMessage { /// received. SendInitiatePaste(ClipboardFormatId), + /// Sent by clipboard backend when clipboard data should be locked on the remote. + /// + /// Implementation should send lock clipboard data PDU on `CLIPRDR` SVC when received. + SendLockClipboard { clip_data_id: u32 }, + + /// Sent by clipboard backend when clipboard data should be unlocked on the remote. + /// + /// Implementation should send unlock clipboard data PDU on `CLIPRDR` SVC when received. + SendUnlockClipboard { clip_data_id: u32 }, + /// Failure received from the OS clipboard event loop. /// /// Client implementation should log/display this error. diff --git a/crates/ironrdp-cliprdr/src/lib.rs b/crates/ironrdp-cliprdr/src/lib.rs index 41f5d62ca..b8e57e603 100644 --- a/crates/ironrdp-cliprdr/src/lib.rs +++ b/crates/ironrdp-cliprdr/src/lib.rs @@ -15,7 +15,7 @@ use ironrdp_svc::{ }; use pdu::{ Capabilities, ClientTemporaryDirectory, ClipboardFormat, ClipboardFormatId, ClipboardGeneralCapabilityFlags, - ClipboardPdu, ClipboardProtocolVersion, FileContentsResponse, FormatDataRequest, FormatListResponse, + ClipboardPdu, ClipboardProtocolVersion, FileContentsResponse, FormatDataRequest, FormatListResponse, LockDataId, OwnedFormatDataResponse, }; use tracing::{error, info}; @@ -276,6 +276,32 @@ impl Cliprdr { Ok(vec![into_cliprdr_message(pdu)].into()) } + + /// [2.2.4.6] Lock Clipboard Data PDU (CLIPRDR_LOCK_CLIPDATA) + /// + /// Locks clipboard data on the remote before file transfer. Should be called before + /// requesting file contents to ensure data stability during transfer. + /// + /// [2.2.4.6]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeclip/150bac72-bc7f-42e5-9e8e-cb5a0ddc7dbc + pub fn lock_clipboard(&self, clip_data_id: u32) -> PduResult> { + ready_guard!(self, lock_clipboard); + + let pdu = ClipboardPdu::LockData(LockDataId(clip_data_id)); + Ok(vec![into_cliprdr_message(pdu)].into()) + } + + /// [2.2.4.7] Unlock Clipboard Data PDU (CLIPRDR_UNLOCK_CLIPDATA) + /// + /// Unlocks previously locked clipboard data. Should be called after file transfer + /// operations complete. + /// + /// [2.2.4.7]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeclip/e587a20c-fb7c-47d1-8698-4bcb92c48a38 + pub fn unlock_clipboard(&self, clip_data_id: u32) -> PduResult> { + ready_guard!(self, unlock_clipboard); + + let pdu = ClipboardPdu::UnlockData(LockDataId(clip_data_id)); + Ok(vec![into_cliprdr_message(pdu)].into()) + } } impl SvcProcessor for Cliprdr { diff --git a/crates/ironrdp-server/src/server.rs b/crates/ironrdp-server/src/server.rs index 9fe234d84..7a27993e8 100644 --- a/crates/ironrdp-server/src/server.rs +++ b/crates/ironrdp-server/src/server.rs @@ -552,6 +552,10 @@ impl RdpServer { ClipboardMessage::SendInitiateCopy(formats) => cliprdr.initiate_copy(&formats), ClipboardMessage::SendFormatData(data) => cliprdr.submit_format_data(data), ClipboardMessage::SendInitiatePaste(format) => cliprdr.initiate_paste(format), + ClipboardMessage::SendLockClipboard { clip_data_id } => cliprdr.lock_clipboard(clip_data_id), + ClipboardMessage::SendUnlockClipboard { clip_data_id } => { + cliprdr.unlock_clipboard(clip_data_id) + } ClipboardMessage::Error(error) => { error!(?error, "Handling clipboard event"); continue; diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 6d0e018ac..f56bde812 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -526,6 +526,14 @@ impl iron_remote_desktop::Session for Session { cliprdr.initiate_paste(format) .context("CLIPRDR initiate paste")? ), + ClipboardMessage::SendLockClipboard { clip_data_id } => Some( + cliprdr.lock_clipboard(clip_data_id) + .context("CLIPRDR lock clipboard")? + ), + ClipboardMessage::SendUnlockClipboard { clip_data_id } => Some( + cliprdr.unlock_clipboard(clip_data_id) + .context("CLIPRDR unlock clipboard")? + ), ClipboardMessage::Error(e) => { error!("Clipboard backend error: {}", e); None diff --git a/ffi/src/clipboard/message.rs b/ffi/src/clipboard/message.rs index 1ab5d003b..8f9515fe8 100644 --- a/ffi/src/clipboard/message.rs +++ b/ffi/src/clipboard/message.rs @@ -14,6 +14,12 @@ pub mod ffi { ironrdp::cliprdr::backend::ClipboardMessage::SendInitiatePaste(_) => { ClipboardMessageType::SendInitiatePaste } + ironrdp::cliprdr::backend::ClipboardMessage::SendLockClipboard { .. } => { + ClipboardMessageType::SendLockClipboard + } + ironrdp::cliprdr::backend::ClipboardMessage::SendUnlockClipboard { .. } => { + ClipboardMessageType::SendUnlockClipboard + } ironrdp::cliprdr::backend::ClipboardMessage::Error(_) => ClipboardMessageType::Error, } } @@ -51,6 +57,8 @@ pub mod ffi { SendInitiateCopy, SendFormatData, SendInitiatePaste, + SendLockClipboard, + SendUnlockClipboard, Error, } From 4ff56cf4f805298c24c0b07d3b2466135ca96e59 Mon Sep 17 00:00:00 2001 From: lamco-office Date: Tue, 23 Dec 2025 13:45:25 +0200 Subject: [PATCH 3/4] feat(cliprdr): add request_file_contents method Per MS-RDPECLIP section 2.2.5.3, the Local Clipboard Owner sends File Contents Request PDU to retrieve file data from the Shared Clipboard Owner. This enables server-side file paste operations. Adds request_file_contents() method with SendFileContentsRequest message variant for server integration. --- crates/ironrdp-client/src/rdp.rs | 4 ++++ crates/ironrdp-cliprdr/src/backend.rs | 5 +++++ crates/ironrdp-cliprdr/src/lib.rs | 18 ++++++++++++++++-- crates/ironrdp-server/src/server.rs | 1 + crates/ironrdp-web/src/session.rs | 4 ++++ ffi/src/clipboard/message.rs | 4 ++++ 6 files changed, 34 insertions(+), 2 deletions(-) diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index 7ab427215..cd8e19488 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -573,6 +573,10 @@ async fn active_session( Some(cliprdr.unlock_clipboard(clip_data_id) .map_err(|e| session::custom_err!("CLIPRDR", e))?) } + ClipboardMessage::SendFileContentsRequest(request) => { + Some(cliprdr.request_file_contents(request) + .map_err(|e| session::custom_err!("CLIPRDR", e))?) + } ClipboardMessage::Error(e) => { error!("Clipboard backend error: {}", e); None diff --git a/crates/ironrdp-cliprdr/src/backend.rs b/crates/ironrdp-cliprdr/src/backend.rs index 1f97747fc..322581777 100644 --- a/crates/ironrdp-cliprdr/src/backend.rs +++ b/crates/ironrdp-cliprdr/src/backend.rs @@ -43,6 +43,11 @@ pub enum ClipboardMessage { /// Implementation should send unlock clipboard data PDU on `CLIPRDR` SVC when received. SendUnlockClipboard { clip_data_id: u32 }, + /// Sent by clipboard backend when file contents are needed from the remote. + /// + /// Implementation should send file contents request on `CLIPRDR` SVC when received. + SendFileContentsRequest(FileContentsRequest), + /// Failure received from the OS clipboard event loop. /// /// Client implementation should log/display this error. diff --git a/crates/ironrdp-cliprdr/src/lib.rs b/crates/ironrdp-cliprdr/src/lib.rs index b8e57e603..66dc6cb58 100644 --- a/crates/ironrdp-cliprdr/src/lib.rs +++ b/crates/ironrdp-cliprdr/src/lib.rs @@ -15,8 +15,8 @@ use ironrdp_svc::{ }; use pdu::{ Capabilities, ClientTemporaryDirectory, ClipboardFormat, ClipboardFormatId, ClipboardGeneralCapabilityFlags, - ClipboardPdu, ClipboardProtocolVersion, FileContentsResponse, FormatDataRequest, FormatListResponse, LockDataId, - OwnedFormatDataResponse, + ClipboardPdu, ClipboardProtocolVersion, FileContentsRequest, FileContentsResponse, FormatDataRequest, + FormatListResponse, LockDataId, OwnedFormatDataResponse, }; use tracing::{error, info}; @@ -302,6 +302,20 @@ impl Cliprdr { let pdu = ClipboardPdu::UnlockData(LockDataId(clip_data_id)); Ok(vec![into_cliprdr_message(pdu)].into()) } + + /// [2.2.5.3] File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST) + /// + /// Requests file contents from the Shared Clipboard Owner. Should be called when + /// the Local Clipboard Owner needs file data after receiving a file list format. + /// The remote will respond via [`CliprdrBackend::on_file_contents_response`]. + /// + /// [2.2.5.3]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeclip/cbc851d3-4e68-45f4-9292-26872a9209f2 + pub fn request_file_contents(&self, request: FileContentsRequest) -> PduResult> { + ready_guard!(self, request_file_contents); + + let pdu = ClipboardPdu::FileContentsRequest(request); + Ok(vec![into_cliprdr_message(pdu)].into()) + } } impl SvcProcessor for Cliprdr { diff --git a/crates/ironrdp-server/src/server.rs b/crates/ironrdp-server/src/server.rs index 7a27993e8..317a3a0ca 100644 --- a/crates/ironrdp-server/src/server.rs +++ b/crates/ironrdp-server/src/server.rs @@ -556,6 +556,7 @@ impl RdpServer { ClipboardMessage::SendUnlockClipboard { clip_data_id } => { cliprdr.unlock_clipboard(clip_data_id) } + ClipboardMessage::SendFileContentsRequest(request) => cliprdr.request_file_contents(request), ClipboardMessage::Error(error) => { error!(?error, "Handling clipboard event"); continue; diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index f56bde812..859117fd5 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -534,6 +534,10 @@ impl iron_remote_desktop::Session for Session { cliprdr.unlock_clipboard(clip_data_id) .context("CLIPRDR unlock clipboard")? ), + ClipboardMessage::SendFileContentsRequest(request) => Some( + cliprdr.request_file_contents(request) + .context("CLIPRDR request file contents")? + ), ClipboardMessage::Error(e) => { error!("Clipboard backend error: {}", e); None diff --git a/ffi/src/clipboard/message.rs b/ffi/src/clipboard/message.rs index 8f9515fe8..295d0658a 100644 --- a/ffi/src/clipboard/message.rs +++ b/ffi/src/clipboard/message.rs @@ -20,6 +20,9 @@ pub mod ffi { ironrdp::cliprdr::backend::ClipboardMessage::SendUnlockClipboard { .. } => { ClipboardMessageType::SendUnlockClipboard } + ironrdp::cliprdr::backend::ClipboardMessage::SendFileContentsRequest(_) => { + ClipboardMessageType::SendFileContentsRequest + } ironrdp::cliprdr::backend::ClipboardMessage::Error(_) => ClipboardMessageType::Error, } } @@ -59,6 +62,7 @@ pub mod ffi { SendInitiatePaste, SendLockClipboard, SendUnlockClipboard, + SendFileContentsRequest, Error, } From eb8454707f261c492953373cb036eb7cb7acb4df Mon Sep 17 00:00:00 2001 From: lamco-office Date: Tue, 23 Dec 2025 15:21:30 +0200 Subject: [PATCH 4/4] feat(cliprdr): add SendFileContentsResponse message variant Adds SendFileContentsResponse to ClipboardMessage enum, enabling clipboard backends to signal when file data is ready to send via submit_file_contents(). This provides the message-based interface pattern used by server implementations for clipboard operations. --- crates/ironrdp-client/src/rdp.rs | 4 ++++ crates/ironrdp-cliprdr/src/backend.rs | 5 +++++ crates/ironrdp-server/src/server.rs | 1 + crates/ironrdp-web/src/session.rs | 4 ++++ ffi/src/clipboard/message.rs | 4 ++++ 5 files changed, 18 insertions(+) diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index cd8e19488..426a1100d 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -577,6 +577,10 @@ async fn active_session( Some(cliprdr.request_file_contents(request) .map_err(|e| session::custom_err!("CLIPRDR", e))?) } + ClipboardMessage::SendFileContentsResponse(response) => { + Some(cliprdr.submit_file_contents(response) + .map_err(|e| session::custom_err!("CLIPRDR", e))?) + } ClipboardMessage::Error(e) => { error!("Clipboard backend error: {}", e); None diff --git a/crates/ironrdp-cliprdr/src/backend.rs b/crates/ironrdp-cliprdr/src/backend.rs index 322581777..5a8d24d3d 100644 --- a/crates/ironrdp-cliprdr/src/backend.rs +++ b/crates/ironrdp-cliprdr/src/backend.rs @@ -48,6 +48,11 @@ pub enum ClipboardMessage { /// Implementation should send file contents request on `CLIPRDR` SVC when received. SendFileContentsRequest(FileContentsRequest), + /// Sent by clipboard backend when file contents data is ready to be sent to the remote. + /// + /// Implementation should send file contents response on `CLIPRDR` SVC when received. + SendFileContentsResponse(FileContentsResponse<'static>), + /// Failure received from the OS clipboard event loop. /// /// Client implementation should log/display this error. diff --git a/crates/ironrdp-server/src/server.rs b/crates/ironrdp-server/src/server.rs index 317a3a0ca..854020595 100644 --- a/crates/ironrdp-server/src/server.rs +++ b/crates/ironrdp-server/src/server.rs @@ -557,6 +557,7 @@ impl RdpServer { cliprdr.unlock_clipboard(clip_data_id) } ClipboardMessage::SendFileContentsRequest(request) => cliprdr.request_file_contents(request), + ClipboardMessage::SendFileContentsResponse(response) => cliprdr.submit_file_contents(response), ClipboardMessage::Error(error) => { error!(?error, "Handling clipboard event"); continue; diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 859117fd5..b760ec18c 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -538,6 +538,10 @@ impl iron_remote_desktop::Session for Session { cliprdr.request_file_contents(request) .context("CLIPRDR request file contents")? ), + ClipboardMessage::SendFileContentsResponse(response) => Some( + cliprdr.submit_file_contents(response) + .context("CLIPRDR submit file contents")? + ), ClipboardMessage::Error(e) => { error!("Clipboard backend error: {}", e); None diff --git a/ffi/src/clipboard/message.rs b/ffi/src/clipboard/message.rs index 295d0658a..ee1cc34dc 100644 --- a/ffi/src/clipboard/message.rs +++ b/ffi/src/clipboard/message.rs @@ -23,6 +23,9 @@ pub mod ffi { ironrdp::cliprdr::backend::ClipboardMessage::SendFileContentsRequest(_) => { ClipboardMessageType::SendFileContentsRequest } + ironrdp::cliprdr::backend::ClipboardMessage::SendFileContentsResponse(_) => { + ClipboardMessageType::SendFileContentsResponse + } ironrdp::cliprdr::backend::ClipboardMessage::Error(_) => ClipboardMessageType::Error, } } @@ -63,6 +66,7 @@ pub mod ffi { SendLockClipboard, SendUnlockClipboard, SendFileContentsRequest, + SendFileContentsResponse, Error, }