mirror of
https://github.com/Devolutions/IronRDP.git
synced 2025-08-04 15:18:17 +00:00
feat(rdpdr): drive announce (#234)
Initial work for drive redirection. Co-authored-by: Benoît CORTIER <bcortier@proton.me>
This commit is contained in:
parent
a2cb31a3d7
commit
2075833ff2
6 changed files with 123 additions and 29 deletions
|
@ -7,6 +7,7 @@ use crate::pdu::{
|
|||
use ironrdp_svc::AsAny;
|
||||
|
||||
pub trait ClipboardError: std::error::Error + Send + Sync + 'static {}
|
||||
|
||||
impl<T> ClipboardError for T where T: std::error::Error + Send + Sync + 'static {}
|
||||
|
||||
/// Message sent by the OS clipboard backend event loop.
|
||||
|
@ -39,12 +40,12 @@ pub enum ClipboardMessage {
|
|||
|
||||
/// Proxy to send messages from the os clipboard backend to the main application event loop
|
||||
/// (e.g. winit event loop).
|
||||
pub trait ClipboardMessageProxy: std::fmt::Debug + Send + Sync {
|
||||
pub trait ClipboardMessageProxy: std::fmt::Debug + Send {
|
||||
fn send_clipboard_message(&self, message: ClipboardMessage);
|
||||
}
|
||||
|
||||
/// OS-specific clipboard backend interface.
|
||||
pub trait CliprdrBackend: AsAny + std::fmt::Debug + Send + Sync + 'static {
|
||||
pub trait CliprdrBackend: AsAny + std::fmt::Debug + Send {
|
||||
/// Returns path to local temporary directory where clipboard-transferred files should be
|
||||
/// stored.
|
||||
fn temporary_directory(&self) -> &str;
|
||||
|
|
|
@ -6,9 +6,10 @@ use crate::pdu::{
|
|||
};
|
||||
use core::fmt;
|
||||
use ironrdp_pdu::PduResult;
|
||||
use ironrdp_svc::AsAny;
|
||||
|
||||
/// OS-specific device redirection backend inteface.
|
||||
pub trait RdpdrBackend: fmt::Debug + Send {
|
||||
pub trait RdpdrBackend: AsAny + fmt::Debug + Send {
|
||||
fn handle_server_device_announce_response(&mut self, pdu: ServerDeviceAnnounceResponse) -> PduResult<()>;
|
||||
fn handle_scard_call(&mut self, req: DeviceControlRequest<ScardIoCtlCode>, call: ScardCall) -> PduResult<()>;
|
||||
}
|
||||
|
|
|
@ -4,10 +4,13 @@ use crate::pdu::{
|
|||
esc::{ScardCall, ScardIoCtlCode},
|
||||
};
|
||||
use ironrdp_pdu::PduResult;
|
||||
use ironrdp_svc::impl_as_any;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NoopRdpdrBackend;
|
||||
|
||||
impl_as_any!(NoopRdpdrBackend);
|
||||
|
||||
impl RdpdrBackend for NoopRdpdrBackend {
|
||||
fn handle_server_device_announce_response(&mut self, _pdu: ServerDeviceAnnounceResponse) -> PduResult<()> {
|
||||
Ok(())
|
||||
|
|
|
@ -10,13 +10,14 @@ extern crate tracing;
|
|||
|
||||
pub mod backend;
|
||||
pub mod pdu;
|
||||
use crate::pdu::efs::MajorFunction;
|
||||
pub use backend::{noop::NoopRdpdrBackend, RdpdrBackend};
|
||||
use ironrdp_pdu::{cursor::ReadCursor, decode_cursor, gcc::ChannelName, other_err, PduResult};
|
||||
use ironrdp_svc::{impl_as_any, CompressionCondition, StaticVirtualChannelProcessor, SvcMessage};
|
||||
use pdu::efs::{
|
||||
Capabilities, ClientDeviceListAnnounce, ClientNameRequest, ClientNameRequestUnicodeFlag, CoreCapability,
|
||||
CoreCapabilityKind, DeviceControlRequest, DeviceIoRequest, Devices, ServerDeviceAnnounceResponse, VersionAndIdPdu,
|
||||
VersionAndIdPduKind,
|
||||
CoreCapabilityKind, DeviceControlRequest, DeviceIoRequest, DeviceType, Devices, ServerDeviceAnnounceResponse,
|
||||
VersionAndIdPdu, VersionAndIdPduKind,
|
||||
};
|
||||
use pdu::esc::{ScardCall, ScardIoCtlCode};
|
||||
use pdu::RdpdrPdu;
|
||||
|
@ -41,6 +42,8 @@ pub struct Rdpdr {
|
|||
backend: Box<dyn RdpdrBackend>,
|
||||
}
|
||||
|
||||
impl_as_any!(Rdpdr);
|
||||
|
||||
impl Rdpdr {
|
||||
pub const NAME: ChannelName = ChannelName::from_static(b"rdpdr\0\0\0");
|
||||
|
||||
|
@ -56,11 +59,42 @@ impl Rdpdr {
|
|||
|
||||
#[must_use]
|
||||
pub fn with_smartcard(mut self, device_id: u32) -> Self {
|
||||
self.device_list.add_smartcard(device_id);
|
||||
self.capabilities.add_smartcard();
|
||||
self.device_list.add_smartcard(device_id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds drive redirection capability.
|
||||
///
|
||||
/// Callers may also include `initial_drives` to pre-configure the list of drives to announce to the server.
|
||||
/// Note that drives do not need to be pre-configured in order to be redirected, a new drive can be announced
|
||||
/// at any time during a session by calling [`Self::add_drive`].
|
||||
#[must_use]
|
||||
pub fn with_drives(mut self, initial_drives: Option<Vec<(u32, String)>>) -> Self {
|
||||
self.capabilities.add_drive();
|
||||
if let Some(initial_drives) = initial_drives {
|
||||
for (device_id, path) in initial_drives {
|
||||
self.device_list.add_drive(device_id, path);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Users should call this method to announce a new drive to the server. It's the caller's responsibility
|
||||
/// to take the returned [`ClientDeviceListAnnounce`] and send it to the server.
|
||||
pub fn add_drive(&mut self, device_id: u32, name: String) -> ClientDeviceListAnnounce {
|
||||
self.device_list.add_drive(device_id, name.clone());
|
||||
ClientDeviceListAnnounce::new_drive(device_id, name)
|
||||
}
|
||||
|
||||
pub fn downcast_backend<T: RdpdrBackend>(&self) -> Option<&T> {
|
||||
self.backend.as_any().downcast_ref::<T>()
|
||||
}
|
||||
|
||||
pub fn downcast_backend_mut<T: RdpdrBackend>(&mut self) -> Option<&mut T> {
|
||||
self.backend.as_any_mut().downcast_mut::<T>()
|
||||
}
|
||||
|
||||
fn handle_server_announce(&mut self, req: VersionAndIdPdu) -> PduResult<Vec<SvcMessage>> {
|
||||
let client_announce_reply = RdpdrPdu::VersionAndIdPdu(VersionAndIdPdu::new_client_announce_reply(req)?);
|
||||
trace!("sending {:?}", client_announce_reply);
|
||||
|
@ -104,28 +138,43 @@ impl Rdpdr {
|
|||
pdu: DeviceIoRequest,
|
||||
payload: &mut ReadCursor<'_>,
|
||||
) -> PduResult<Vec<SvcMessage>> {
|
||||
if self.is_for_smartcard(&pdu) {
|
||||
let req = DeviceControlRequest::<ScardIoCtlCode>::decode(pdu, payload)?;
|
||||
let call = ScardCall::decode(req.io_control_code, payload)?;
|
||||
match self.device_list.for_device_type(pdu.device_id)? {
|
||||
DeviceType::Smartcard => {
|
||||
let req = DeviceControlRequest::<ScardIoCtlCode>::decode(pdu, payload)?;
|
||||
let call = ScardCall::decode(req.io_control_code, payload)?;
|
||||
|
||||
debug!(?req);
|
||||
debug!(?req.io_control_code, ?call);
|
||||
debug!(?req);
|
||||
debug!(?req.io_control_code, ?call);
|
||||
|
||||
self.backend.handle_scard_call(req, call)?;
|
||||
self.backend.handle_scard_call(req, call)?;
|
||||
|
||||
Ok(Vec::new())
|
||||
} else {
|
||||
Err(other_err!("Rdpdr", "received unexpected packet"))
|
||||
Ok(Vec::new())
|
||||
}
|
||||
DeviceType::Filesystem => {
|
||||
debug!("received filesystem packet");
|
||||
match pdu.major_function {
|
||||
MajorFunction::Create => todo!(),
|
||||
MajorFunction::Close => todo!(),
|
||||
MajorFunction::Read => todo!(),
|
||||
MajorFunction::Write => todo!(),
|
||||
MajorFunction::DeviceControl => todo!(),
|
||||
MajorFunction::QueryVolumeInformation => todo!(),
|
||||
MajorFunction::SetVolumeInformation => todo!(),
|
||||
MajorFunction::QueryInformation => todo!(),
|
||||
MajorFunction::SetInformation => todo!(),
|
||||
MajorFunction::DirectoryControl => todo!(),
|
||||
MajorFunction::LockControl => todo!(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// This should never happen, as we only announce devices that we support.
|
||||
warn!(?pdu, "received packet for unsupported device type");
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_for_smartcard(&self, pdu: &DeviceIoRequest) -> bool {
|
||||
self.device_list.is_smartcard(pdu.device_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl_as_any!(Rdpdr);
|
||||
|
||||
impl StaticVirtualChannelProcessor for Rdpdr {
|
||||
fn channel_name(&self) -> ChannelName {
|
||||
Self::NAME
|
||||
|
|
|
@ -273,13 +273,17 @@ impl Capabilities {
|
|||
this
|
||||
}
|
||||
|
||||
pub fn clone_inner(&mut self) -> Vec<CapabilityMessage> {
|
||||
self.0.clone()
|
||||
}
|
||||
|
||||
pub fn add_smartcard(&mut self) {
|
||||
self.push(CapabilityMessage::new_smartcard());
|
||||
self.increment_special_devices();
|
||||
}
|
||||
|
||||
pub fn clone_inner(&mut self) -> Vec<CapabilityMessage> {
|
||||
self.0.clone()
|
||||
pub fn add_drive(&mut self) {
|
||||
self.push(CapabilityMessage::new_drive());
|
||||
}
|
||||
|
||||
fn add_general(&mut self, special_type_device_cap: u32) {
|
||||
|
@ -721,6 +725,13 @@ pub struct ClientDeviceListAnnounce {
|
|||
impl ClientDeviceListAnnounce {
|
||||
const FIXED_PART_SIZE: usize = size_of::<u32>(); // DeviceCount
|
||||
|
||||
/// Library users should not typically call this directly, use [`Rdpdr::add_drive`] instead.
|
||||
pub(crate) fn new_drive(device_id: u32, name: String) -> Self {
|
||||
Self {
|
||||
device_list: vec![DeviceAnnounceHeader::new_drive(device_id, name)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> {
|
||||
dst.write_u32(cast_length!(
|
||||
"ClientDeviceListAnnounce",
|
||||
|
@ -756,11 +767,21 @@ impl Devices {
|
|||
self.push(DeviceAnnounceHeader::new_smartcard(device_id));
|
||||
}
|
||||
|
||||
pub fn is_smartcard(&self, device_id: u32) -> bool {
|
||||
// TODO: consider caching here
|
||||
self.0
|
||||
.iter()
|
||||
.any(|d| d.device_id == device_id && d.device_type == DeviceType::Smartcard)
|
||||
pub fn add_drive(&mut self, device_id: u32, name: String) {
|
||||
self.push(DeviceAnnounceHeader::new_drive(device_id, name));
|
||||
}
|
||||
|
||||
/// Returns the [`DeviceType`] for the given device ID.
|
||||
pub fn for_device_type(&self, device_id: u32) -> PduResult<DeviceType> {
|
||||
if let Some(device_type) = self.0.iter().find(|d| d.device_id == device_id).map(|d| d.device_type) {
|
||||
Ok(device_type)
|
||||
} else {
|
||||
Err(invalid_message_err!(
|
||||
"Devices::for_device_type",
|
||||
"device_id",
|
||||
"no device with that ID"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, device: DeviceAnnounceHeader) {
|
||||
|
@ -802,6 +823,25 @@ impl DeviceAnnounceHeader {
|
|||
}
|
||||
}
|
||||
|
||||
fn new_drive(device_id: u32, name: String) -> Self {
|
||||
// The spec says Unicode but empirically this wants null terminated UTF-8.
|
||||
let mut device_data = name.into_bytes();
|
||||
device_data.push(0u8);
|
||||
|
||||
Self {
|
||||
device_type: DeviceType::Filesystem,
|
||||
device_id,
|
||||
// "The drive name MUST be specified in the PreferredDosName field; however, if the drive name is larger than the allocated size of the PreferredDosName field,
|
||||
// then the drive name MUST be truncated to fit. If the client supports DRIVE_CAPABILITY_VERSION_02 in the Drive Capability Set, then the full name MUST also
|
||||
// be specified in the DeviceData field, as a null-terminated Unicode string. If the DeviceDataLength field is nonzero, the content of the PreferredDosName field
|
||||
// is ignored."
|
||||
//
|
||||
// Since we do support DRIVE_CAPABILITY_VERSION_02, we'll put the full name in the DeviceData field.
|
||||
preferred_dos_name: PreferredDosName("ignored".to_owned()),
|
||||
device_data,
|
||||
}
|
||||
}
|
||||
|
||||
fn encode(&self, dst: &mut WriteCursor<'_>) -> PduResult<()> {
|
||||
dst.write_u32(self.device_type.into());
|
||||
dst.write_u32(self.device_id);
|
||||
|
|
|
@ -321,7 +321,7 @@ pub fn make_channel_definition(channel: &StaticVirtualChannel) -> ChannelDef {
|
|||
}
|
||||
|
||||
/// Type information ([`TypeId`]) may be retrieved at runtime for this type.
|
||||
pub trait AsAny {
|
||||
pub trait AsAny: 'static {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue