From 476afbed25155c5e21d4a9828534ed637356cf23 Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 2 Jun 2025 11:28:07 -0400 Subject: [PATCH 01/10] WIP --- Cargo.lock | 2 - devolutions-gateway/Cargo.toml | 2 +- devolutions-gateway/src/generic_client.rs | 1 + devolutions-gateway/src/rd_clean_path.rs | 110 ++++++++++++++++------ devolutions-gateway/src/rdp_pcb.rs | 4 + 5 files changed, 88 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e0bdca8f6..2bdbb9def 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2786,8 +2786,6 @@ dependencies = [ [[package]] name = "ironrdp-rdcleanpath" version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3f5401de43e86384ac0f7f356af8c0bdc321671853f76095da5d480d6998e0" dependencies = [ "der", ] diff --git a/devolutions-gateway/Cargo.toml b/devolutions-gateway/Cargo.toml index 4a546a080..29d5497da 100644 --- a/devolutions-gateway/Cargo.toml +++ b/devolutions-gateway/Cargo.toml @@ -28,8 +28,8 @@ job-queue = { path = "../crates/job-queue" } job-queue-libsql = { path = "../crates/job-queue-libsql" } ironrdp-pdu = { version = "0.5", features = ["std"] } ironrdp-core = { version = "0.1", features = ["std"] } -ironrdp-rdcleanpath = "0.1" ironrdp-tokio = "0.5" +ironrdp-rdcleanpath = { path = "C:\\dev\\IronRDP\\crates\\ironrdp-rdcleanpath" } ironrdp-connector = { version = "0.5" } ironrdp-acceptor = { version = "0.5" } ceviche = "0.6.1" diff --git a/devolutions-gateway/src/generic_client.rs b/devolutions-gateway/src/generic_client.rs index 13c1d9c48..c12edb7d3 100644 --- a/devolutions-gateway/src/generic_client.rs +++ b/devolutions-gateway/src/generic_client.rs @@ -77,6 +77,7 @@ where if conf.debug.dump_tokens { debug!(token, "**DEBUG OPTION**"); } + info!(pdu = ?pdu, "Received preconnection blob"); let source_ip = client_addr.ip(); diff --git a/devolutions-gateway/src/rd_clean_path.rs b/devolutions-gateway/src/rd_clean_path.rs index 8c4edb5f3..fb5e649bb 100644 --- a/devolutions-gateway/src/rd_clean_path.rs +++ b/devolutions-gateway/src/rd_clean_path.rs @@ -164,7 +164,7 @@ struct CleanPathResult { destination: TargetAddr, server_addr: SocketAddr, server_stream: tokio_rustls::client::TlsStream, - x224_rsp: Vec, + x224_rsp: Option>, } async fn process_cleanpath( @@ -234,45 +234,99 @@ async fn process_cleanpath( debug!(%selected_target, "Connected to destination server"); span.record("target", selected_target.to_string()); - // Send preconnection blob if applicable - if let Some(pcb) = cleanpath_pdu.preconnection_blob { - server_stream.write_all(pcb.as_bytes()).await?; - } + // Preconnection Blob (PCB) is currently only used for Hyper-V VMs. + // + // Connection sequence with Hyper-V VMs (PCB enabled): + // ┌─────────────────────┐ ┌─────────────────────────────────────────────────────────────┐ + // │ handled by │ │ handled by IronRDP client │ + // │ Gateway │ │ │ + // └─────────────────────┘ └─────────────────────────────────────────────────────────────┘ + // │PCB → TLS handshake │ → │CredSSP → X224 connection request → X224 connection response │ + // └─────────────────────┘ └─────────────────────────────────────────────────────────────┘ + // + // Connection sequence without Hyper-V VMs (PCB disabled): + // ┌─────────────────────────────────────────────────────────────┐ ┌──────────────────────┐ + // │ handled by Gateway │ │ handled by IronRDP │ + // │ │ │ client │ + // └─────────────────────────────────────────────────────────────┘ └──────────────────────┘ + // │X224 connection request → X224 connection response → TLS hs │ → │CredSSP → ... │ + // └─────────────────────────────────────────────────────────────┘ └──────────────────────┘ + // + // Summary: + // - With PCB: Gateway handles (1) sending PCB, (2) TLS handshake, then leaves CredSSP + // and X224 connection request/response to IronRDP client + // - Without PCB: Gateway handles (1) X224 connection request, (2) X224 connection response, + // then leaves TLS handshake and CredSSP to IronRDP client + let (server_stream, x224_rsp) = if let Some(pcb_string) = cleanpath_pdu.preconnection_blob { + let pcb = ironrdp_pdu::pcb::PreconnectionBlob { + version: ironrdp_pdu::pcb::PcbVersion::V2, + id: 0, + v2_payload: Some(pcb_string), + }; + + let encoded = ironrdp_core::encode_vec(&pcb) + .context("failed to encode preconnection blob") + .map_err(CleanPathError::BadRequest)?; + + server_stream.write_all(&encoded).await?; + + let server_stream = crate::tls::connect(selected_target.host(), server_stream) + .await + .map_err(|source| CleanPathError::TlsHandshake { + source, + target_server: selected_target.to_owned(), + })?; - // Send X224 connection request - let x224_req = cleanpath_pdu - .x224_connection_pdu - .context("request is missing X224 connection PDU") - .map_err(CleanPathError::BadRequest)?; - server_stream.write_all(x224_req.as_bytes()).await?; + (server_stream, None) + } else { + debug!("Preconnection blob sent"); - // Receive server X224 connection response + // Send X224 connection request + let x224_req = cleanpath_pdu + .x224_connection_pdu + .context("request is missing X224 connection PDU") + .map_err(CleanPathError::BadRequest)?; - trace!("Receiving X224 response"); + server_stream.write_all(x224_req.as_bytes()).await?; - let x224_rsp = read_x224_response(&mut server_stream) - .await - .with_context(|| format!("read X224 response from {selected_target}")) - .map_err(CleanPathError::BadRequest)?; + let server_stream = crate::tls::connect(selected_target.host().to_owned(), server_stream) + .await + .map_err(|source| CleanPathError::TlsHandshake { + source, + target_server: selected_target.to_owned(), + })?; + debug!("X224 connection request sent"); - trace!("Establishing TLS connection with server"); + // Receive server X224 connection response - // Establish TLS connection with server + trace!("Receiving X224 response"); - let server_stream = crate::tls::connect(selected_target.host().to_owned(), server_stream) - .await - .map_err(|source| CleanPathError::TlsHandshake { - source, - target_server: selected_target.to_owned(), - })?; + let x224_rsp = read_x224_response(&mut server_stream) + .await + .with_context(|| format!("read X224 response from {selected_target}")) + .map_err(CleanPathError::BadRequest)?; + + trace!("Establishing TLS connection with server"); + + // Establish TLS connection with server + + let server_stream = crate::tls::connect(selected_target.host(), server_stream) + .await + .map_err(|source| CleanPathError::TlsHandshake { + source, + target_server: selected_target.to_owned(), + })?; + + (server_stream, Some(x224_rsp)) + }; - Ok(CleanPathResult { + return Ok(CleanPathResult { destination: selected_target.to_owned(), claims, server_addr, server_stream, x224_rsp, - }) + }); } #[allow(clippy::too_many_arguments)] @@ -295,7 +349,7 @@ pub async fn handle( .await .context("couldn’t read clean cleanpath PDU")?; - trace!("Processing RDCleanPath"); + trace!(RDCleanPath = ?cleanpath_pdu,"Processing RDCleanPath"); let CleanPathResult { claims, diff --git a/devolutions-gateway/src/rdp_pcb.rs b/devolutions-gateway/src/rdp_pcb.rs index 3ff270deb..f0f462011 100644 --- a/devolutions-gateway/src/rdp_pcb.rs +++ b/devolutions-gateway/src/rdp_pcb.rs @@ -54,6 +54,10 @@ fn decode_pcb(buf: &[u8]) -> Result, io::Erro Ok(pcb) => { let pdu_size = ironrdp_core::size(&pcb); let read_len = cursor.pos(); + info!( + pdu_size, + read_len, "read preconnection blob (size: {}, read: {})", pdu_size, read_len + ); // NOTE: sanity check (reporting the wrong number will corrupt the communication) if read_len != pdu_size { From c964c47257b714d81f36a86b28cf2e80e2cb9091 Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 2 Jun 2025 17:36:05 -0400 Subject: [PATCH 02/10] works --- devolutions-gateway/src/generic_client.rs | 1 - devolutions-gateway/src/rd_clean_path.rs | 27 +++++++---------------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/devolutions-gateway/src/generic_client.rs b/devolutions-gateway/src/generic_client.rs index c12edb7d3..13c1d9c48 100644 --- a/devolutions-gateway/src/generic_client.rs +++ b/devolutions-gateway/src/generic_client.rs @@ -77,7 +77,6 @@ where if conf.debug.dump_tokens { debug!(token, "**DEBUG OPTION**"); } - info!(pdu = ?pdu, "Received preconnection blob"); let source_ip = client_addr.ip(); diff --git a/devolutions-gateway/src/rd_clean_path.rs b/devolutions-gateway/src/rd_clean_path.rs index fb5e649bb..1b15cde05 100644 --- a/devolutions-gateway/src/rd_clean_path.rs +++ b/devolutions-gateway/src/rd_clean_path.rs @@ -258,6 +258,7 @@ async fn process_cleanpath( // - Without PCB: Gateway handles (1) X224 connection request, (2) X224 connection response, // then leaves TLS handshake and CredSSP to IronRDP client let (server_stream, x224_rsp) = if let Some(pcb_string) = cleanpath_pdu.preconnection_blob { + debug!("Sending preconnection blob to server"); let pcb = ironrdp_pdu::pcb::PreconnectionBlob { version: ironrdp_pdu::pcb::PcbVersion::V2, id: 0, @@ -270,7 +271,7 @@ async fn process_cleanpath( server_stream.write_all(&encoded).await?; - let server_stream = crate::tls::connect(selected_target.host(), server_stream) + let server_stream = crate::tls::connect(selected_target.host().to_owned(), server_stream) .await .map_err(|source| CleanPathError::TlsHandshake { source, @@ -279,8 +280,6 @@ async fn process_cleanpath( (server_stream, None) } else { - debug!("Preconnection blob sent"); - // Send X224 connection request let x224_req = cleanpath_pdu .x224_connection_pdu @@ -289,6 +288,12 @@ async fn process_cleanpath( server_stream.write_all(x224_req.as_bytes()).await?; + let x224_rsp = read_x224_response(&mut server_stream) + .await + .with_context(|| format!("read X224 response from {selected_target}")) + .map_err(CleanPathError::BadRequest)?; + trace!("Receiving X224 response"); + let server_stream = crate::tls::connect(selected_target.host().to_owned(), server_stream) .await .map_err(|source| CleanPathError::TlsHandshake { @@ -299,24 +304,8 @@ async fn process_cleanpath( // Receive server X224 connection response - trace!("Receiving X224 response"); - - let x224_rsp = read_x224_response(&mut server_stream) - .await - .with_context(|| format!("read X224 response from {selected_target}")) - .map_err(CleanPathError::BadRequest)?; - trace!("Establishing TLS connection with server"); - // Establish TLS connection with server - - let server_stream = crate::tls::connect(selected_target.host(), server_stream) - .await - .map_err(|source| CleanPathError::TlsHandshake { - source, - target_server: selected_target.to_owned(), - })?; - (server_stream, Some(x224_rsp)) }; From a10f4b9125350a691b2e50c4c2b332edd288389e Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 26 Jun 2025 11:18:00 -0400 Subject: [PATCH 03/10] review fix --- devolutions-gateway/src/rd_clean_path.rs | 93 +++++++++++++----------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/devolutions-gateway/src/rd_clean_path.rs b/devolutions-gateway/src/rd_clean_path.rs index 1b15cde05..ff7099a9b 100644 --- a/devolutions-gateway/src/rd_clean_path.rs +++ b/devolutions-gateway/src/rd_clean_path.rs @@ -257,36 +257,38 @@ async fn process_cleanpath( // and X224 connection request/response to IronRDP client // - Without PCB: Gateway handles (1) X224 connection request, (2) X224 connection response, // then leaves TLS handshake and CredSSP to IronRDP client - let (server_stream, x224_rsp) = if let Some(pcb_string) = cleanpath_pdu.preconnection_blob { - debug!("Sending preconnection blob to server"); - let pcb = ironrdp_pdu::pcb::PreconnectionBlob { - version: ironrdp_pdu::pcb::PcbVersion::V2, - id: 0, - v2_payload: Some(pcb_string), - }; - - let encoded = ironrdp_core::encode_vec(&pcb) - .context("failed to encode preconnection blob") - .map_err(CleanPathError::BadRequest)?; - - server_stream.write_all(&encoded).await?; - - let server_stream = crate::tls::connect(selected_target.host().to_owned(), server_stream) - .await - .map_err(|source| CleanPathError::TlsHandshake { - source, - target_server: selected_target.to_owned(), - })?; - - (server_stream, None) - } else { - // Send X224 connection request - let x224_req = cleanpath_pdu - .x224_connection_pdu - .context("request is missing X224 connection PDU") - .map_err(CleanPathError::BadRequest)?; + // Send preconnection blob and/or X224 connection request + match (&cleanpath_pdu.preconnection_blob, &cleanpath_pdu.x224_connection_pdu) { + (None, None) => { + return Err(CleanPathError::BadRequest(anyhow::anyhow!( + "RDCleanPath PDU is missing both preconnection blob and X224 connection PDU" + ))); + } + (Some(general_pcb), Some(_)) => { + server_stream.write_all(general_pcb.as_bytes()).await?; + } + (None, Some(_)) => {} // no preconnection blob to send + // This is considered to be the case where the preconnection blob is used for Hyper-V VMs connection + (Some(pcb), None) => { + debug!("Sending preconnection blob to server"); + let pcb = ironrdp_pdu::pcb::PreconnectionBlob { + version: ironrdp_pdu::pcb::PcbVersion::V2, + id: 0, + v2_payload: Some(pcb.clone()), + }; + + let encoded = ironrdp_core::encode_vec(&pcb) + .context("failed to encode preconnection blob") + .map_err(CleanPathError::BadRequest)?; + + server_stream.write_all(&encoded).await?; + } + } - server_stream.write_all(x224_req.as_bytes()).await?; + // Send X224 connection request if present + let x224_rsp = if let Some(x224_connection_pdu) = &cleanpath_pdu.x224_connection_pdu { + server_stream.write_all(x224_connection_pdu.as_bytes()).await?; + debug!("X224 connection request sent"); let x224_rsp = read_x224_response(&mut server_stream) .await @@ -294,21 +296,20 @@ async fn process_cleanpath( .map_err(CleanPathError::BadRequest)?; trace!("Receiving X224 response"); - let server_stream = crate::tls::connect(selected_target.host().to_owned(), server_stream) - .await - .map_err(|source| CleanPathError::TlsHandshake { - source, - target_server: selected_target.to_owned(), - })?; - debug!("X224 connection request sent"); - - // Receive server X224 connection response - - trace!("Establishing TLS connection with server"); - - (server_stream, Some(x224_rsp)) + Some(x224_rsp) + } else { + None }; + // Establish TLS connection + trace!("Establishing TLS connection with server"); + let server_stream = crate::tls::connect(selected_target.host().to_owned(), server_stream) + .await + .map_err(|source| CleanPathError::TlsHandshake { + source, + target_server: selected_target.to_owned(), + })?; + return Ok(CleanPathResult { destination: selected_target.to_owned(), claims, @@ -379,8 +380,12 @@ pub async fn handle( trace!("Sending RDCleanPath response"); - let rdcleanpath_rsp = RDCleanPathPdu::new_response(server_addr.to_string(), x224_rsp, x509_chain) - .map_err(|e| anyhow::anyhow!("couldn’t build RDCleanPath response: {e}"))?; + let rdcleanpath_rsp = RDCleanPathPdu::new_response( + server_addr.to_string(), + x224_rsp, + x509_chain, + ) + .map_err(|e| anyhow::anyhow!("couldn’t build RDCleanPath response: {e}"))?; send_clean_path_response(&mut client_stream, &rdcleanpath_rsp).await?; From dabf8e418fccce70bf1665777784333d72a51d71 Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 4 Jul 2025 14:25:25 -0400 Subject: [PATCH 04/10] review fix --- devolutions-agent/src/config.rs | 7 +-- .../src/remote_desktop/graphics.rs | 10 ++-- devolutions-gateway/src/api/net.rs | 29 ++++----- devolutions-gateway/src/credential.rs | 6 ++ devolutions-gateway/src/rd_clean_path.rs | 60 +++++++++---------- devolutions-gateway/src/tls.rs | 2 +- jetsocat/src/main.rs | 4 +- 7 files changed, 56 insertions(+), 62 deletions(-) diff --git a/devolutions-agent/src/config.rs b/devolutions-agent/src/config.rs index 1e6dc64a7..f04f51d50 100644 --- a/devolutions-agent/src/config.rs +++ b/devolutions-agent/src/config.rs @@ -244,17 +244,12 @@ pub mod dto { #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] + #[derive(Default)] pub struct PedmConf { /// Enable PEDM module (disabled by default) pub enabled: bool, } - impl Default for PedmConf { - fn default() -> Self { - Self { enabled: false } - } - } - #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct SessionConf { diff --git a/devolutions-agent/src/remote_desktop/graphics.rs b/devolutions-agent/src/remote_desktop/graphics.rs index 0417dab0a..187ad4ce4 100644 --- a/devolutions-agent/src/remote_desktop/graphics.rs +++ b/devolutions-agent/src/remote_desktop/graphics.rs @@ -46,10 +46,12 @@ impl RdpServerDisplayUpdates for DisplayUpdates { let left: u16 = rng.gen_range(0..WIDTH); let width = NonZeroU16::new(rng.gen_range(1..=WIDTH - left)).expect("never zero"); - let data: Vec = std::iter::repeat([rng.r#gen(), rng.r#gen(), rng.r#gen(), 255]) - .take(usize::from(width.get()) * usize::from(height.get())) - .flatten() - .collect(); + let data: Vec = std::iter::repeat_n( + [rng.r#gen(), rng.r#gen(), rng.r#gen(), 255], + usize::from(width.get()) * usize::from(height.get()), + ) + .flatten() + .collect(); trace!(left, top, width, height, "BitmapUpdate"); diff --git a/devolutions-gateway/src/api/net.rs b/devolutions-gateway/src/api/net.rs index 7171463e7..b2cf54213 100644 --- a/devolutions-gateway/src/api/net.rs +++ b/devolutions-gateway/src/api/net.rs @@ -370,22 +370,19 @@ impl TryFrom for NetworkScanResponse { protocol, port, }) => { - let protocol = match protocol { - None => None, - Some(protocol) => Some(match protocol { - scanner::ServiceType::Rdp => Protocol::Rdp, - scanner::ServiceType::Ard => Protocol::Ard, - scanner::ServiceType::Vnc => Protocol::Vnc, - scanner::ServiceType::Ssh => Protocol::Ssh, - scanner::ServiceType::Sftp => Protocol::Sftp, - scanner::ServiceType::Scp => Protocol::Scp, - scanner::ServiceType::Telnet => Protocol::Telnet, - scanner::ServiceType::Http => Protocol::Http, - scanner::ServiceType::Https => Protocol::Https, - scanner::ServiceType::Ldap => Protocol::Ldap, - scanner::ServiceType::Ldaps => Protocol::Ldaps, - }), - }; + let protocol = protocol.map(|protocol| match protocol { + scanner::ServiceType::Rdp => Protocol::Rdp, + scanner::ServiceType::Ard => Protocol::Ard, + scanner::ServiceType::Vnc => Protocol::Vnc, + scanner::ServiceType::Ssh => Protocol::Ssh, + scanner::ServiceType::Sftp => Protocol::Sftp, + scanner::ServiceType::Scp => Protocol::Scp, + scanner::ServiceType::Telnet => Protocol::Telnet, + scanner::ServiceType::Http => Protocol::Http, + scanner::ServiceType::Https => Protocol::Https, + scanner::ServiceType::Ldap => Protocol::Ldap, + scanner::ServiceType::Ldaps => Protocol::Ldaps, + }); let protocol = match protocol { Some(protocol) => TcpKnockProbe::NamedApplication(protocol), diff --git a/devolutions-gateway/src/credential.rs b/devolutions-gateway/src/credential.rs index feee21a4c..857f7117f 100644 --- a/devolutions-gateway/src/credential.rs +++ b/devolutions-gateway/src/credential.rs @@ -29,6 +29,12 @@ pub struct AppCredentialMapping { #[derive(Debug, Clone)] pub struct CredentialStoreHandle(Arc>); +impl Default for CredentialStoreHandle { + fn default() -> Self { + Self::new() + } +} + impl CredentialStoreHandle { pub fn new() -> Self { Self(Arc::new(Mutex::new(CredentialStore::new()))) diff --git a/devolutions-gateway/src/rd_clean_path.rs b/devolutions-gateway/src/rd_clean_path.rs index ff7099a9b..7b8b58fb0 100644 --- a/devolutions-gateway/src/rd_clean_path.rs +++ b/devolutions-gateway/src/rd_clean_path.rs @@ -77,10 +77,7 @@ async fn read_cleanpath_pdu(mut stream: impl AsyncRead + Unpin + Send) -> io::Re std::cmp::Ordering::Less => {} std::cmp::Ordering::Equal => break, std::cmp::Ordering::Greater => { - return Err(io::Error::new( - ErrorKind::Other, - "no leftover is expected when reading cleanpath PDU", - )); + return Err(io::Error::other("no leftover is expected when reading cleanpath PDU")); } } } @@ -234,30 +231,31 @@ async fn process_cleanpath( debug!(%selected_target, "Connected to destination server"); span.record("target", selected_target.to_string()); - // Preconnection Blob (PCB) is currently only used for Hyper-V VMs. + // Preconnection Blob (PCB) is currently used for Hyper-V VMs almost exclusively in practice. + // However, we still leave space for future extensions of usages of PCB. // - // Connection sequence with Hyper-V VMs (PCB enabled): - // ┌─────────────────────┐ ┌─────────────────────────────────────────────────────────────┐ - // │ handled by │ │ handled by IronRDP client │ - // │ Gateway │ │ │ - // └─────────────────────┘ └─────────────────────────────────────────────────────────────┘ - // │PCB → TLS handshake │ → │CredSSP → X224 connection request → X224 connection response │ - // └─────────────────────┘ └─────────────────────────────────────────────────────────────┘ + // Connection sequence with Hyper-V VMs (PCB included and X224 connection request is not present): + // ┌───────────────────────┐ ┌───────────────────────────────────────────────────────────────┐ + // │ handled by │ │ handled by IronRDP client │ + // │ Gateway │ │ │ + // └───────────────────────┘ └───────────────────────────────────────────────────────────────┘ + // │ PCB → TLS handshake │ → │ CredSSP → X224 connection request → X224 connection response │ + // └───────────────────────┘ └───────────────────────────────────────────────────────────────┘ // - // Connection sequence without Hyper-V VMs (PCB disabled): - // ┌─────────────────────────────────────────────────────────────┐ ┌──────────────────────┐ - // │ handled by Gateway │ │ handled by IronRDP │ - // │ │ │ client │ - // └─────────────────────────────────────────────────────────────┘ └──────────────────────┘ - // │X224 connection request → X224 connection response → TLS hs │ → │CredSSP → ... │ - // └─────────────────────────────────────────────────────────────┘ └──────────────────────┘ + // Connection sequence without Hyper-V VMs (PCB optional): + // ┌───────────────────────────────────────────────────────────────┐ ┌───────────────────────┐ + // │ handled by Gateway │ │ handled by IronRDP │ + // │ │ │ client │ + // └───────────────────────────────────────────────────────────────┘ └───────────────────────┘ + // │ PCB → X224 connection request → X224 connection response → TLS| │ → CredSSP → ... │ + // └───────────────────────────────────────────────────────────────┘ └───────────────────────┘ // // Summary: - // - With PCB: Gateway handles (1) sending PCB, (2) TLS handshake, then leaves CredSSP - // and X224 connection request/response to IronRDP client - // - Without PCB: Gateway handles (1) X224 connection request, (2) X224 connection response, - // then leaves TLS handshake and CredSSP to IronRDP client - // Send preconnection blob and/or X224 connection request + // - With PCB but not X224 connection request: Gateway handles (1) sending PCB/VmConnectID, (2) TLS handshake, then leaves CredSSP + // and X224 connection request/response to IronRDP client. + // - With PCB and X224 connection request: Gateway handles (1) sending PCB/VmConnectID, (2) X224 connection request, (3) X224 connection response, (4) TLS handshake, + // then leaves CredSSP to IronRDP client. + // - Without PCB: In this case, X224 MUST be present! Gateway handles (1) X224 connection request, (2) X224 connection response, (3) TLS handshake, then leaves CredSSP to IronRDP client. match (&cleanpath_pdu.preconnection_blob, &cleanpath_pdu.x224_connection_pdu) { (None, None) => { return Err(CleanPathError::BadRequest(anyhow::anyhow!( @@ -310,13 +308,13 @@ async fn process_cleanpath( target_server: selected_target.to_owned(), })?; - return Ok(CleanPathResult { + Ok(CleanPathResult { destination: selected_target.to_owned(), claims, server_addr, server_stream, x224_rsp, - }); + }) } #[allow(clippy::too_many_arguments)] @@ -380,12 +378,8 @@ pub async fn handle( trace!("Sending RDCleanPath response"); - let rdcleanpath_rsp = RDCleanPathPdu::new_response( - server_addr.to_string(), - x224_rsp, - x509_chain, - ) - .map_err(|e| anyhow::anyhow!("couldn’t build RDCleanPath response: {e}"))?; + let rdcleanpath_rsp = RDCleanPathPdu::new_response(server_addr.to_string(), x224_rsp, x509_chain) + .context("couldn’t build RDCleanPath response")?; send_clean_path_response(&mut client_stream, &rdcleanpath_rsp).await?; @@ -504,7 +498,7 @@ enum WsaError { WSAESTALE = 10070, WSAEREMOTE = 10071, WSASYSNOTREADY = 10091, - WSAVERNOTSUPPORTED = 10092, + WSAVERNOT_SUPPORTED = 10092, WSANOTINITIALISED = 10093, WSAEDISCON = 10101, WSAENOMORE = 10102, diff --git a/devolutions-gateway/src/tls.rs b/devolutions-gateway/src/tls.rs index 293206fe2..39d327c6b 100644 --- a/devolutions-gateway/src/tls.rs +++ b/devolutions-gateway/src/tls.rs @@ -76,7 +76,7 @@ pub fn build_server_config(cert_source: CertificateSource) -> anyhow::Result { let first_certificate = certificates.first().context("empty certificate list")?; - if let Ok(report) = check_certificate_now(&first_certificate) { + if let Ok(report) = check_certificate_now(first_certificate) { if report.issues.intersects( CertIssues::MISSING_SERVER_AUTH_EXTENDED_KEY_USAGE | CertIssues::MISSING_SUBJECT_ALT_NAME, ) { diff --git a/jetsocat/src/main.rs b/jetsocat/src/main.rs index 9b2757c25..b033e4a18 100644 --- a/jetsocat/src/main.rs +++ b/jetsocat/src/main.rs @@ -463,7 +463,7 @@ impl CommonArgs { let pid = u32::try_from(process_id).context("invalid value for process ID")?; Some(sysinfo::Pid::from_u32(pid)) } else if c.bool_flag("watch-parent") { - use sysinfo::{ProcessRefreshKind, RefreshKind, System}; + use sysinfo::System; // Find current process' parent process ID let current_pid = @@ -850,7 +850,7 @@ fn clean_old_log_files(logging: &Logging) -> anyhow::Result<()> { match entry .metadata() .and_then(|metadata| metadata.modified()) - .and_then(|time| time.elapsed().map_err(|e| io::Error::new(io::ErrorKind::Other, e))) + .and_then(|time| time.elapsed().map_err(io::Error::other)) { Ok(modified) if modified > MAX_AGE => { info!("Delete log file"); From 9e15bba472c10ffb91fd33bd5c10d937377859e3 Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 4 Jul 2025 14:26:14 -0400 Subject: [PATCH 05/10] clean up --- devolutions-agent/src/config.rs | 7 ++++++- devolutions-agent/src/remote_desktop/graphics.rs | 10 ++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/devolutions-agent/src/config.rs b/devolutions-agent/src/config.rs index f04f51d50..1e6dc64a7 100644 --- a/devolutions-agent/src/config.rs +++ b/devolutions-agent/src/config.rs @@ -244,12 +244,17 @@ pub mod dto { #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] - #[derive(Default)] pub struct PedmConf { /// Enable PEDM module (disabled by default) pub enabled: bool, } + impl Default for PedmConf { + fn default() -> Self { + Self { enabled: false } + } + } + #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct SessionConf { diff --git a/devolutions-agent/src/remote_desktop/graphics.rs b/devolutions-agent/src/remote_desktop/graphics.rs index 187ad4ce4..0417dab0a 100644 --- a/devolutions-agent/src/remote_desktop/graphics.rs +++ b/devolutions-agent/src/remote_desktop/graphics.rs @@ -46,12 +46,10 @@ impl RdpServerDisplayUpdates for DisplayUpdates { let left: u16 = rng.gen_range(0..WIDTH); let width = NonZeroU16::new(rng.gen_range(1..=WIDTH - left)).expect("never zero"); - let data: Vec = std::iter::repeat_n( - [rng.r#gen(), rng.r#gen(), rng.r#gen(), 255], - usize::from(width.get()) * usize::from(height.get()), - ) - .flatten() - .collect(); + let data: Vec = std::iter::repeat([rng.r#gen(), rng.r#gen(), rng.r#gen(), 255]) + .take(usize::from(width.get()) * usize::from(height.get())) + .flatten() + .collect(); trace!(left, top, width, height, "BitmapUpdate"); From b87a79198a0f5c2285476bf9c148ee203d5766dd Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 4 Jul 2025 14:27:00 -0400 Subject: [PATCH 06/10] clean up --- devolutions-gateway/src/api/net.rs | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/devolutions-gateway/src/api/net.rs b/devolutions-gateway/src/api/net.rs index b2cf54213..7171463e7 100644 --- a/devolutions-gateway/src/api/net.rs +++ b/devolutions-gateway/src/api/net.rs @@ -370,19 +370,22 @@ impl TryFrom for NetworkScanResponse { protocol, port, }) => { - let protocol = protocol.map(|protocol| match protocol { - scanner::ServiceType::Rdp => Protocol::Rdp, - scanner::ServiceType::Ard => Protocol::Ard, - scanner::ServiceType::Vnc => Protocol::Vnc, - scanner::ServiceType::Ssh => Protocol::Ssh, - scanner::ServiceType::Sftp => Protocol::Sftp, - scanner::ServiceType::Scp => Protocol::Scp, - scanner::ServiceType::Telnet => Protocol::Telnet, - scanner::ServiceType::Http => Protocol::Http, - scanner::ServiceType::Https => Protocol::Https, - scanner::ServiceType::Ldap => Protocol::Ldap, - scanner::ServiceType::Ldaps => Protocol::Ldaps, - }); + let protocol = match protocol { + None => None, + Some(protocol) => Some(match protocol { + scanner::ServiceType::Rdp => Protocol::Rdp, + scanner::ServiceType::Ard => Protocol::Ard, + scanner::ServiceType::Vnc => Protocol::Vnc, + scanner::ServiceType::Ssh => Protocol::Ssh, + scanner::ServiceType::Sftp => Protocol::Sftp, + scanner::ServiceType::Scp => Protocol::Scp, + scanner::ServiceType::Telnet => Protocol::Telnet, + scanner::ServiceType::Http => Protocol::Http, + scanner::ServiceType::Https => Protocol::Https, + scanner::ServiceType::Ldap => Protocol::Ldap, + scanner::ServiceType::Ldaps => Protocol::Ldaps, + }), + }; let protocol = match protocol { Some(protocol) => TcpKnockProbe::NamedApplication(protocol), From 7e4eb76823ffc293220982369203690c4f235d45 Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 4 Jul 2025 14:27:29 -0400 Subject: [PATCH 07/10] clean up --- devolutions-gateway/src/credential.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/devolutions-gateway/src/credential.rs b/devolutions-gateway/src/credential.rs index 857f7117f..feee21a4c 100644 --- a/devolutions-gateway/src/credential.rs +++ b/devolutions-gateway/src/credential.rs @@ -29,12 +29,6 @@ pub struct AppCredentialMapping { #[derive(Debug, Clone)] pub struct CredentialStoreHandle(Arc>); -impl Default for CredentialStoreHandle { - fn default() -> Self { - Self::new() - } -} - impl CredentialStoreHandle { pub fn new() -> Self { Self(Arc::new(Mutex::new(CredentialStore::new()))) From 6e326b35d17e1118112bc1c445878b9c9cd282c3 Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 4 Jul 2025 14:28:42 -0400 Subject: [PATCH 08/10] clean up --- devolutions-gateway/src/rdp_pcb.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/devolutions-gateway/src/rdp_pcb.rs b/devolutions-gateway/src/rdp_pcb.rs index f0f462011..22c4293b4 100644 --- a/devolutions-gateway/src/rdp_pcb.rs +++ b/devolutions-gateway/src/rdp_pcb.rs @@ -54,10 +54,7 @@ fn decode_pcb(buf: &[u8]) -> Result, io::Erro Ok(pcb) => { let pdu_size = ironrdp_core::size(&pcb); let read_len = cursor.pos(); - info!( - pdu_size, - read_len, "read preconnection blob (size: {}, read: {})", pdu_size, read_len - ); + debug!(pdu_size, read_len, "read preconnection blob",); // NOTE: sanity check (reporting the wrong number will corrupt the communication) if read_len != pdu_size { From 946033794389c94b3522592d8ccf3951ffafca1f Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 4 Jul 2025 14:29:39 -0400 Subject: [PATCH 09/10] clean up --- jetsocat/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jetsocat/src/main.rs b/jetsocat/src/main.rs index b033e4a18..9b2757c25 100644 --- a/jetsocat/src/main.rs +++ b/jetsocat/src/main.rs @@ -463,7 +463,7 @@ impl CommonArgs { let pid = u32::try_from(process_id).context("invalid value for process ID")?; Some(sysinfo::Pid::from_u32(pid)) } else if c.bool_flag("watch-parent") { - use sysinfo::System; + use sysinfo::{ProcessRefreshKind, RefreshKind, System}; // Find current process' parent process ID let current_pid = @@ -850,7 +850,7 @@ fn clean_old_log_files(logging: &Logging) -> anyhow::Result<()> { match entry .metadata() .and_then(|metadata| metadata.modified()) - .and_then(|time| time.elapsed().map_err(io::Error::other)) + .and_then(|time| time.elapsed().map_err(|e| io::Error::new(io::ErrorKind::Other, e))) { Ok(modified) if modified > MAX_AGE => { info!("Delete log file"); From 39f0ff1d00b7c10fc947d89fedf9725e1257ce8a Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 4 Jul 2025 14:37:53 -0400 Subject: [PATCH 10/10] clean up --- devolutions-gateway/src/rd_clean_path.rs | 38 +++++++++++------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/devolutions-gateway/src/rd_clean_path.rs b/devolutions-gateway/src/rd_clean_path.rs index 7b8b58fb0..66dcbbbb0 100644 --- a/devolutions-gateway/src/rd_clean_path.rs +++ b/devolutions-gateway/src/rd_clean_path.rs @@ -256,31 +256,29 @@ async fn process_cleanpath( // - With PCB and X224 connection request: Gateway handles (1) sending PCB/VmConnectID, (2) X224 connection request, (3) X224 connection response, (4) TLS handshake, // then leaves CredSSP to IronRDP client. // - Without PCB: In this case, X224 MUST be present! Gateway handles (1) X224 connection request, (2) X224 connection response, (3) TLS handshake, then leaves CredSSP to IronRDP client. - match (&cleanpath_pdu.preconnection_blob, &cleanpath_pdu.x224_connection_pdu) { + let pcb_bytes = match (&cleanpath_pdu.preconnection_blob, &cleanpath_pdu.x224_connection_pdu) { (None, None) => { return Err(CleanPathError::BadRequest(anyhow::anyhow!( "RDCleanPath PDU is missing both preconnection blob and X224 connection PDU" ))); } - (Some(general_pcb), Some(_)) => { - server_stream.write_all(general_pcb.as_bytes()).await?; - } - (None, Some(_)) => {} // no preconnection blob to send - // This is considered to be the case where the preconnection blob is used for Hyper-V VMs connection - (Some(pcb), None) => { - debug!("Sending preconnection blob to server"); - let pcb = ironrdp_pdu::pcb::PreconnectionBlob { - version: ironrdp_pdu::pcb::PcbVersion::V2, - id: 0, - v2_payload: Some(pcb.clone()), - }; - - let encoded = ironrdp_core::encode_vec(&pcb) - .context("failed to encode preconnection blob") - .map_err(CleanPathError::BadRequest)?; - - server_stream.write_all(&encoded).await?; - } + (None, Some(_)) => None, // no preconnection blob to send + (Some(general_pcb), Some(_)) => Some(general_pcb), + (Some(vmconnect), None) => Some(vmconnect), + }; + + if let Some(pcb_bytes) = pcb_bytes { + let pcb = ironrdp_pdu::pcb::PreconnectionBlob { + version: ironrdp_pdu::pcb::PcbVersion::V2, + id: 0, + v2_payload: Some(pcb_bytes.to_owned()), + }; + + let encoded = ironrdp_core::encode_vec(&pcb) + .context("failed to encode preconnection blob") + .map_err(CleanPathError::BadRequest)?; + + server_stream.write_all(&encoded).await?; } // Send X224 connection request if present