1#![cfg_attr(doc, doc = include_str!("../README.md"))]
2#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
3
4mod macros;
5
6pub mod legacy;
7
8mod channel_connection;
9mod connection;
10pub mod connection_activation;
11mod connection_finalization;
12pub mod credssp;
13mod license_exchange;
14mod server_name;
15
16use core::any::Any;
17use core::fmt;
18use std::sync::Arc;
19
20use ironrdp_core::{encode_buf, encode_vec, Encode, WriteBuf};
21use ironrdp_pdu::nego::NegoRequestData;
22use ironrdp_pdu::rdp::capability_sets::{self, BitmapCodecs};
23use ironrdp_pdu::rdp::client_info::{PerformanceFlags, TimezoneInfo};
24use ironrdp_pdu::x224::X224;
25use ironrdp_pdu::{gcc, x224, PduHint};
26pub use sspi;
27
28pub use self::channel_connection::{ChannelConnectionSequence, ChannelConnectionState};
29pub use self::connection::{encode_send_data_request, ClientConnector, ClientConnectorState, ConnectionResult};
30pub use self::connection_finalization::{ConnectionFinalizationSequence, ConnectionFinalizationState};
31pub use self::license_exchange::{LicenseExchangeSequence, LicenseExchangeState};
32pub use self::server_name::ServerName;
33pub use crate::license_exchange::LicenseCache;
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub struct NegotiationFailure(ironrdp_pdu::nego::FailureCode);
38
39impl NegotiationFailure {
40 pub fn code(self) -> ironrdp_pdu::nego::FailureCode {
41 self.0
42 }
43}
44
45impl core::error::Error for NegotiationFailure {}
46
47impl From<ironrdp_pdu::nego::FailureCode> for NegotiationFailure {
48 fn from(code: ironrdp_pdu::nego::FailureCode) -> Self {
49 Self(code)
50 }
51}
52
53impl fmt::Display for NegotiationFailure {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 use ironrdp_pdu::nego::FailureCode;
56
57 match self.0 {
58 FailureCode::SSL_REQUIRED_BY_SERVER => {
59 write!(f, "server requires Enhanced RDP Security with TLS or CredSSP")
60 }
61 FailureCode::SSL_NOT_ALLOWED_BY_SERVER => {
62 write!(f, "server only supports Standard RDP Security")
63 }
64 FailureCode::SSL_CERT_NOT_ON_SERVER => {
65 write!(f, "server lacks valid authentication certificate")
66 }
67 FailureCode::INCONSISTENT_FLAGS => {
68 write!(f, "inconsistent security protocol flags")
69 }
70 FailureCode::HYBRID_REQUIRED_BY_SERVER => {
71 write!(f, "server requires Enhanced RDP Security with CredSSP")
72 }
73 FailureCode::SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER => {
74 write!(
75 f,
76 "server requires Enhanced RDP Security with TLS and client certificate"
77 )
78 }
79 _ => write!(f, "unknown negotiation failure (code: 0x{:08x})", u32::from(self.0)),
80 }
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
86pub struct DesktopSize {
87 pub width: u16,
88 pub height: u16,
89}
90
91#[derive(Debug, Clone)]
92#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
93pub struct BitmapConfig {
94 pub lossy_compression: bool,
95 pub color_depth: u32,
96 pub codecs: BitmapCodecs,
97}
98
99#[derive(Debug, Clone)]
100pub struct SmartCardIdentity {
101 pub certificate: Vec<u8>,
103 pub reader_name: String,
105 pub container_name: String,
107 pub csp_name: String,
109 pub private_key: Vec<u8>,
111}
112
113#[derive(Debug, Clone)]
114pub enum Credentials {
115 UsernamePassword {
116 username: String,
117 password: String,
118 },
119 SmartCard {
120 pin: String,
121 config: Option<SmartCardIdentity>,
122 },
123}
124
125impl Credentials {
126 fn username(&self) -> Option<&str> {
127 match self {
128 Self::UsernamePassword { username, .. } => Some(username),
129 Self::SmartCard { .. } => None, }
131 }
132
133 fn secret(&self) -> &str {
134 match self {
135 Self::UsernamePassword { password, .. } => password,
136 Self::SmartCard { pin, .. } => pin,
137 }
138 }
139}
140
141#[derive(Debug, Clone)]
142#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
143pub struct Config {
144 pub desktop_size: DesktopSize,
146 pub desktop_scale_factor: u32,
150 pub enable_tls: bool,
180 #[doc(alias("enable_nla", "nla"))]
195 pub enable_credssp: bool,
196 pub credentials: Credentials,
197 pub domain: Option<String>,
198 pub client_build: u32,
200 pub client_name: String,
204 pub keyboard_type: gcc::KeyboardType,
205 pub keyboard_subtype: u32,
206 pub keyboard_functional_keys_count: u32,
207 pub keyboard_layout: u32,
208 pub ime_file_name: String,
209 pub bitmap: Option<BitmapConfig>,
210 pub dig_product_id: String,
211 pub client_dir: String,
212 pub platform: capability_sets::MajorPlatformType,
213 pub hardware_id: Option<[u32; 4]>,
217 pub request_data: Option<NegoRequestData>,
224 pub autologon: bool,
226 pub enable_audio_playback: bool,
228 pub performance_flags: PerformanceFlags,
229
230 pub license_cache: Option<Arc<dyn LicenseCache>>,
231
232 pub timezone_info: TimezoneInfo,
234
235 pub enable_server_pointer: bool,
237 pub pointer_software_rendering: bool,
238}
239
240ironrdp_core::assert_impl!(Config: Send, Sync);
241
242pub trait State: Send + fmt::Debug + 'static {
243 fn name(&self) -> &'static str;
244 fn is_terminal(&self) -> bool;
245 fn as_any(&self) -> &dyn Any;
246}
247
248ironrdp_core::assert_obj_safe!(State);
249
250pub fn state_downcast<T: State>(state: &dyn State) -> Option<&T> {
251 state.as_any().downcast_ref()
252}
253
254pub fn state_is<T: State>(state: &dyn State) -> bool {
255 state.as_any().is::<T>()
256}
257
258impl State for () {
259 fn name(&self) -> &'static str {
260 "()"
261 }
262
263 fn is_terminal(&self) -> bool {
264 true
265 }
266
267 fn as_any(&self) -> &dyn Any {
268 self
269 }
270}
271
272#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
273pub enum Written {
274 Nothing,
275 Size(core::num::NonZeroUsize),
276}
277
278impl Written {
279 #[inline]
280 pub fn from_size(value: usize) -> ConnectorResult<Self> {
281 core::num::NonZeroUsize::new(value)
282 .map(Self::Size)
283 .ok_or_else(|| ConnectorError::general("invalid written length (can't be zero)"))
284 }
285
286 #[inline]
287 pub fn is_nothing(self) -> bool {
288 matches!(self, Self::Nothing)
289 }
290
291 #[inline]
292 pub fn size(self) -> Option<usize> {
293 if let Self::Size(size) = self {
294 Some(size.get())
295 } else {
296 None
297 }
298 }
299}
300
301pub trait Sequence: Send {
302 fn next_pdu_hint(&self) -> Option<&dyn PduHint>;
303
304 fn state(&self) -> &dyn State;
305
306 fn step(&mut self, input: &[u8], output: &mut WriteBuf) -> ConnectorResult<Written>;
307
308 fn step_no_input(&mut self, output: &mut WriteBuf) -> ConnectorResult<Written> {
309 self.step(&[], output)
310 }
311}
312
313ironrdp_core::assert_obj_safe!(Sequence);
314
315pub type ConnectorResult<T> = Result<T, ConnectorError>;
316
317#[non_exhaustive]
318#[derive(Debug)]
319pub enum ConnectorErrorKind {
320 Encode(ironrdp_core::EncodeError),
321 Decode(ironrdp_core::DecodeError),
322 Credssp(sspi::Error),
323 Reason(String),
324 AccessDenied,
325 General,
326 Custom,
327 Negotiation(NegotiationFailure),
328}
329
330impl fmt::Display for ConnectorErrorKind {
331 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332 match &self {
333 ConnectorErrorKind::Encode(_) => write!(f, "encode error"),
334 ConnectorErrorKind::Decode(_) => write!(f, "decode error"),
335 ConnectorErrorKind::Credssp(_) => write!(f, "CredSSP"),
336 ConnectorErrorKind::Reason(description) => write!(f, "reason: {description}"),
337 ConnectorErrorKind::AccessDenied => write!(f, "access denied"),
338 ConnectorErrorKind::General => write!(f, "general error"),
339 ConnectorErrorKind::Custom => write!(f, "custom error"),
340 ConnectorErrorKind::Negotiation(failure) => write!(f, "negotiation failure: {failure}"),
341 }
342 }
343}
344
345impl core::error::Error for ConnectorErrorKind {
346 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
347 match &self {
348 ConnectorErrorKind::Encode(e) => Some(e),
349 ConnectorErrorKind::Decode(e) => Some(e),
350 ConnectorErrorKind::Credssp(e) => Some(e),
351 ConnectorErrorKind::Reason(_) => None,
352 ConnectorErrorKind::AccessDenied => None,
353 ConnectorErrorKind::Custom => None,
354 ConnectorErrorKind::General => None,
355 ConnectorErrorKind::Negotiation(failure) => Some(failure),
356 }
357 }
358}
359
360pub type ConnectorError = ironrdp_error::Error<ConnectorErrorKind>;
361
362pub trait ConnectorErrorExt {
363 fn encode(error: ironrdp_core::EncodeError) -> Self;
364 fn decode(error: ironrdp_core::DecodeError) -> Self;
365 fn general(context: &'static str) -> Self;
366 fn reason(context: &'static str, reason: impl Into<String>) -> Self;
367 fn custom<E>(context: &'static str, e: E) -> Self
368 where
369 E: core::error::Error + Sync + Send + 'static;
370}
371
372impl ConnectorErrorExt for ConnectorError {
373 fn encode(error: ironrdp_core::EncodeError) -> Self {
374 Self::new("encode error", ConnectorErrorKind::Encode(error))
375 }
376
377 fn decode(error: ironrdp_core::DecodeError) -> Self {
378 Self::new("decode error", ConnectorErrorKind::Decode(error))
379 }
380
381 fn general(context: &'static str) -> Self {
382 Self::new(context, ConnectorErrorKind::General)
383 }
384
385 fn reason(context: &'static str, reason: impl Into<String>) -> Self {
386 Self::new(context, ConnectorErrorKind::Reason(reason.into()))
387 }
388
389 fn custom<E>(context: &'static str, e: E) -> Self
390 where
391 E: core::error::Error + Sync + Send + 'static,
392 {
393 Self::new(context, ConnectorErrorKind::Custom).with_source(e)
394 }
395}
396
397pub trait ConnectorResultExt {
398 #[must_use]
399 fn with_context(self, context: &'static str) -> Self;
400 #[must_use]
401 fn with_source<E>(self, source: E) -> Self
402 where
403 E: core::error::Error + Sync + Send + 'static;
404}
405
406impl<T> ConnectorResultExt for ConnectorResult<T> {
407 fn with_context(self, context: &'static str) -> Self {
408 self.map_err(|mut e| {
409 e.context = context;
410 e
412 })
413 }
414
415 fn with_source<E>(self, source: E) -> Self
416 where
417 E: core::error::Error + Sync + Send + 'static,
418 {
419 self.map_err(|e| e.with_source(source))
420 }
421}
422
423pub fn encode_x224_packet<T>(x224_msg: &T, buf: &mut WriteBuf) -> ConnectorResult<usize>
424where
425 T: Encode,
426{
427 let x224_msg_buf = encode_vec(x224_msg).map_err(ConnectorError::encode)?;
428
429 let pdu = x224::X224Data {
430 data: std::borrow::Cow::Owned(x224_msg_buf),
431 };
432
433 let written = encode_buf(&X224(pdu), buf).map_err(ConnectorError::encode)?;
434
435 Ok(written)
436}