Skip to content

Commit

Permalink
feat(cliprdr): Implement CLIPRDR SVC for Windows platform.
Browse files Browse the repository at this point in the history
This branch also includes refactoring changes by @ibeckermayer from #196 and #197

Co-authored-by: Isaiah Becker-Mayer <isaiah@goteleport.com>
  • Loading branch information
pacmancoder and Isaiah Becker-Mayer committed Sep 26, 2023
1 parent fe1567c commit cf7d682
Show file tree
Hide file tree
Showing 34 changed files with 1,868 additions and 234 deletions.
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ ironrdp-acceptor = { version = "0.1", path = "crates/ironrdp-acceptor" }
ironrdp-async = { version = "0.1", path = "crates/ironrdp-async" }
ironrdp-blocking = { version = "0.1", path = "crates/ironrdp-blocking" }
ironrdp-cliprdr = { version = "0.1", path = "crates/ironrdp-cliprdr" }
ironrdp-cliprdr-native = { version = "0.1", path = "crates/ironrdp-cliprdr-native" }
ironrdp-connector = { version = "0.1", path = "crates/ironrdp-connector" }
ironrdp-dvc = { version = "0.1", path = "crates/ironrdp-dvc" }
ironrdp-error = { version = "0.1", path = "crates/ironrdp-error" }
Expand Down
6 changes: 6 additions & 0 deletions crates/ironrdp-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ native-tls = ["ironrdp-tls/native-tls"]

# Protocols
ironrdp = { workspace = true, features = ["input", "graphics", "dvc", "rdpdr", "rdpsnd", "cliprdr"] }
ironrdp-cliprdr-native.workspace = true
ironrdp-tls.workspace = true
ironrdp-tokio.workspace = true
ironrdp-rdpsnd.workspace = true
Expand Down Expand Up @@ -58,3 +59,8 @@ anyhow = "1"
smallvec = "1.10"
tap = "1"
semver = "1"

[target.'cfg(windows)'.dependencies]
windows = { version = "0.48.0", features = [
"Win32_Foundation",
] }
24 changes: 24 additions & 0 deletions crates/ironrdp-client/src/clipboard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use ironrdp::cliprdr::backend::{ClipboardMessage, ClipboardMessageProxy};
use tokio::sync::mpsc;

use crate::rdp::RdpInputEvent;

/// Shim for sending and receiving CLIPRDR events as `RdpInputEvent`
#[derive(Clone, Debug)]
pub struct ClientClipboardMessageProxy {
tx: mpsc::UnboundedSender<RdpInputEvent>,
}

impl ClientClipboardMessageProxy {
pub fn new(tx: mpsc::UnboundedSender<RdpInputEvent>) -> Self {
Self { tx }
}
}

impl ClipboardMessageProxy for ClientClipboardMessageProxy {
fn send_clipboard_message(&self, message: ClipboardMessage) {
if self.tx.send(RdpInputEvent::Clipboard(message)).is_err() {
error!("Failed to send os clipboard message, receiver is closed");
}
}
}
1 change: 1 addition & 0 deletions crates/ironrdp-client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#[macro_use]
extern crate tracing;

