Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clipboard PDU support #170

Merged
merged 9 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/ironrdp-fuzzing/src/oracles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ pub fn pdu_decode(data: &[u8]) {
let _ = input::InputEvent::from_buffer(data);

let _ = decode::<bitmap::rdp6::BitmapStream>(data);

let _ = decode::<clipboard::ClipboardPdu>(data);
}

pub fn rle_decompress_bitmap(input: BitmapInput) {
Expand Down
249 changes: 249 additions & 0 deletions crates/ironrdp-pdu/src/clipboard/capabilities.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
use crate::clipboard::PartialHeader;
use crate::cursor::{ReadCursor, WriteCursor};
use crate::{ensure_fixed_part_size, invalid_message_err, PduDecode, PduEncode, PduResult};
use bitflags::bitflags;

/// Represents `CLIPRDR_CAPS`
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Capabilities {
pub capabilities: Vec<CapabilitySet>,
}

impl Capabilities {
const NAME: &str = "CLIPRDR_CAPS";
const FIXED_PART_SIZE: usize = std::mem::size_of::<u16>() * 2;

fn inner_size(&self) -> usize {
Self::FIXED_PART_SIZE + self.capabilities.iter().map(|c| c.size()).sum::<usize>()
}
}

impl PduEncode for Capabilities {
fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> {
if self.capabilities.len() > u16::MAX as usize {
return Err(invalid_message_err!(
"cCapabilitiesSets",
"Too much capability sets specified",
));
}

let header = PartialHeader::new(self.inner_size() as u32);
CBenoit marked this conversation as resolved.
Show resolved Hide resolved
header.encode(dst)?;

ensure_size!(in: dst, size: self.inner_size());

dst.write_u16(self.capabilities.len() as u16);
dst.write_u16(0); // pad

for capability in &self.capabilities {
capability.encode(dst)?;
}

Ok(())
}

fn name(&self) -> &'static str {
Self::NAME
}

fn size(&self) -> usize {
self.inner_size() + PartialHeader::SIZE
}
}

impl<'de> PduDecode<'de> for Capabilities {
fn decode(src: &mut ReadCursor<'de>) -> PduResult<Self> {
let _header = PartialHeader::decode(src)?;

ensure_fixed_part_size!(in: src);
let capabilities_count = src.read_u16();
src.read_u16(); // pad

let mut capabilities = Vec::with_capacity(capabilities_count as usize);

for _ in 0..capabilities_count {
let caps = CapabilitySet::decode(src)?;
capabilities.push(caps);
}

Ok(Self { capabilities })
}
}

/// Represents `CLIPRDR_CAPS_SET`
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CapabilitySet {
General(GeneralCapabilitySet),
}

impl CapabilitySet {
const NAME: &str = "CLIPRDR_CAPS_SET";
const FIXED_PART_SIZE: usize = std::mem::size_of::<u16>() * 2;

const CAPSTYPE_GENERAL: u16 = 0x0001;
}

impl From<GeneralCapabilitySet> for CapabilitySet {
fn from(value: GeneralCapabilitySet) -> Self {
Self::General(value)
}
}

impl PduEncode for CapabilitySet {
fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> {
let (caps, length) = match self {
Self::General(value) => {
let length = value.size() + Self::FIXED_PART_SIZE;
(value, length)
}
};

ensure_size!(in: dst, size: length);
dst.write_u16(Self::CAPSTYPE_GENERAL);
dst.write_u16(length as u16);
caps.encode(dst)
}

fn name(&self) -> &'static str {
Self::NAME
}

fn size(&self) -> usize {
let variable_size = match self {
Self::General(value) => value.size(),
};

Self::FIXED_PART_SIZE + variable_size
}
}

impl<'de> PduDecode<'de> for CapabilitySet {
fn decode(src: &mut ReadCursor<'de>) -> PduResult<Self> {
ensure_fixed_part_size!(in: src);

let caps_type = src.read_u16();
let _length = src.read_u16();

match caps_type {
Self::CAPSTYPE_GENERAL => {
let general = GeneralCapabilitySet::decode(src)?;
Ok(Self::General(general))
}
_ => Err(invalid_message_err!(
"capabilitySetType",
"invalid clipboard capability set type"
)),
}
}
}

/// Represents `CLIPRDR_GENERAL_CAPABILITY` without header
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GeneralCapabilitySet {
pub version: ClipboardProtocolVersion,
pub general_flags: ClipboardGeneralCapabilityFlags,
}

impl GeneralCapabilitySet {
const NAME: &str = "CLIPRDR_GENERAL_CAPABILITY";
const FIXED_PART_SIZE: usize = std::mem::size_of::<u32>() * 2;
}

impl PduEncode for GeneralCapabilitySet {
fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> {
ensure_fixed_part_size!(in: dst);

dst.write_u32(self.version.into());
dst.write_u32(self.general_flags.bits());

Ok(())
}

fn name(&self) -> &'static str {
Self::NAME
}

fn size(&self) -> usize {
Self::FIXED_PART_SIZE
}
}

impl<'de> PduDecode<'de> for GeneralCapabilitySet {
fn decode(src: &mut ReadCursor<'de>) -> PduResult<Self> {
ensure_fixed_part_size!(in: src);

let version: ClipboardProtocolVersion = src.read_u32().try_into()?;
let general_flags = ClipboardGeneralCapabilityFlags::from_bits_truncate(src.read_u32());

Ok(Self { version, general_flags })
}
}

