#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://webdevolutions.blob.core.windows.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg"
)]
#[macro_use]
extern crate tracing;
#[macro_use]
mod macros;
pub mod legacy;
mod channel_connection;
mod connection;
pub mod connection_activation;
mod connection_finalization;
pub mod credssp;
mod license_exchange;
mod server_name;
use core::any::Any;
use core::fmt;
pub use channel_connection::{ChannelConnectionSequence, ChannelConnectionState};
pub use connection::{encode_send_data_request, ClientConnector, ClientConnectorState, ConnectionResult};
pub use connection_finalization::{ConnectionFinalizationSequence, ConnectionFinalizationState};
use ironrdp_core::{encode_buf, encode_vec, Encode, WriteBuf};
use ironrdp_pdu::rdp::capability_sets;
use ironrdp_pdu::rdp::client_info::PerformanceFlags;
use ironrdp_pdu::x224::X224;
use ironrdp_pdu::{gcc, x224, PduHint};
pub use license_exchange::{LicenseExchangeSequence, LicenseExchangeState};
pub use server_name::ServerName;
pub use sspi;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct DesktopSize {
pub width: u16,
pub height: u16,
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct BitmapConfig {
pub lossy_compression: bool,
pub color_depth: u32,
}
#[derive(Debug, Clone)]
pub struct SmartCardIdentity {
pub certificate: Vec<u8>,
pub reader_name: String,
pub container_name: String,
pub csp_name: String,
pub private_key: Vec<u8>,
}
#[derive(Debug, Clone)]
pub enum Credentials {
UsernamePassword {
username: String,
password: String,
},
SmartCard {
pin: String,
config: Option<Box<SmartCardIdentity>>,
},
}
impl Credentials {
fn username(&self) -> &str {
match self {
Self::UsernamePassword { username, .. } => username,
Self::SmartCard { .. } => "", }
}
fn secret(&self) -> &str {
match self {
Self::UsernamePassword { password, .. } => password,
Self::SmartCard { pin, .. } => pin,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Config {
pub desktop_size: DesktopSize,
pub desktop_scale_factor: u32,
pub enable_tls: bool,
#[doc(alias("enable_nla", "nla"))]
pub enable_credssp: bool,
pub credentials: Credentials,
pub domain: Option<String>,
pub client_build: u32,
pub client_name: String,
pub keyboard_type: gcc::KeyboardType,
pub keyboard_subtype: u32,
pub keyboard_functional_keys_count: u32,
pub keyboard_layout: u32,
pub ime_file_name: String,
pub bitmap: Option<BitmapConfig>,
pub dig_product_id: String,
pub client_dir: String,
pub platform: capability_sets::MajorPlatformType,
pub autologon: bool,
pub no_server_pointer: bool,
pub pointer_software_rendering: bool,
pub performance_flags: PerformanceFlags,
}
ironrdp_core::assert_impl!(Config: Send, Sync);
pub trait State: Send + fmt::Debug + 'static {
fn name(&self) -> &'static str;
fn is_terminal(&self) -> bool;
fn as_any(&self) -> &dyn Any;
}
ironrdp_core::assert_obj_safe!(State);
pub fn state_downcast<T: State>(state: &dyn State) -> Option<&T> {
state.as_any().downcast_ref()
}
pub fn state_is<T: State>(state: &dyn State) -> bool {
state.as_any().is::<T>()
}
impl State for () {
fn name(&self) -> &'static str {
"()"
}
fn is_terminal(&self) -> bool {
true
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Written {
Nothing,
Size(core::num::NonZeroUsize),
}
impl Written {
#[inline]
pub fn from_size(value: usize) -> ConnectorResult<Self> {
core::num::NonZeroUsize::new(value)
.map(Self::Size)
.ok_or_else(|| ConnectorError::general("invalid written length (can’t be zero)"))
}
#[inline]
pub fn is_nothing(self) -> bool {
matches!(self, Self::Nothing)
}
#[inline]
pub fn size(self) -> Option<usize> {
if let Self::Size(size) = self {
Some(size.get())
} else {
None
}
}
}
pub trait Sequence: Send {
fn next_pdu_hint(&self) -> Option<&dyn PduHint>;
fn state(&self) -> &dyn State;
fn step(&mut self, input: &[u8], output: &mut WriteBuf) -> ConnectorResult<Written>;
fn step_no_input(&mut self, output: &mut WriteBuf) -> ConnectorResult<Written> {
self.step(&[], output)
}
}
ironrdp_core::assert_obj_safe!(Sequence);
pub type ConnectorResult<T> = Result<T, ConnectorError>;
#[non_exhaustive]
#[derive(Debug)]
pub enum ConnectorErrorKind {
Encode(ironrdp_core::EncodeError),
Decode(ironrdp_core::DecodeError),
Credssp(sspi::Error),
Reason(String),
AccessDenied,
General,
Custom,
}
impl fmt::Display for ConnectorErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
ConnectorErrorKind::Encode(_) => write!(f, "encode error"),
ConnectorErrorKind::Decode(_) => write!(f, "decode error"),
ConnectorErrorKind::Credssp(_) => write!(f, "CredSSP"),
ConnectorErrorKind::Reason(description) => write!(f, "reason: {description}"),
ConnectorErrorKind::AccessDenied => write!(f, "access denied"),
ConnectorErrorKind::General => write!(f, "general error"),
ConnectorErrorKind::Custom => write!(f, "custom error"),
}
}
}
impl std::error::Error for ConnectorErrorKind {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self {
ConnectorErrorKind::Encode(e) => Some(e),
ConnectorErrorKind::Decode(e) => Some(e),
ConnectorErrorKind::Credssp(e) => Some(e),
ConnectorErrorKind::Reason(_) => None,
ConnectorErrorKind::AccessDenied => None,
ConnectorErrorKind::Custom => None,
ConnectorErrorKind::General => None,
}
}
}
pub type ConnectorError = ironrdp_error::Error<ConnectorErrorKind>;
pub trait ConnectorErrorExt {
fn encode(error: ironrdp_core::EncodeError) -> Self;
fn decode(error: ironrdp_core::DecodeError) -> Self;
fn general(context: &'static str) -> Self;
fn reason(context: &'static str, reason: impl Into<String>) -> Self;
fn custom<E>(context: &'static str, e: E) -> Self
where
E: std::error::Error + Sync + Send + 'static;
}
impl ConnectorErrorExt for ConnectorError {
fn encode(error: ironrdp_core::EncodeError) -> Self {
Self::new("encode error", ConnectorErrorKind::Encode(error))
}
fn decode(error: ironrdp_core::DecodeError) -> Self {
Self::new("decode error", ConnectorErrorKind::Decode(error))
}
fn general(context: &'static str) -> Self {
Self::new(context, ConnectorErrorKind::General)
}
fn reason(context: &'static str, reason: impl Into<String>) -> Self {
Self::new(context, ConnectorErrorKind::Reason(reason.into()))
}
fn custom<E>(context: &'static str, e: E) -> Self
where
E: std::error::Error + Sync + Send + 'static,
{
Self::new(context, ConnectorErrorKind::Custom).with_source(e)
}
}
pub trait ConnectorResultExt {
#[must_use]
fn with_context(self, context: &'static str) -> Self;
#[must_use]
fn with_source<E>(self, source: E) -> Self
where
E: std::error::Error + Sync + Send + 'static;
}
impl<T> ConnectorResultExt for ConnectorResult<T> {
fn with_context(self, context: &'static str) -> Self {
self.map_err(|mut e| {
e.context = context;
e
})
}
fn with_source<E>(self, source: E) -> Self
where
E: std::error::Error + Sync + Send + 'static,
{
self.map_err(|e| e.with_source(source))
}
}
pub fn encode_x224_packet<T>(x224_msg: &T, buf: &mut WriteBuf) -> ConnectorResult<usize>
where
T: Encode,
{
let x224_msg_buf = encode_vec(x224_msg).map_err(ConnectorError::encode)?;
let pdu = x224::X224Data {
data: std::borrow::Cow::Owned(x224_msg_buf),
};
let written = encode_buf(&X224(pdu), buf).map_err(ConnectorError::encode)?;
Ok(written)
}