pub mod clipboard;
pub mod config;
pub mod gui;
pub mod rdp;
27 changes: 27 additions & 0 deletions crates/ironrdp-client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,37 @@ fn main() -> anyhow::Result<()> {

let (input_event_sender, input_event_receiver) = RdpInputEvent::create_channel();

#[cfg(not(windows))]
let cliprdr_factory = None;

#[cfg(windows)]
let (_win_clipboard, cliprdr_factory) = {
use ironrdp_client::clipboard::ClientClipboardMessageProxy;
use ironrdp_cliprdr_native::WinClipboard;
use windows::Win32::Foundation::HWND;
use winit::platform::windows::WindowExtWindows;

// SAFETY: provided window handle from `winit` is valid and is guaranteed to be alive
// while the gui window is still open.
let win_clipboard = unsafe {
WinClipboard::new(
HWND(gui.window.hwnd() as _),
ClientClipboardMessageProxy::new(input_event_sender.clone()),
)?
};

let factory = Some(win_clipboard.backend_factory());

// NOTE: we need to keep `win_clipboard` alive, otherwise it will be dropped before IronRDP
// starts and clipboard functionality will not be available.
(win_clipboard, factory)
};

let client = RdpClient {
config,
event_loop_proxy,
input_event_receiver,
cliprdr_factory,
};

debug!("Start RDP thread");
Expand Down
54 changes: 50 additions & 4 deletions crates/ironrdp-client/src/rdp.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use ironrdp::cliprdr::backend::{ClipboardMessage, CliprdrBackendFactory};
use ironrdp::cliprdr::Cliprdr;
use ironrdp::connector::sspi::network_client::reqwest_network_client::RequestClientFactory;
use ironrdp::connector::{ConnectionResult, ConnectorResult};
use ironrdp::graphics::image_processing::PixelFormat;
Expand Down Expand Up @@ -27,6 +29,7 @@ pub enum RdpInputEvent {
Resize { width: u16, height: u16 },
FastPath(SmallVec<[FastPathInputEvent; 2]>),
Close,
Clipboard(ClipboardMessage),
}

impl RdpInputEvent {
Expand All @@ -39,12 +42,13 @@ pub struct RdpClient {
pub config: Config,
pub event_loop_proxy: EventLoopProxy<RdpOutputEvent>,
pub input_event_receiver: mpsc::UnboundedReceiver<RdpInputEvent>,
pub cliprdr_factory: Option<Box<dyn CliprdrBackendFactory + Send>>,
}

impl RdpClient {
pub async fn run(mut self) {
loop {
let (connection_result, framed) = match connect(&self.config).await {
let (connection_result, framed) = match connect(&self.config, self.cliprdr_factory.as_deref()).await {
Ok(result) => result,
Err(e) => {
let _ = self.event_loop_proxy.send_event(RdpOutputEvent::ConnectionFailure(e));
Expand Down Expand Up @@ -84,7 +88,10 @@ enum RdpControlFlow {

type UpgradedFramed = ironrdp_tokio::TokioFramed<ironrdp_tls::TlsStream<TcpStream>>;

async fn connect(config: &Config) -> ConnectorResult<(ConnectionResult, UpgradedFramed)> {
async fn connect(
config: &Config,
cliprdr_factory: Option<&(dyn CliprdrBackendFactory + Send)>,
) -> ConnectorResult<(ConnectionResult, UpgradedFramed)> {
let server_addr = config
.destination
.lookup_addr()
Expand All @@ -102,8 +109,15 @@ async fn connect(config: &Config) -> ConnectorResult<(ConnectionResult, Upgraded
.with_credssp_network_client(RequestClientFactory)
// .with_static_channel(ironrdp::dvc::Drdynvc::new()) // FIXME: drdynvc is not working
.with_static_channel(ironrdp::rdpsnd::Rdpsnd::new())
.with_static_channel(ironrdp_rdpdr::Rdpdr::new("IronRDP".to_string()).with_smartcard(0))
.with_static_channel(ironrdp::cliprdr::Cliprdr::default());
.with_static_channel(ironrdp_rdpdr::Rdpdr::new("IronRDP".to_string()).with_smartcard(0));

if let Some(builder) = cliprdr_factory {
let backend = builder.build_cliprdr_backend();

let cliprdr = Cliprdr::new(backend);

connector.attach_static_channel(cliprdr);
}

let should_upgrade = ironrdp_tokio::connect_begin(&mut framed, &mut connector).await?;

Expand Down Expand Up @@ -180,6 +194,38 @@ async fn active_session(
// TODO: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/27915739-8f77-487e-9927-55008af7fd68
break 'outer;
}
RdpInputEvent::Clipboard(event) => {
if let Some(cliprdr) = active_stage.get_svc_processor::<ironrdp::cliprdr::Cliprdr>() {
if let Some(svc_messages) = match event {
ClipboardMessage::SendInitiateCopy(formats) => {
Some(cliprdr.initiate_copy(&formats)
.map_err(|e| session::custom_err!("CLIPRDR", e))?)
}
ClipboardMessage::SendFormatData(response) => {
Some(cliprdr.submit_format_data(response)
.map_err(|e| session::custom_err!("CLIPRDR", e))?)
}
ClipboardMessage::SendInitiatePaste(format) => {
Some(cliprdr.initiate_paste(format)
.map_err(|e| session::custom_err!("CLIPRDR", e))?)
}
ClipboardMessage::Error(e) => {
error!("Clipboard backend error: {}", e);
None
}
} {
let frame = active_stage.process_svc_processor_messages(svc_messages)?;
// Send the messages to the server
vec![ActiveStageOutput::ResponseFrame(frame)]
} else {
// No messages to send to the server
Vec::new()
}
} else {
warn!("Clipboard event received, but Cliprdr is not available");
Vec::new()
}
}
}
}
};
Expand Down
29 changes: 29 additions & 0 deletions crates/ironrdp-cliprdr-native/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "ironrdp-cliprdr-native"
version = "0.1.0"
readme = "README.md"
description = "Native CLIPRDR static channel backend implementations for IronRDP"
edition.workspace = true
license.workspace = true

homepage.workspace = true
repository.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true

[dependencies]
ironrdp-cliprdr.workspace = true
ironrdp-svc.workspace = true
thiserror.workspace = true
tracing.workspace = true

[target.'cfg(windows)'.dependencies]

windows = { version = "0.48.0", features = [
"Win32_Foundation",
"Win32_System_DataExchange",
"Win32_System_Memory",
"Win32_UI_Shell",
"Win32_UI_WindowsAndMessaging"
] }
6 changes: 6 additions & 0 deletions crates/ironrdp-cliprdr-native/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// Native CLIPRDR backend implementations. Currently only Windows is supported.
#[cfg(windows)]
mod windows;
#[cfg(windows)]
pub use crate::windows::{WinClipboard, WinCliprdrError, WinCliprdrResult};
Loading

0 comments on commit cf7d682

Please sign in to comment.