Skip to content

Commit

Permalink
fix: save the disconnect reason from the set error info PDU (#547)
Browse files Browse the repository at this point in the history
The Set Error Info PDU is sent as a precursor to a server-side
disconnect and informs the client of the reason for the disconnection
which will follow. Once this PDU has been processed, the client MUST
store the error code so that the reason for the server disconnect
which will follow can be accurately reported to the user.
  • Loading branch information
zmb3 authored Sep 23, 2024
1 parent c04bc2d commit b55924e
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 46 deletions.
29 changes: 16 additions & 13 deletions crates/ironrdp-session/src/active_stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,14 +265,17 @@ impl TryFrom<x224::ProcessorOutput> for ActiveStageOutput {
fn try_from(value: x224::ProcessorOutput) -> Result<Self, Self::Error> {
match value {
x224::ProcessorOutput::ResponseFrame(frame) => Ok(Self::ResponseFrame(frame)),
x224::ProcessorOutput::Disconnect(reason) => {
let reason = match reason {
mcs::DisconnectReason::UserRequested => GracefulDisconnectReason::UserInitiated,
mcs::DisconnectReason::ProviderInitiated => GracefulDisconnectReason::ServerInitiated,
other => GracefulDisconnectReason::Other(other.description()),
x224::ProcessorOutput::Disconnect(desc) => {
let desc = match desc {
x224::DisconnectDescription::McsDisconnect(reason) => match reason {
mcs::DisconnectReason::ProviderInitiated => GracefulDisconnectReason::ServerInitiated,
mcs::DisconnectReason::UserRequested => GracefulDisconnectReason::UserInitiated,
other => GracefulDisconnectReason::Other(other.description().to_owned()),
},
x224::DisconnectDescription::ErrorInfo(info) => GracefulDisconnectReason::Other(info.description()),
};

Ok(Self::Terminate(reason))
Ok(Self::Terminate(desc))
}
x224::ProcessorOutput::DeactivateAll(cas) => Ok(Self::DeactivateAll(cas)),
}
Expand All @@ -281,25 +284,25 @@ impl TryFrom<x224::ProcessorOutput> for ActiveStageOutput {

/// Reasons for graceful disconnect. This type provides GUI-friendly descriptions for
/// disconnect reasons.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub enum GracefulDisconnectReason {
UserInitiated,
ServerInitiated,
Other(&'static str),
Other(String),
}

impl GracefulDisconnectReason {
pub fn description(&self) -> &'static str {
pub fn description(&self) -> String {
match self {
GracefulDisconnectReason::UserInitiated => "user initiated disconnect",
GracefulDisconnectReason::ServerInitiated => "server initiated disconnect",
GracefulDisconnectReason::Other(description) => description,
GracefulDisconnectReason::UserInitiated => "user initiated disconnect".to_owned(),
GracefulDisconnectReason::ServerInitiated => "server initiated disconnect".to_owned(),
GracefulDisconnectReason::Other(description) => description.clone(),
}
}
}

impl core::fmt::Display for GracefulDisconnectReason {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.description())
f.write_str(&self.description())
}
}
50 changes: 18 additions & 32 deletions crates/ironrdp-session/src/x224/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,26 @@ pub enum ProcessorOutput {
/// A buffer with encoded data to send to the server.
ResponseFrame(Vec<u8>),
/// A graceful disconnect notification. Client should close the connection upon receiving this.
Disconnect(DisconnectReason),
Disconnect(DisconnectDescription),
/// Received a [`ironrdp_pdu::rdp::headers::ServerDeactivateAll`] PDU. Client should execute the
/// [Deactivation-Reactivation Sequence].
///
/// [Deactivation-Reactivation Sequence]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/dfc234ce-481a-4674-9a5d-2a7bafb14432
DeactivateAll(Box<ConnectionActivationSequence>),
}

#[derive(Debug, Clone)]
pub enum DisconnectDescription {
/// Includes the reason from the MCS Disconnect Provider Ultimatum.
/// This is the least-specific disconnect reason and is only used
/// when a more specific disconnect code is not available.
McsDisconnect(DisconnectReason),

/// Includes the error information sent by the RDP server when there
/// is a connection or disconnection failure.
ErrorInfo(ErrorInfo),
}

pub struct Processor {
static_channels: StaticChannelSet,
user_channel_id: u16,
Expand Down Expand Up @@ -123,15 +135,8 @@ impl Processor {
// in [MS-RDPBCGR].
//
// [MS-RDPBCGR]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/149070b0-ecec-4c20-af03-934bbc48adb8
let graceful_disconnect = error_info_to_graceful_disconnect_reason(&e);

if let Some(reason) = graceful_disconnect {
debug!("Received server-side graceful disconnect request: {reason}");

Ok(vec![ProcessorOutput::Disconnect(reason)])
} else {
Err(reason_err!("ServerSetErrorInfo", "{}", e.description()))
}
let desc = DisconnectDescription::ErrorInfo(e);
Ok(vec![ProcessorOutput::Disconnect(desc)])
}
ShareDataPdu::ShutdownDenied => {
debug!("ShutdownDenied received, session will be closed");
Expand All @@ -149,7 +154,9 @@ impl Processor {

Ok(vec![
ProcessorOutput::ResponseFrame(encoded_pdu?),
ProcessorOutput::Disconnect(DisconnectReason::UserRequested),
ProcessorOutput::Disconnect(DisconnectDescription::McsDisconnect(
DisconnectReason::UserRequested,
)),
])
}
_ => Err(reason_err!(
Expand Down Expand Up @@ -183,24 +190,3 @@ impl Processor {
fn process_svc_messages(messages: Vec<SvcMessage>, channel_id: u16, initiator_id: u16) -> SessionResult<Vec<u8>> {
client_encode_svc_messages(messages, channel_id, initiator_id).map_err(SessionError::encode)
}

/// Converts an [`ErrorInfo`] into a [`DisconnectReason`].
///
/// Returns `None` if the error code is not a graceful disconnect code.
pub fn error_info_to_graceful_disconnect_reason(error_info: &ErrorInfo) -> Option<DisconnectReason> {
let code = if let ErrorInfo::ProtocolIndependentCode(code) = error_info {
code
} else {
return None;
};

match code {
ProtocolIndependentCode::RpcInitiatedDisconnect
| ProtocolIndependentCode::RpcInitiatedLogoff
| ProtocolIndependentCode::DisconnectedByOtherconnection => Some(DisconnectReason::ProviderInitiated),
ProtocolIndependentCode::RpcInitiatedDisconnectByuser | ProtocolIndependentCode::LogoffByUser => {
Some(DisconnectReason::UserRequested)
}
_ => None,
}
}
2 changes: 1 addition & 1 deletion ffi/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ pub mod ffi {

pub fn get_terminate(&self) -> Result<Box<GracefulDisconnectReason>, Box<IronRdpError>> {
match &self.0 {
ironrdp::session::ActiveStageOutput::Terminate(reason) => Ok(GracefulDisconnectReason(*reason)),
ironrdp::session::ActiveStageOutput::Terminate(reason) => Ok(GracefulDisconnectReason(reason.clone())),
_ => Err(IncorrectEnumTypeError::on_variant("Terminate")
.of_enum("ActiveStageOutput")
.into()),
Expand Down

0 comments on commit b55924e

Please sign in to comment.