/// Specifies the `Remote Desktop Protocol: Clipboard Virtual Channel Extension` version number.
/// This field is for informational purposes and MUST NOT be used to make protocol capability
/// decisions. The actual features supported are specified via [`ClipboardGeneralCapabilityFlags`]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClipboardProtocolVersion {
V1,
V2,
}

impl ClipboardProtocolVersion {
const VERSION_VALUE_V1: u32 = 0x00000001;
const VERSION_VALUE_V2: u32 = 0x00000002;

const NAME: &str = "CLIPRDR_CAPS_VERSION";
}

impl From<ClipboardProtocolVersion> for u32 {
fn from(version: ClipboardProtocolVersion) -> Self {
match version {
ClipboardProtocolVersion::V1 => ClipboardProtocolVersion::VERSION_VALUE_V1,
ClipboardProtocolVersion::V2 => ClipboardProtocolVersion::VERSION_VALUE_V2,
}
}
}

impl TryFrom<u32> for ClipboardProtocolVersion {
type Error = crate::PduError;

fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
Self::VERSION_VALUE_V1 => Ok(Self::V1),
Self::VERSION_VALUE_V2 => Ok(Self::V2),
_ => Err(invalid_message_err!(
"version",
"Invalid clipboard capabilities version"
)),
}
}
}

bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ClipboardGeneralCapabilityFlags: u32 {
/// The Long Format Name variant of the Format List PDU is supported
/// for exchanging updated format names. If this flag is not set, the
/// Short Format Name variant MUST be used. If this flag is set by both
/// protocol endpoints, then the Long Format Name variant MUST be
/// used.
const USE_LONG_FORMAT_NAMES = 0x00000002;
/// File copy and paste using stream-based operations are supported
/// using the File Contents Request PDU and File Contents Response
/// PDU.
const STREAM_FILECLIP_ENABLED = 0x00000004;
/// Indicates that any description of files to copy and paste MUST NOT
/// include the source path of the files.
const FILECLIP_NO_FILE_PATHS = 0x00000008;
/// Locking and unlocking of File Stream data on the clipboard is
/// supported using the Lock Clipboard Data PDU and Unlock Clipboard
/// Data PDU.
const CAN_LOCK_CLIPDATA = 0x00000010;
/// Indicates support for transferring files that are larger than
/// 4,294,967,295 bytes in size. If this flag is not set, then only files of
/// size less than or equal to 4,294,967,295 bytes can be exchanged
/// using the File Contents Request PDU and File Contents
/// Response PDU.
const HUGE_FILE_SUPPORT_ENABLED = 0x00000020;
}
}
77 changes: 77 additions & 0 deletions crates/ironrdp-pdu/src/clipboard/client_temporary_directory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use crate::clipboard::PartialHeader;
use crate::cursor::{ReadCursor, WriteCursor};
use crate::utils::{read_string_from_cursor, write_string_to_cursor, CharacterSet};
use crate::{ensure_fixed_part_size, invalid_message_err, PduDecode, PduEncode, PduResult};
use std::borrow::Cow;

/// Represents `CLIPRDR_TEMP_DIRECTORY`
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClientTemporaryDirectory<'a> {
path_buffer: Cow<'a, [u8]>,
}

impl ClientTemporaryDirectory<'_> {
const PATH_BUFFER_SIZE: usize = 520;

const NAME: &str = "CLIPRDR_TEMP_DIRECTORY";
const FIXED_PART_SIZE: usize = Self::PATH_BUFFER_SIZE;

/// Creates new `ClientTemporaryDirectory` and encodes given path to UTF-16 representation.
pub fn new(path: String) -> PduResult<Self> {
let mut buffer = vec![0x00; Self::PATH_BUFFER_SIZE];

{
let mut cursor = WriteCursor::new(&mut buffer);
write_string_to_cursor(&mut cursor, &path, CharacterSet::Unicode, true)?;
}

Ok(Self {
path_buffer: Cow::Owned(buffer),
})
}

/// Returns parsed temporary directory path.
pub fn temporary_directory_path(&self) -> PduResult<String> {
let mut cursor = ReadCursor::new(&self.path_buffer);

read_string_from_cursor(&mut cursor, CharacterSet::Unicode, true)
.map_err(|_| invalid_message_err!("wszTempDir", "failed to decode temp dir path"))
}

fn inner_size(&self) -> usize {
Self::FIXED_PART_SIZE
}
}

impl PduEncode for ClientTemporaryDirectory<'_> {
fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> {
let header = PartialHeader::new(self.inner_size() as u32);
header.encode(dst)?;

ensure_size!(in: dst, size: self.inner_size());
dst.write_slice(&self.path_buffer);

Ok(())
}

fn name(&self) -> &'static str {
Self::NAME
}

fn size(&self) -> usize {
PartialHeader::SIZE + self.inner_size()
}
}

impl<'de> PduDecode<'de> for ClientTemporaryDirectory<'de> {
fn decode(src: &mut ReadCursor<'de>) -> PduResult<Self> {
let _header = PartialHeader::decode(src)?;

ensure_fixed_part_size!(in: src);
let buffer = src.read_slice(Self::PATH_BUFFER_SIZE);

Ok(Self {
path_buffer: Cow::Borrowed(buffer),
})
}
}
Loading