Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 73 additions & 6 deletions crates/ironrdp-acceptor/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,37 @@ pub struct Acceptor {
pub(crate) creds: Option<Credentials>,
received_credentials: Option<Credentials>,
reactivation: bool,
honor_client_desktop_size: bool,
}

/// Minimum and maximum desktop dimension accepted from a client.
///
/// A desktop dimension in RDP is a `u16`; [MS-RDPBCGR] caps it at 8192, and
/// 200 is a conservative floor below which a request is treated as malformed.
const MIN_DESKTOP_DIM: u16 = 200;
const MAX_DESKTOP_DIM: u16 = 8192;

/// Returns the client-requested desktop size if both dimensions are within the
/// protocol-legal range, otherwise `None`.
fn validate_desktop_size(width: u16, height: u16) -> Option<DesktopSize> {
if (MIN_DESKTOP_DIM..=MAX_DESKTOP_DIM).contains(&width) && (MIN_DESKTOP_DIM..=MAX_DESKTOP_DIM).contains(&height) {
Some(DesktopSize { width, height })
} else {
None
}
}

/// Writes `size` into every Bitmap capability set in `capabilities`.
///
/// The server advertises its desktop size in the Bitmap capability set of the
/// Demand Active PDU; this keeps that advertisement in sync with `size`.
fn set_bitmap_desktop_size(capabilities: &mut [CapabilitySet], size: DesktopSize) {
for cap in capabilities.iter_mut() {
if let CapabilitySet::Bitmap(cap) = cap {
cap.desktop_width = size.width;
cap.desktop_height = size.height;
}
}
}

#[derive(Debug)]
Expand Down Expand Up @@ -76,9 +107,29 @@ impl Acceptor {
creds,
received_credentials: None,
reactivation: false,
honor_client_desktop_size: false,
}
}

/// Adopt the desktop size requested by the client in its Client Core Data
/// instead of the size this acceptor was constructed with.
///
/// The client's requested resolution is only carried in the GCC Client
/// Core Data of the MCS Connect Initial PDU; the desktop size echoed back
/// later in the client's Confirm Active is, per [MS-RDPBCGR] 2.2.1.13.2,
/// the value the client copied from the *server's* Demand Active, so it
/// cannot be used to discover what the client originally asked for. When
/// this is enabled and the client's request is within the protocol-legal
/// range, the acceptor negotiates that size from the start (it is written
/// into the server's Bitmap capability set before Demand Active is sent),
/// avoiding a Deactivation-Reactivation resize round trip.
///
/// Disabled by default, preserving the previous behavior of always
/// enforcing the server-provided size.
pub fn set_honor_client_desktop_size(&mut self, honor: bool) {
self.honor_client_desktop_size = honor;
}

pub fn new_deactivation_reactivation(
mut consumed: Acceptor,
static_channels: StaticChannelSet,
Expand All @@ -92,12 +143,7 @@ impl Acceptor {
return Err(general_err!("invalid acceptor state"));
};

for cap in consumed.server_capabilities.iter_mut() {
if let CapabilitySet::Bitmap(cap) = cap {
cap.desktop_width = desktop_size.width;
cap.desktop_height = desktop_size.height;
}
}
set_bitmap_desktop_size(&mut consumed.server_capabilities, desktop_size);
let state = AcceptorState::CapabilitiesSendServer {
early_capability,
channels: channels.clone(),
Expand All @@ -118,6 +164,7 @@ impl Acceptor {
creds: consumed.creds,
received_credentials: consumed.received_credentials,
reactivation: true,
honor_client_desktop_size: consumed.honor_client_desktop_size,
})
}

