diff --git a/crates/ironrdp-async/src/connector.rs b/crates/ironrdp-async/src/connector.rs index c7543c13..dcb31862 100644 --- a/crates/ironrdp-async/src/connector.rs +++ b/crates/ironrdp-async/src/connector.rs @@ -1,16 +1,14 @@ +use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, KerberosConfig}; +use ironrdp_connector::sspi::credssp::ClientState; +use ironrdp_connector::sspi::generator::GeneratorState; use ironrdp_connector::{ - credssp_sequence::{CredsspProcessGenerator, CredsspSequence}, - custom_err, - sspi::{credssp::ClientState, generator::GeneratorState}, - ClientConnector, ClientConnectorState, ConnectionResult, ConnectorResult, KerberosConfig, Sequence as _, - ServerName, State as _, Written, + custom_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, + Sequence as _, ServerName, State as _, }; use ironrdp_pdu::write_buf::WriteBuf; -use crate::{ - framed::{Framed, FramedRead, FramedWrite}, - AsyncNetworkClient, -}; +use crate::framed::{Framed, FramedRead, FramedWrite}; +use crate::AsyncNetworkClient; #[non_exhaustive] pub struct ShouldUpgrade; @@ -50,10 +48,10 @@ pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector) -> Up pub async fn connect_finalize( _: Upgraded, framed: &mut Framed, + mut connector: ClientConnector, server_name: ServerName, server_public_key: Vec, network_client: Option<&mut dyn AsyncNetworkClient>, - mut connector: ClientConnector, kerberos_config: Option, ) -> ConnectorResult where @@ -92,6 +90,7 @@ async fn resolve_generator( network_client: &mut dyn AsyncNetworkClient, ) -> ConnectorResult { let mut state = generator.start(); + loop { match state { GeneratorState::Suspended(request) => { @@ -99,13 +98,14 @@ async fn resolve_generator( state = generator.resume(Ok(response)); } GeneratorState::Completed(client_state) => { - break Ok(client_state.map_err(|e| custom_err!("cannot resolve generator state", e))?) + break client_state + .map_err(|e| ConnectorError::new("CredSSP", ironrdp_connector::ConnectorErrorKind::Credssp(e))) } } } } -#[instrument(level = "trace", skip(network_client, framed, buf, server_name, server_public_key))] +#[instrument(level = "trace", skip_all)] async fn perform_credssp_step( framed: &mut Framed, connector: &mut ClientConnector, @@ -119,10 +119,13 @@ where S: FramedRead + FramedWrite, { assert!(connector.should_perform_credssp()); + let mut credssp_sequence = CredsspSequence::new(connector, server_name, server_public_key, kerberos_config)?; + while !credssp_sequence.is_done() { buf.clear(); - let input = if let Some(next_pdu_hint) = credssp_sequence.next_pdu_hint() { + + if let Some(next_pdu_hint) = credssp_sequence.next_pdu_hint() { debug!( connector.state = connector.state.name(), hint = ?next_pdu_hint, @@ -135,25 +138,23 @@ where .map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?; trace!(length = pdu.len(), "PDU received"); - Some(pdu.to_vec()) - } else { - None - }; - if credssp_sequence.wants_request_from_server() { - credssp_sequence.read_request_from_server(&input.unwrap_or_else(|| [].to_vec()))?; + credssp_sequence.read_request_from_server(&pdu)?; } + let client_state = { let mut generator = credssp_sequence.process(); + if let Some(network_client_ref) = network_client.as_deref_mut() { trace!("resolving network"); resolve_generator(&mut generator, network_client_ref).await? } else { generator .resolve_to_result() - .map_err(|e| custom_err!(" cannot resolve generator without a network client", e))? + .map_err(|e| custom_err!("resolve without network client", e))? } }; // drop generator + let written = credssp_sequence.handle_process_result(client_state, buf)?; if let Some(response_len) = written.size() { @@ -165,7 +166,9 @@ where .map_err(|e| ironrdp_connector::custom_err!("write all", e))?; } } + connector.mark_credssp_as_done(); + Ok(()) } @@ -179,7 +182,7 @@ where { buf.clear(); - let written: Written = if let Some(next_pdu_hint) = connector.next_pdu_hint() { + let written = if let Some(next_pdu_hint) = connector.next_pdu_hint() { debug!( connector.state = connector.state.name(), hint = ?next_pdu_hint, diff --git a/crates/ironrdp-blocking/src/connector.rs b/crates/ironrdp-blocking/src/connector.rs index a1907969..2fddf56d 100644 --- a/crates/ironrdp-blocking/src/connector.rs +++ b/crates/ironrdp-blocking/src/connector.rs @@ -1,10 +1,11 @@ use std::io::{Read, Write}; +use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, KerberosConfig}; +use ironrdp_connector::sspi::credssp::ClientState; +use ironrdp_connector::sspi::generator::GeneratorState; +use ironrdp_connector::sspi::network_client::NetworkClient; use ironrdp_connector::{ - credssp_sequence::{CredsspProcessGenerator, CredsspSequence}, - custom_err, - sspi::{credssp::ClientState, generator::GeneratorState, network_client::NetworkClient}, - ClientConnector, ClientConnectorState, ConnectionResult, ConnectorResult, KerberosConfig, Sequence as _, + ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, Sequence as _, ServerName, State as _, }; use ironrdp_pdu::write_buf::WriteBuf; @@ -49,10 +50,10 @@ pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector) -> Up pub fn connect_finalize( _: Upgraded, framed: &mut Framed, + mut connector: ClientConnector, server_name: ServerName, server_public_key: Vec, network_client: &mut impl NetworkClient, - mut connector: ClientConnector, kerberos_config: Option, ) -> ConnectorResult where @@ -89,28 +90,27 @@ where Ok(result) } -#[instrument(level = "info", skip(generator, network_client))] fn resolve_generator( generator: &mut CredsspProcessGenerator<'_>, network_client: &mut impl NetworkClient, ) -> ConnectorResult { let mut state = generator.start(); - let res = loop { + + loop { match state { GeneratorState::Suspended(request) => { let response = network_client.send(&request).unwrap(); state = generator.resume(Ok(response)); } GeneratorState::Completed(client_state) => { - break client_state.map_err(|e| custom_err!("failed to resolve generator", e))? + break client_state + .map_err(|e| ConnectorError::new("CredSSP", ironrdp_connector::ConnectorErrorKind::Credssp(e))) } } - }; - debug!("client state = {:?}", &res); - Ok(res) + } } -#[instrument(level = "trace", skip(network_client, framed, buf, server_name, server_public_key))] +#[instrument(level = "trace", skip_all)] fn perform_credssp_step( framed: &mut Framed, connector: &mut ClientConnector, @@ -124,10 +124,13 @@ where S: Read + Write, { assert!(connector.should_perform_credssp()); + let mut credssp_sequence = CredsspSequence::new(connector, server_name, server_public_key, kerberos_config)?; + while !credssp_sequence.is_done() { buf.clear(); - let input = if let Some(next_pdu_hint) = credssp_sequence.next_pdu_hint() { + + if let Some(next_pdu_hint) = credssp_sequence.next_pdu_hint() { debug!( connector.state = connector.state.name(), hint = ?next_pdu_hint, @@ -139,18 +142,15 @@ where .map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?; trace!(length = pdu.len(), "PDU received"); - Some(pdu.to_vec()) - } else { - None - }; - if credssp_sequence.wants_request_from_server() { - credssp_sequence.read_request_from_server(&input.unwrap_or_else(|| [].to_vec()))?; + credssp_sequence.read_request_from_server(&pdu)?; } + let client_state = { let mut generator = credssp_sequence.process(); resolve_generator(&mut generator, network_client)? }; // drop generator + let written = credssp_sequence.handle_process_result(client_state, buf)?; if let Some(response_len) = written.size() { @@ -161,7 +161,9 @@ where .map_err(|e| ironrdp_connector::custom_err!("write all", e))?; } } + connector.mark_credssp_as_done(); + Ok(()) } @@ -169,7 +171,7 @@ pub fn single_connect_step( framed: &mut Framed, connector: &mut ClientConnector, buf: &mut WriteBuf, -) -> ConnectorResult +) -> ConnectorResult<()> where S: Read + Write, { @@ -201,5 +203,5 @@ where .map_err(|e| ironrdp_connector::custom_err!("write all", e))?; } - Ok(written) + Ok(()) } diff --git a/crates/ironrdp-client/src/network_client.rs b/crates/ironrdp-client/src/network_client.rs index dd191ed5..4c15c21b 100644 --- a/crates/ironrdp-client/src/network_client.rs +++ b/crates/ironrdp-client/src/network_client.rs @@ -1,17 +1,19 @@ -use sspi::{Error, ErrorKind}; +use std::future::Future; use std::net::{IpAddr, Ipv4Addr}; -use std::{future::Future, pin::Pin}; +use std::pin::Pin; + +use ironrdp::connector::{custom_err, ConnectorResult}; +use ironrdp_tokio::AsyncNetworkClient; +use reqwest::Client; +use sspi::{Error, ErrorKind}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpStream, UdpSocket}; - -use ironrdp::connector::{custom_err, general_err, ConnectorResult}; -use reqwest::Client; use url::Url; -use ironrdp_tokio::AsyncNetworkClient; pub(crate) struct ReqwestNetworkClient { client: Option, } + impl AsyncNetworkClient for ReqwestNetworkClient { fn send<'a>( &'a mut self, @@ -38,22 +40,23 @@ impl ReqwestNetworkClient { impl ReqwestNetworkClient { async fn send_tcp(&self, url: &Url, data: &[u8]) -> ConnectorResult> { let addr = format!("{}:{}", url.host_str().unwrap_or_default(), url.port().unwrap_or(88)); + let mut stream = TcpStream::connect(addr) .await .map_err(|e| Error::new(ErrorKind::NoAuthenticatingAuthority, format!("{:?}", e))) - .map_err(|e| custom_err!("sending KDC request over TCP ", e))?; + .map_err(|e| custom_err!("failed to send KDC request over TCP", e))?; stream .write(data) .await .map_err(|e| Error::new(ErrorKind::NoAuthenticatingAuthority, format!("{:?}", e))) - .map_err(|e| custom_err!("Sending KDC request over TCP ", e))?; + .map_err(|e| custom_err!("failed to send KDC request over TCP", e))?; let len = stream .read_u32() .await .map_err(|e| Error::new(ErrorKind::NoAuthenticatingAuthority, format!("{:?}", e))) - .map_err(|e| custom_err!("Sending KDC request over TCP ", e))?; + .map_err(|e| custom_err!("failed to send KDC request over TCP", e))?; let mut buf = vec![0; len as usize + 4]; buf[0..4].copy_from_slice(&(len.to_be_bytes())); @@ -62,7 +65,7 @@ impl ReqwestNetworkClient { .read_exact(&mut buf[4..]) .await .map_err(|e| Error::new(ErrorKind::NoAuthenticatingAuthority, format!("{:?}", e))) - .map_err(|e| custom_err!("Sending KDC request over TCP ", e))?; + .map_err(|e| custom_err!("failed to send KDC request over TCP", e))?; Ok(buf) } @@ -70,13 +73,14 @@ impl ReqwestNetworkClient { async fn send_udp(&self, url: &Url, data: &[u8]) -> ConnectorResult> { let udp_socket = UdpSocket::bind((IpAddr::V4(Ipv4Addr::LOCALHOST), 0)) .await - .map_err(|e| custom_err!("Cannot bind udp socket", e))?; + .map_err(|e| custom_err!("cannot bind UDP socket", e))?; let addr = format!("{}:{}", url.host_str().unwrap_or_default(), url.port().unwrap_or(88)); + udp_socket .send_to(data, addr) .await - .map_err(|e| custom_err!("Error sending udp request", e))?; + .map_err(|e| custom_err!("failed to send UDP request", e))?; // 48 000 bytes: default maximum token len in Windows let mut buf = vec![0; 0xbb80]; @@ -84,7 +88,7 @@ impl ReqwestNetworkClient { let n = udp_socket .recv(&mut buf) .await - .map_err(|e| custom_err!("Error receiving UDP request", e))?; + .map_err(|e| custom_err!("failed to receive UDP request", e))?; let mut reply_buf = Vec::with_capacity(n + 4); reply_buf.extend_from_slice(&(n as u32).to_be_bytes()); @@ -94,21 +98,17 @@ impl ReqwestNetworkClient { } async fn send_http(&mut self, url: &Url, data: &[u8]) -> ConnectorResult> { - if self.client.is_none() { - self.client = Some(Client::new()); // dont drop the cllient, keep-alive - } - let result_bytes = self - .client - .as_ref() - .ok_or_else(|| general_err!("Missing HTTP client, should never happen"))? + let client = self.client.get_or_insert_with(Client::new); + + let result_bytes = client .post(url.clone()) .body(data.to_vec()) .send() .await - .map_err(|e| custom_err!("Sending KDC request over proxy", e))? + .map_err(|e| custom_err!("failed to send KDC request over proxy", e))? .bytes() .await - .map_err(|e| custom_err!("Receving KDC response", e))? + .map_err(|e| custom_err!("failed to receive KDC response", e))? .to_vec(); Ok(result_bytes) diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index aa67af37..efe922e9 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -135,10 +135,10 @@ async fn connect( let connection_result = ironrdp_tokio::connect_finalize( upgraded, &mut upgraded_framed, + connector, (&config.destination).into(), server_public_key, Some(&mut network_client), - connector, None, ) .await?; diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 8ea0b2b7..2842b340 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -38,7 +38,7 @@ pub enum ClientConnectorState { EnhancedSecurityUpgrade { selected_protocol: nego::SecurityProtocol, }, - CredSsp { + Credssp { selected_protocol: nego::SecurityProtocol, }, BasicSettingsExchangeSendInitial { @@ -97,7 +97,7 @@ impl State for ClientConnectorState { Self::ConnectionInitiationSendRequest => "ConnectionInitiationSendRequest", Self::ConnectionInitiationWaitConfirm => "ConnectionInitiationWaitResponse", Self::EnhancedSecurityUpgrade { .. } => "EnhancedSecurityUpgrade", - Self::CredSsp { .. } => "CredSsp", + Self::Credssp { .. } => "Credssp", Self::BasicSettingsExchangeSendInitial { .. } => "BasicSettingsExchangeSendInitial", Self::BasicSettingsExchangeWaitResponse { .. } => "BasicSettingsExchangeWaitResponse", Self::ChannelConnection { .. } => "ChannelConnection", @@ -179,7 +179,7 @@ impl ClientConnector { } pub fn should_perform_credssp(&self) -> bool { - matches!(self.state, ClientConnectorState::CredSsp { .. }) + matches!(self.state, ClientConnectorState::Credssp { .. }) } pub fn mark_credssp_as_done(&mut self) { @@ -197,7 +197,7 @@ impl Sequence for ClientConnector { ClientConnectorState::ConnectionInitiationSendRequest => None, ClientConnectorState::ConnectionInitiationWaitConfirm => Some(&ironrdp_pdu::X224_HINT), ClientConnectorState::EnhancedSecurityUpgrade { .. } => None, - ClientConnectorState::CredSsp { .. } => None, + ClientConnectorState::Credssp { .. } => None, ClientConnectorState::BasicSettingsExchangeSendInitial { .. } => None, ClientConnectorState::BasicSettingsExchangeWaitResponse { .. } => Some(&ironrdp_pdu::X224_HINT), ClientConnectorState::ChannelConnection { channel_connection, .. } => channel_connection.next_pdu_hint(), @@ -280,7 +280,7 @@ impl Sequence for ClientConnector { let next_state = if selected_protocol.contains(nego::SecurityProtocol::HYBRID) || selected_protocol.contains(nego::SecurityProtocol::HYBRID_EX) { - ClientConnectorState::CredSsp { selected_protocol } + ClientConnectorState::Credssp { selected_protocol } } else { debug!("Skipped CredSSP"); ClientConnectorState::BasicSettingsExchangeSendInitial { selected_protocol } @@ -290,7 +290,7 @@ impl Sequence for ClientConnector { } //== CredSSP ==// - ClientConnectorState::CredSsp { selected_protocol } => ( + ClientConnectorState::Credssp { selected_protocol } => ( Written::Nothing, ClientConnectorState::BasicSettingsExchangeSendInitial { selected_protocol }, ), diff --git a/crates/ironrdp-connector/src/credssp_sequence.rs b/crates/ironrdp-connector/src/credssp.rs similarity index 78% rename from crates/ironrdp-connector/src/credssp_sequence.rs rename to crates/ironrdp-connector/src/credssp.rs index 4e13a8d3..67b8547b 100644 --- a/crates/ironrdp-connector/src/credssp_sequence.rs +++ b/crates/ironrdp-connector/src/credssp.rs @@ -6,10 +6,37 @@ use sspi::negotiate::ProtocolConfig; use sspi::Username; use crate::{ - ClientConnector, ClientConnectorState, ConnectorError, ConnectorErrorKind, ConnectorResult, KerberosConfig, - ServerName, Written, + ClientConnector, ClientConnectorState, ConnectorError, ConnectorErrorKind, ConnectorResult, ServerName, Written, }; +#[derive(Debug, Clone, Default)] +pub struct KerberosConfig { + pub kdc_proxy_url: Option, + pub hostname: Option, +} + +impl KerberosConfig { + pub fn new(kdc_proxy_url: Option, hostname: Option) -> ConnectorResult { + let kdc_proxy_url = kdc_proxy_url + .map(|url| url::Url::parse(&url)) + .transpose() + .map_err(|e| custom_err!("invalid KDC URL", e))?; + Ok(Self { + kdc_proxy_url, + hostname, + }) + } +} + +impl From for sspi::KerberosConfig { + fn from(val: KerberosConfig) -> Self { + sspi::KerberosConfig { + kdc_url: val.kdc_proxy_url, + client_computer_name: val.hostname, + } + } +} + #[derive(Clone, Copy, Debug)] struct CredsspTsRequestHint; @@ -24,6 +51,7 @@ impl PduHint for CredsspTsRequestHint { } } } + #[derive(Clone, Copy, Debug)] struct CredsspEarlyUserAuthResultHint; @@ -63,6 +91,7 @@ impl CredsspSequence { } } + /// `server_name` must be the actual target server hostname (as opposed to the proxy) pub fn new( connector: &ClientConnector, server_name: ServerName, @@ -76,8 +105,11 @@ impl CredsspSequence { )); } + let username = Username::new(config.credentials.username(), config.domain.as_deref()) + .map_err(|e| custom_err!("invalid username", e))?; + let credentials = sspi::AuthIdentity { - username: Username::parse(config.credentials.username()).map_err(|e| custom_err!("parsing username", e))?, + username, password: config.credentials.secret().to_owned().into(), }; @@ -91,7 +123,7 @@ impl CredsspSequence { } else { credssp_config = Box::::default(); } - info!("using config : {:?}", &credssp_config); + debug!(?credssp_config); let client = credssp::CredSspClient::new( server_public_key, @@ -107,15 +139,13 @@ impl CredsspSequence { .map_err(|e| ConnectorError::new("CredSSP", ConnectorErrorKind::Credssp(e)))?; match connector.state { - ClientConnectorState::CredSsp { selected_protocol } => Ok(Self { + ClientConnectorState::Credssp { selected_protocol } => Ok(Self { client, next_request: Some(credssp::TsRequest::default()), state: CredsspState::CredsspInitial, selected_protocol, }), - _ => Err(general_err!( - "Cannot perform cred ssp opeartions when ClientConnector is not in CredSsp state" - )), + _ => Err(general_err!("invalid connector state for CredSSP sequence")), } } @@ -123,38 +153,35 @@ impl CredsspSequence { self.state == CredsspState::Finished } - pub fn wants_request_from_server(&self) -> bool { - self.next_request.is_none() - } - pub fn read_request_from_server(&mut self, input: &[u8]) -> ConnectorResult<()> { match self.state { - CredsspState::CredsspInitial | CredsspState::CredsspReplyNeeded => { - info!("read request from server: {:?}", input); - let message = credssp::TsRequest::from_buffer(input) - .map_err(|e| reason_err!("CredSSP", "TsRequest decode: {e}"))?; + CredsspState::CredsspReplyNeeded => { + let message = credssp::TsRequest::from_buffer(input).map_err(|e| custom_err!("TsRequest", e))?; debug!(?message, "Received"); self.next_request = Some(message); Ok(()) } CredsspState::CredsspEarlyUserAuthResult => { let early_user_auth_result = credssp::EarlyUserAuthResult::from_buffer(input) - .map_err(|e| custom_err!("credssp::EarlyUserAuthResult", e))?; + .map_err(|e| custom_err!("EarlyUserAuthResult", e))?; debug!(message = ?early_user_auth_result, "Received"); - let credssp::EarlyUserAuthResult::Success = early_user_auth_result else { - return Err(ConnectorError::new("CredSSP", ConnectorErrorKind::AccessDenied)); - }; - Ok(()) + match early_user_auth_result { + credssp::EarlyUserAuthResult::Success => Ok(()), + credssp::EarlyUserAuthResult::AccessDenied => { + Err(ConnectorError::new("CredSSP", ConnectorErrorKind::AccessDenied)) + } + } } - _ => Err(general_err!("CredSsp Sequence is Finished")), + _ => Err(general_err!( + "attempted to feed server request to CredSSP sequence in an unexpected state" + )), } } pub fn process(&mut self) -> CredsspProcessGenerator<'_> { - let request = self.next_request.take().expect("next request"); - info!("Ts request = {:?}", &request); + let request = self.next_request.take().expect("next request"); // FIXME: error handling self.client.process(request) } @@ -191,9 +218,11 @@ impl CredsspSequence { Ok((Written::from_size(written)?, next_state)) } CredsspState::CredsspEarlyUserAuthResult => Ok((Written::Nothing, CredsspState::Finished)), - CredsspState::Finished => Err(general_err!("CredSSP Sequence if finished")), + CredsspState::Finished => Err(general_err!("CredSSP sequence is already done")), }?; + self.state = next_state; + Ok(size) } } @@ -205,7 +234,7 @@ fn write_credssp_request(ts_request: credssp::TsRequest, output: &mut WriteBuf) ts_request .encode_ts_request(unfilled_buffer) - .map_err(|e| reason_err!("CredSSP", "TsRequest encode: {e}"))?; + .map_err(|e| custom_err!("TsRequest", e))?; output.advance(length); diff --git a/crates/ironrdp-connector/src/lib.rs b/crates/ironrdp-connector/src/lib.rs index e10336cf..72bb6418 100644 --- a/crates/ironrdp-connector/src/lib.rs +++ b/crates/ironrdp-connector/src/lib.rs @@ -12,7 +12,7 @@ pub mod legacy; mod channel_connection; mod connection; mod connection_finalization; -pub mod credssp_sequence; +pub mod credssp; mod license_exchange; mod server_name; @@ -100,34 +100,6 @@ pub struct Config { pub autologon: bool, } -#[derive(Debug, Clone, Default)] -pub struct KerberosConfig { - pub kdc_proxy_url: Option, - pub hostname: Option, -} - -impl KerberosConfig { - pub fn new(kdc_proxy_url: Option, hostname: Option) -> ConnectorResult { - let kdc_proxy_url = kdc_proxy_url - .map(|url| url::Url::parse(&url)) - .transpose() - .map_err(|e| custom_err!("invalid KDC URL", e))?; - Ok(Self { - kdc_proxy_url, - hostname, - }) - } -} - -impl From for sspi::KerberosConfig { - fn from(val: KerberosConfig) -> Self { - sspi::KerberosConfig { - kdc_url: val.kdc_proxy_url, - client_computer_name: val.hostname, - } - } -} - ironrdp_pdu::assert_impl!(Config: Send, Sync); pub trait State: Send + core::fmt::Debug + 'static { diff --git a/crates/ironrdp-web/Cargo.toml b/crates/ironrdp-web/Cargo.toml index 6c02cd52..48b4fcd9 100644 --- a/crates/ironrdp-web/Cargo.toml +++ b/crates/ironrdp-web/Cargo.toml @@ -32,7 +32,7 @@ ironrdp-rdcleanpath.workspace = true # WASM wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" -web-sys = { version = "0.3", features = ["HtmlCanvasElement", "ReadableStreamDefaultReader"] } +web-sys = { version = "0.3", features = ["HtmlCanvasElement"] } js-sys = "0.3" gloo-net = { version = "0.4", default-features = false, features = ["websocket", "http"] } gloo-timers = { version = "0.3", default-features = false, features = ["futures"] } @@ -66,4 +66,4 @@ smallvec = "1.11" x509-cert = { version = "0.2", default-features = false, features = ["std"] } tap = "1" semver = "1" -url = "2.4.1" +url = "2.4" diff --git a/crates/ironrdp-web/src/network_client.rs b/crates/ironrdp-web/src/network_client.rs index e80a11b9..da49f076 100644 --- a/crates/ironrdp-web/src/network_client.rs +++ b/crates/ironrdp-web/src/network_client.rs @@ -3,76 +3,39 @@ use std::pin::Pin; use futures_util::Future; use ironrdp::connector::sspi::generator::NetworkRequest; use ironrdp::connector::sspi::network_client::NetworkProtocol; -use ironrdp::connector::{general_err, reason_err, ConnectorResult}; +use ironrdp::connector::{custom_err, reason_err, ConnectorResult}; use ironrdp_futures::AsyncNetworkClient; -use wasm_bindgen::JsCast; -use wasm_bindgen_futures::JsFuture; -use web_sys::ReadableStream; #[derive(Debug)] pub(crate) struct WasmNetworkClient; + impl AsyncNetworkClient for WasmNetworkClient { fn send<'a>( &'a mut self, network_request: &'a NetworkRequest, ) -> Pin>> + 'a>> { Box::pin(async move { - trace!("network requwest = {:?}", &network_request); + debug!(?network_request.protocol, ?network_request.url); + match &network_request.protocol { NetworkProtocol::Http | NetworkProtocol::Https => { let body = js_sys::Uint8Array::from(&network_request.data[..]); - let stream = gloo_net::http::Request::post(network_request.url.as_str()) + let response = gloo_net::http::Request::post(network_request.url.as_str()) .header("keep-alive", "true") .body(body) - .map_err(|e| reason_err!("Error send KDC request", "{}", e))? + .map_err(|e| custom_err!("failed to send KDC request", e))? .send() .await - .map_err(|e| reason_err!("Error send KDC request", "{}", e))? - .body() - .ok_or_else(|| general_err!("No body in response"))?; - let res = read_stream(stream).await?; + .map_err(|e| custom_err!("failed to send KDC request", e))? + .binary() + .await + .map_err(|e| custom_err!("failed to retrieve HTTP response", e))?; - Ok(res) + Ok(response) } - _ => Err(general_err!("KDC Url must always start with HTTP/HTTPS for Web")), + unsupported => Err(reason_err!("CredSSP", "unsupported protocol: {unsupported:?}")), } }) } } - -impl WasmNetworkClient { - pub(crate) fn new() -> Self { - Self - } -} -async fn read_stream(stream: ReadableStream) -> ConnectorResult> { - let mut bytes = Vec::new(); - let reader = web_sys::ReadableStreamDefaultReader::new(&stream).map_err(|_| general_err!("error create reader"))?; - - loop { - let result = JsFuture::from(reader.read()) - .await - .map_err(|_e| general_err!("error read stream"))?; - - // Cast the result into an object and check if the stream is done - let result_obj = result.dyn_into::().unwrap(); - let done = js_sys::Reflect::get(&result_obj, &"done".into()) - .map_err(|_| general_err!("error read stream"))? - .as_bool() - .ok_or_else(|| general_err!("error resolve reader promise property: done"))?; - - if done { - break; - } - - // Extract the value (the chunk) from the result - let value = js_sys::Reflect::get(&result_obj, &"value".into()).unwrap(); - - // Convert value to Uint8Array, then to Vec and append to bytes - let chunk = js_sys::Uint8Array::new(&value); - bytes.extend_from_slice(&chunk.to_vec()); - } - - Ok(bytes) -} diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 11b758aa..d51e72fb 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -10,7 +10,8 @@ use gloo_net::websocket; use gloo_net::websocket::futures::WebSocket; use ironrdp::cliprdr::backend::ClipboardMessage; use ironrdp::cliprdr::Cliprdr; -use ironrdp::connector::{self, ClientConnector, Credentials, KerberosConfig}; +use ironrdp::connector::credssp::KerberosConfig; +use ironrdp::connector::{self, ClientConnector, Credentials}; use ironrdp::graphics::image_processing::PixelFormat; use ironrdp::pdu::input::fast_path::FastPathInputEvent; use ironrdp::pdu::write_buf::WriteBuf; @@ -140,6 +141,12 @@ impl SessionBuilder { self.clone() } + /// Optional + pub fn kdc_proxy_url(&self, kdc_proxy_url: Option) -> SessionBuilder { + self.0.borrow_mut().kdc_proxy_url = kdc_proxy_url; + self.clone() + } + /// Optional pub fn desktop_size(&self, desktop_size: DesktopSize) -> SessionBuilder { self.0.borrow_mut().desktop_size = desktop_size; @@ -176,12 +183,6 @@ impl SessionBuilder { self.clone() } - /// Optional - pub fn kdc_proxy_url(&self, kdc_proxy_url: Option) -> SessionBuilder { - self.0.borrow_mut().kdc_proxy_url = kdc_proxy_url; - self.clone() - } - /// Optional pub fn remote_clipboard_changed_callback(&self, callback: js_sys::Function) -> SessionBuilder { self.0.borrow_mut().remote_clipboard_changed_callback = Some(callback); @@ -209,6 +210,7 @@ impl SessionBuilder { proxy_address, auth_token, pcb, + kdc_proxy_url, client_name, desktop_size, render_canvas, @@ -216,7 +218,6 @@ impl SessionBuilder { hide_pointer_callback_context, show_pointer_callback, show_pointer_callback_context, - kdc_proxy_url, remote_clipboard_changed_callback, remote_received_format_list_callback, force_clipboard_update_callback, @@ -224,6 +225,7 @@ impl SessionBuilder { { let inner = self.0.borrow(); + username = inner.username.clone().context("username missing")?; destination = inner.destination.clone().context("destination missing")?; server_domain = inner.server_domain.clone(); @@ -231,11 +233,10 @@ impl SessionBuilder { proxy_address = inner.proxy_address.clone().context("proxy_address missing")?; auth_token = inner.auth_token.clone().context("auth_token missing")?; pcb = inner.pcb.clone(); + kdc_proxy_url = inner.kdc_proxy_url.clone(); client_name = inner.client_name.clone(); desktop_size = inner.desktop_size.clone(); - kdc_proxy_url = inner.kdc_proxy_url.clone(); - render_canvas = inner.render_canvas.clone().context("render_canvas missing")?; hide_pointer_callback = inner @@ -310,8 +311,8 @@ impl SessionBuilder { auth_token, destination, pcb, - clipboard.as_ref().map(|clip| clip.backend()), kdc_proxy_url, + clipboard.as_ref().map(|clip| clip.backend()), ) .await?; @@ -651,8 +652,8 @@ async fn connect( proxy_auth_token: String, destination: String, pcb: Option, - clipboard_backend: Option, kdc_proxy_url: Option, + clipboard_backend: Option, ) -> Result<(connector::ConnectionResult, WebSocketCompat), IronRdpError> { let mut framed = ironrdp_futures::SingleThreadedFuturesFramed::new(ws); @@ -665,17 +666,13 @@ async fn connect( let (upgraded, server_public_key) = connect_rdcleanpath(&mut framed, &mut connector, destination.clone(), proxy_auth_token, pcb).await?; - info!("kdc url = {:?}", &kdc_proxy_url); - - let mut network_client = WasmNetworkClient::new(); - let connection_result = ironrdp_futures::connect_finalize( upgraded, &mut framed, + connector, (&destination).into(), server_public_key, - Some(&mut network_client), - connector, + Some(&mut WasmNetworkClient), url::Url::parse(kdc_proxy_url.unwrap_or_default().as_str()) // if kdc_proxy_url does not exit, give url parser a empty string, it will fail anyway and map to a None .ok() .map(|url| KerberosConfig { diff --git a/crates/ironrdp/examples/screenshot.rs b/crates/ironrdp/examples/screenshot.rs index ad087b86..b34d5ad7 100644 --- a/crates/ironrdp/examples/screenshot.rs +++ b/crates/ironrdp/examples/screenshot.rs @@ -258,10 +258,10 @@ fn connect( let connection_result = ironrdp_blocking::connect_finalize( upgraded, &mut upgraded_framed, + connector, server_name.into(), server_public_key, &mut network_client, - connector, None, ) .context("finalize connection")?; diff --git a/web-client/iron-remote-gui/README.md b/web-client/iron-remote-gui/README.md index 84c93d98..5f3933f6 100644 --- a/web-client/iron-remote-gui/README.md +++ b/web-client/iron-remote-gui/README.md @@ -54,16 +54,16 @@ You can add some parameters for default initialization on the component `; + username: string, + password: string, + destination: string, + proxyAddress: string, + serverDomain: string, + authToken: string, + desktopSize?: DesktopSize, + preConnectionBlob?: string, + kdc_proxy_url?: string, +): Observable; ``` > `username` and `password` are the credentials to use on the remote host.