Expand Down Expand Up @@ -427,6 +474,26 @@ impl Sequence for Acceptor {
let gcc_blocks = settings_initial.conference_create_request.into_gcc_blocks();
let early_capability = gcc_blocks.core.optional_data.early_capability_flags;

// Adopt the client's requested desktop size (from its Client
// Core Data) before Demand Active is sent, so the session is
// negotiated at that size without a Deactivation-Reactivation
// resize. See `set_honor_client_desktop_size`.
if self.honor_client_desktop_size {
if let Some(client_size) =
validate_desktop_size(gcc_blocks.core.desktop_width, gcc_blocks.core.desktop_height)
{
if client_size != self.desktop_size {
debug!(
requested = ?client_size,
previous = ?self.desktop_size,
"Honoring client-requested desktop size"
);
self.desktop_size = client_size;
set_bitmap_desktop_size(&mut self.server_capabilities, client_size);
}
}
}

let joined: Vec<_> = gcc_blocks
.network
.map(|network| {
Expand Down
23 changes: 23 additions & 0 deletions crates/ironrdp-server/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub struct BuilderDone {
#[cfg(feature = "egfx")]
gfx_factory: Option<Box<dyn GfxServerFactory>>,
display_suppressed: Option<Arc<AtomicBool>>,
honor_client_desktop_size: bool,
}

pub struct RdpServerBuilder<State> {
Expand Down Expand Up @@ -140,6 +141,7 @@ impl RdpServerBuilder<WantsDisplay> {
#[cfg(feature = "egfx")]
gfx_factory: None,
display_suppressed: None,
honor_client_desktop_size: false,
},
}
}
Expand All @@ -160,6 +162,7 @@ impl RdpServerBuilder<WantsDisplay> {
#[cfg(feature = "egfx")]
gfx_factory: None,
display_suppressed: None,
honor_client_desktop_size: false,
},
}
}
Expand Down Expand Up @@ -226,6 +229,25 @@ impl RdpServerBuilder<BuilderDone> {
self
}

/// Negotiate each session at the desktop size the client requests in its
/// Client Core Data, rather than the size reported by the display handler.
///
/// The client's requested resolution is only carried in the GCC Client
/// Core Data of the connection handshake; the size echoed back in the
/// client's Confirm Active is the value it copied from the server's Demand
/// Active (per [MS-RDPBCGR] 2.2.1.13.2) and so cannot reveal what the
/// client asked for. With this enabled the acceptor adopts the requested
/// size (when within the protocol-legal range) before Demand Active is
/// sent, so the session starts at that size with no Deactivation-
/// Reactivation resize. The display handler observes the negotiated size
/// through [`RdpServerDisplay::request_initial_size`].
///
/// Defaults to `false`, enforcing the size reported by the display handler.
pub fn with_honor_client_desktop_size(mut self, honor: bool) -> Self {
self.state.honor_client_desktop_size = honor;
self
}

/// Set a credential validator for TLS-mode connections.
///
/// When set, credentials received from the client during
Expand All @@ -248,6 +270,7 @@ impl RdpServerBuilder<BuilderDone> {
security: self.state.security,
codecs: self.state.codecs,
max_request_size: self.state.max_request_size,
honor_client_desktop_size: self.state.honor_client_desktop_size,
},
self.state.handler,
self.state.display,
Expand Down
7 changes: 7 additions & 0 deletions crates/ironrdp-server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,12 @@ pub struct RdpServerOptions {
pub security: RdpServerSecurity,
pub codecs: BitmapCodecs,
pub max_request_size: u32,
/// When `true`, each connection's acceptor adopts the desktop size the
/// client requests in its Client Core Data (instead of the size reported
/// by the display handler), negotiating that size from the start without a
/// Deactivation-Reactivation resize. Defaults to `false`. Set via
/// [`RdpServerBuilder::with_honor_client_desktop_size`](crate::RdpServerBuilder::with_honor_client_desktop_size).
pub honor_client_desktop_size: bool,
}

impl RdpServerOptions {
Expand Down Expand Up @@ -692,6 +698,7 @@ impl RdpServer {
let size = self.display.lock().await.size().await;
let capabilities = capabilities::capabilities(&self.opts, size);
let mut acceptor = Acceptor::new(self.opts.security.flag(), size, capabilities, self.creds.clone());
acceptor.set_honor_client_desktop_size(self.opts.honor_client_desktop_size);

self.attach_channels(&mut acceptor);

Expand Down
Loading