diff --git a/lib/srv/desktop/rdp/rdpclient/client.go b/lib/srv/desktop/rdp/rdpclient/client.go index 46d7c9a1d42d2..75beaaf40534c 100644 --- a/lib/srv/desktop/rdp/rdpclient/client.go +++ b/lib/srv/desktop/rdp/rdpclient/client.go @@ -401,11 +401,11 @@ func (c *Client) start() { defer C.free(unsafe.Pointer(path)) if errCode := C.handle_tdp_sd_info_response(c.rustClient, C.CGOSharedDirectoryInfoResponse{ completion_id: C.uint32_t(m.CompletionID), - err_code: C.uint32_t(m.ErrCode), + err_code: m.ErrCode, fso: C.CGOFileSystemObject{ last_modified: C.uint64_t(m.Fso.LastModified), size: C.uint64_t(m.Fso.Size), - file_type: C.uint32_t(m.Fso.FileType), + file_type: m.Fso.FileType, path: path, }, }); errCode != C.ErrCodeSuccess { @@ -417,7 +417,7 @@ func (c *Client) start() { if c.cfg.AllowDirectorySharing { if errCode := C.handle_tdp_sd_create_response(c.rustClient, C.CGOSharedDirectoryCreateResponse{ completion_id: C.uint32_t(m.CompletionID), - err_code: C.uint32_t(m.ErrCode), + err_code: m.ErrCode, }); errCode != C.ErrCodeSuccess { c.cfg.Log.Errorf("SharedDirectoryCreateResponse failed: %v", errCode) return @@ -427,7 +427,7 @@ func (c *Client) start() { if c.cfg.AllowDirectorySharing { if errCode := C.handle_tdp_sd_delete_response(c.rustClient, C.CGOSharedDirectoryDeleteResponse{ completion_id: C.uint32_t(m.CompletionID), - err_code: C.uint32_t(m.ErrCode), + err_code: m.ErrCode, }); errCode != C.ErrCodeSuccess { c.cfg.Log.Errorf("SharedDirectoryDeleteResponse failed: %v", errCode) return @@ -506,7 +506,6 @@ func tdp_sd_acknowledge(handle C.uintptr_t, ack *C.CGOSharedDirectoryAcknowledge }) } -// sharedDirectoryAcknowledge acknowledges that a `Shared Directory Announce` TDP message was processed. func (c *Client) sharedDirectoryAcknowledge(ack tdp.SharedDirectoryAcknowledge) C.CGOErrCode { if c.cfg.AllowDirectorySharing { if err := c.cfg.Conn.OutputMessage(ack); err != nil { diff --git a/lib/srv/desktop/rdp/rdpclient/librdprs.h b/lib/srv/desktop/rdp/rdpclient/librdprs.h index abafc6f10d4b5..7d298f23bb3c8 100644 --- a/lib/srv/desktop/rdp/rdpclient/librdprs.h +++ b/lib/srv/desktop/rdp/rdpclient/librdprs.h @@ -52,6 +52,30 @@ typedef enum CGOPointerWheel { PointerWheelHorizontal, } CGOPointerWheel; +typedef enum FileType { + File = 0, + Directory = 1, +} FileType; + +typedef enum TdpErrCode { + /** + * nil (no error, operation succeeded) + */ + Nil = 0, + /** + * operation failed + */ + Failed = 1, + /** + * resource does not exist + */ + DoesNotExist = 2, + /** + * resource already exists + */ + AlreadyExists = 3, +} TdpErrCode; + /** * Client has an unusual lifecycle: * - connect_rdp creates it on the heap, grabs a raw pointer and returns in to Go @@ -82,13 +106,13 @@ typedef struct CGOSharedDirectoryAnnounce { typedef struct CGOFileSystemObject { uint64_t last_modified; uint64_t size; - uint32_t file_type; + enum FileType file_type; const char *path; } CGOFileSystemObject; typedef struct CGOSharedDirectoryInfoResponse { uint32_t completion_id; - uint32_t err_code; + enum TdpErrCode err_code; struct CGOFileSystemObject fso; } CGOSharedDirectoryInfoResponse; @@ -98,7 +122,7 @@ typedef struct CGOSharedDirectoryInfoResponse { */ typedef struct SharedDirectoryCreateResponse { uint32_t completion_id; - uint32_t err_code; + enum TdpErrCode err_code; } SharedDirectoryCreateResponse; typedef struct SharedDirectoryCreateResponse CGOSharedDirectoryCreateResponse; @@ -149,7 +173,7 @@ typedef struct CGOBitmap { * to acknowledge that a SharedDirectoryAnnounce was received. */ typedef struct SharedDirectoryAcknowledge { - uint32_t err_code; + enum TdpErrCode err_code; uint32_t directory_id; } SharedDirectoryAcknowledge; @@ -164,7 +188,7 @@ typedef struct CGOSharedDirectoryInfoRequest { typedef struct CGOSharedDirectoryCreateRequest { uint32_t completion_id; uint32_t directory_id; - uint32_t file_type; + enum FileType file_type; const char *path; } CGOSharedDirectoryCreateRequest; diff --git a/lib/srv/desktop/rdp/rdpclient/src/lib.rs b/lib/srv/desktop/rdp/rdpclient/src/lib.rs index 8cfd047454b72..3a4ab7cf296a3 100644 --- a/lib/srv/desktop/rdp/rdpclient/src/lib.rs +++ b/lib/srv/desktop/rdp/rdpclient/src/lib.rs @@ -989,7 +989,7 @@ pub struct CGOSharedDirectoryAnnounce { #[derive(Debug)] #[repr(C)] pub struct SharedDirectoryAcknowledge { - pub err_code: u32, + pub err_code: TdpErrCode, pub directory_id: u32, } @@ -1027,14 +1027,14 @@ impl From for SharedDirectoryInfoRequest { #[allow(dead_code)] pub struct SharedDirectoryInfoResponse { completion_id: u32, - err_code: u32, + err_code: TdpErrCode, fso: FileSystemObject, } #[repr(C)] pub struct CGOSharedDirectoryInfoResponse { pub completion_id: u32, - pub err_code: u32, + pub err_code: TdpErrCode, pub fso: CGOFileSystemObject, } @@ -1055,7 +1055,7 @@ impl From for SharedDirectoryInfoResponse { pub struct FileSystemObject { last_modified: u64, size: u64, - file_type: u32, // TODO(isaiah): make an enum + file_type: FileType, path: String, } @@ -1063,7 +1063,7 @@ pub struct FileSystemObject { pub struct CGOFileSystemObject { pub last_modified: u64, pub size: u64, - pub file_type: u32, // TODO(isaiah): make an enum + pub file_type: FileType, pub path: *const c_char, } @@ -1080,13 +1080,33 @@ impl From for FileSystemObject { } } +#[repr(C)] +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum FileType { + File = 0, + Directory = 1, +} + +#[repr(C)] +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum TdpErrCode { + /// nil (no error, operation succeeded) + Nil = 0, + /// operation failed + Failed = 1, + /// resource does not exist + DoesNotExist = 2, + /// resource already exists + AlreadyExists = 3, +} + /// SharedDirectoryCreateRequest is sent by the TDP server to /// the client to request the creation of a new file or directory. #[derive(Debug)] pub struct SharedDirectoryCreateRequest { completion_id: u32, directory_id: u32, - file_type: u32, + file_type: FileType, path: String, } @@ -1094,7 +1114,7 @@ pub struct SharedDirectoryCreateRequest { pub struct CGOSharedDirectoryCreateRequest { pub completion_id: u32, pub directory_id: u32, - pub file_type: u32, + pub file_type: FileType, pub path: *const c_char, } @@ -1104,7 +1124,7 @@ pub struct CGOSharedDirectoryCreateRequest { #[repr(C)] pub struct SharedDirectoryCreateResponse { pub completion_id: u32, - pub err_code: u32, + pub err_code: TdpErrCode, } type CGOSharedDirectoryCreateResponse = SharedDirectoryCreateResponse; diff --git a/lib/srv/desktop/rdp/rdpclient/src/rdpdr/mod.rs b/lib/srv/desktop/rdp/rdpclient/src/rdpdr/mod.rs index 6ed0780ac2d59..c284585520a5c 100644 --- a/lib/srv/desktop/rdp/rdpclient/src/rdpdr/mod.rs +++ b/lib/srv/desktop/rdp/rdpclient/src/rdpdr/mod.rs @@ -23,9 +23,9 @@ use crate::errors::{ use crate::util; use crate::vchan; use crate::{ - FileSystemObject, Payload, SharedDirectoryAcknowledge, SharedDirectoryCreateRequest, + FileSystemObject, FileType, Payload, SharedDirectoryAcknowledge, SharedDirectoryCreateRequest, SharedDirectoryCreateResponse, SharedDirectoryDeleteRequest, SharedDirectoryDeleteResponse, - SharedDirectoryInfoRequest, SharedDirectoryInfoResponse, + SharedDirectoryInfoRequest, SharedDirectoryInfoResponse, TdpErrCode, }; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; @@ -160,60 +160,57 @@ impl Client { fn handle_server_announce(&self, payload: &mut Payload) -> RdpResult>> { let req = ServerAnnounceRequest::decode(payload)?; - debug!("got ServerAnnounceRequest {:?}", req); + debug!("received RDP {:?}", req); + + let resp = ClientAnnounceReply::new(req); + debug!("sending RDP {:?}", resp); + let resp = + self.add_headers_and_chunkify(PacketId::PAKID_CORE_CLIENTID_CONFIRM, resp.encode()?)?; - let resp = self.add_headers_and_chunkify( - PacketId::PAKID_CORE_CLIENTID_CONFIRM, - ClientAnnounceReply::new(req).encode()?, - )?; - debug!("sending client announce reply"); Ok(resp) } fn handle_server_capability(&self, payload: &mut Payload) -> RdpResult>> { let req = ServerCoreCapabilityRequest::decode(payload)?; - debug!("got {:?}", req); + debug!("received RDP {:?}", req); - let resp = self.add_headers_and_chunkify( - PacketId::PAKID_CORE_CLIENT_CAPABILITY, - ClientCoreCapabilityResponse::new_response(self.allow_directory_sharing).encode()?, - )?; + let resp = + ClientCoreCapabilityResponse::new_response(self.allow_directory_sharing).encode()?; + debug!("sending RDP {:?}", resp); + let resp = self.add_headers_and_chunkify(PacketId::PAKID_CORE_CLIENT_CAPABILITY, resp)?; debug!("sending client core capability response"); Ok(resp) } fn handle_client_id_confirm(&mut self, payload: &mut Payload) -> RdpResult>> { let req = ServerClientIdConfirm::decode(payload)?; - debug!("got ServerClientIdConfirm {:?}", req); + debug!("received RDP {:?}", req); // The smartcard initialization sequence that contains this message happens once at session startup, // and once when login succeeds. We only need to announce the smartcard once. let resp = if !self.active_device_ids.contains(&SCARD_DEVICE_ID) { self.push_active_device_id(SCARD_DEVICE_ID)?; - self.add_headers_and_chunkify( - PacketId::PAKID_CORE_DEVICELIST_ANNOUNCE, - ClientDeviceListAnnounceRequest::new_smartcard(SCARD_DEVICE_ID).encode()?, - )? + let resp = ClientDeviceListAnnounceRequest::new_smartcard(SCARD_DEVICE_ID); + debug!("sending RDP {:?}", resp); + self.add_headers_and_chunkify(PacketId::PAKID_CORE_DEVICELIST_ANNOUNCE, resp.encode()?)? } else { - self.add_headers_and_chunkify( - PacketId::PAKID_CORE_DEVICELIST_ANNOUNCE, - ClientDeviceListAnnounceRequest::new_empty().encode()?, - )? + let resp = ClientDeviceListAnnounceRequest::new_empty(); + debug!("sending RDP {:?}", resp); + self.add_headers_and_chunkify(PacketId::PAKID_CORE_DEVICELIST_ANNOUNCE, resp.encode()?)? }; - debug!("replying with: {:?}", resp); Ok(resp) } fn handle_device_reply(&self, payload: &mut Payload) -> RdpResult>> { let req = ServerDeviceAnnounceResponse::decode(payload)?; - debug!("got ServerDeviceAnnounceResponse: {:?}", req); + debug!("received RDP: {:?}", req); if self.active_device_ids.contains(&req.device_id) { if req.device_id != self.get_scard_device_id()? { // This was for a directory we're sharing over TDP - let mut err_code: u32 = 0; + let mut err_code = TdpErrCode::Nil; if req.result_code != NTSTATUS_OK { - err_code = 1; + err_code = TdpErrCode::Failed; debug!("ServerDeviceAnnounceResponse for smartcard redirection failed with result code NTSTATUS({})", &req.result_code); } else { debug!("ServerDeviceAnnounceResponse for shared directory succeeded") @@ -261,252 +258,262 @@ impl Client { match major_function { MajorFunction::IRP_MJ_DEVICE_CONTROL => { - let ioctl = DeviceControlRequest::decode(device_io_request, payload)?; - let is_smart_card_op = ioctl.header.device_id == self.get_scard_device_id()?; - debug!("got: {:?}", ioctl); - - // IRP_MJ_DEVICE_CONTROL is the one major function used by both the smartcard controller (always enabled) - // and shared directory controller (potentially disabled by RBAC). Here we check that directory sharing - // is enabled here before proceeding with any shared directory controls as an additional security measure. - if !is_smart_card_op && !self.allow_directory_sharing { - return Err(Error::TryError("received a drive redirection major function when drive redirection was not allowed".to_string())); - } - let resp = if is_smart_card_op { - // Smart card control - let (code, res) = self.scard.ioctl(ioctl.io_control_code, payload)?; - if code == SPECIAL_NO_RESPONSE { - return Ok(vec![]); - } - DeviceControlResponse::new(&ioctl, code, res) - } else { - // Drive redirection, mimic FreeRDP's "no-op" - // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_main.c#L677-L684 - DeviceControlResponse::new( - &ioctl, - NTSTATUS::STATUS_SUCCESS.to_u32().unwrap(), - vec![], - ) - }; - debug!("replying with: {:?}", resp); - let resp = self.add_headers_and_chunkify( - PacketId::PAKID_CORE_DEVICE_IOCOMPLETION, - resp.encode()?, - )?; - debug!("sending device IO response"); - Ok(resp) + self.process_irp_device_control(device_io_request, payload) } - MajorFunction::IRP_MJ_CREATE => { - // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L207 - let rdp_req = ServerCreateDriveRequest::decode(device_io_request, payload)?; - debug!("got: {:?}", rdp_req); - - // Send a TDP Shared Directory Info Request - // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L210 - (self.tdp_sd_info_request)(SharedDirectoryInfoRequest::from(rdp_req.clone()))?; - - // Add a TDP Shared Directory Info Response handler to the handler cache. - // When we receive a TDP Shared Directory Info Response with this completion_id, - // this handler will be called. - self.pending_sd_info_resp_handlers.insert( - rdp_req.device_io_request.completion_id, - Box::new( - |cli: &mut Self, - res: SharedDirectoryInfoResponse| - -> RdpResult>> { - let rdp_req = rdp_req; - debug!("got {:?}", res); - if res.err_code == 0 { - // The file exists - // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L214 - if res.fso.file_type == 1 { - if rdp_req.create_disposition - == flags::CreateDisposition::FILE_CREATE - { - // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L221 - // ERROR_ALREADY_EXISTS --> STATUS_OBJECT_NAME_COLLISION: https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_main.c#L102 - return cli.prep_device_create_response( - &rdp_req, - NTSTATUS::STATUS_OBJECT_NAME_COLLISION, - 0, - ); - } - - if rdp_req - .create_options - .contains(flags::CreateOptions::FILE_NON_DIRECTORY_FILE) - { - // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L227 - // ERROR_ACCESS_DENIED --> STATUS_ACCESS_DENIED: https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_main.c#L81 - return cli.prep_device_create_response( - &rdp_req, - NTSTATUS::STATUS_ACCESS_DENIED, - 0, - ); - } - } else if rdp_req - .create_options - .contains(flags::CreateOptions::FILE_DIRECTORY_FILE) - { - // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L237 - // ERROR_DIRECTORY --> STATUS_NOT_A_DIRECTORY: https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_main.c#L118 - return cli.prep_device_create_response( - &rdp_req, - NTSTATUS::STATUS_NOT_A_DIRECTORY, - 0, - ); - } - } else if res.err_code == 2 { - // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L242 - if rdp_req - .create_options - .contains(flags::CreateOptions::FILE_DIRECTORY_FILE) - { - if rdp_req.create_disposition.intersects( - flags::CreateDisposition::FILE_OPEN_IF - | flags::CreateDisposition::FILE_CREATE, - ) { - // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L252 - return cli.tdp_sd_create(rdp_req, 1, res.fso); - } else { - // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L258 - // ERROR_FILE_NOT_FOUND --> STATUS_NO_SUCH_FILE: https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_main.c#L85 - return cli.prep_device_create_response( - &rdp_req, - NTSTATUS::STATUS_NO_SUCH_FILE, - 0, - ); - } - } - } else { - // generic RDP error + MajorFunction::IRP_MJ_CREATE => self.process_irp_create(device_io_request, payload), + MajorFunction::IRP_MJ_QUERY_INFORMATION => { + self.process_irp_query_information(device_io_request, payload) + } + MajorFunction::IRP_MJ_CLOSE => self.process_irp_close(device_io_request), + _ => Err(invalid_data_error(&format!( + // TODO(isaiah): send back a not implemented response(?) + "got unsupported major_function in DeviceIoRequest: {:?}", + &major_function + ))), + } + } + + fn process_irp_device_control( + &mut self, + device_io_request: DeviceIoRequest, + payload: &mut Payload, + ) -> RdpResult>> { + let ioctl = DeviceControlRequest::decode(device_io_request, payload)?; + let is_smart_card_op = ioctl.header.device_id == self.get_scard_device_id()?; + debug!("received RDP: {:?}", ioctl); + + // IRP_MJ_DEVICE_CONTROL is the one major function used by both the smartcard controller (always enabled) + // and shared directory controller (potentially disabled by RBAC). Here we check that directory sharing + // is enabled here before proceeding with any shared directory controls as an additional security measure. + if !is_smart_card_op && !self.allow_directory_sharing { + return Err(Error::TryError("received a drive redirection major function when drive redirection was not allowed".to_string())); + } + let resp = if is_smart_card_op { + // Smart card control + let (code, res) = self.scard.ioctl(ioctl.io_control_code, payload)?; + if code == SPECIAL_NO_RESPONSE { + return Ok(vec![]); + } + DeviceControlResponse::new(&ioctl, code, res) + } else { + // Drive redirection, mimic FreeRDP's "no-op" + // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_main.c#L677-L684 + DeviceControlResponse::new(&ioctl, NTSTATUS::STATUS_SUCCESS.to_u32().unwrap(), vec![]) + }; + debug!("sending RDP: {:?}", resp); + let resp = self + .add_headers_and_chunkify(PacketId::PAKID_CORE_DEVICE_IOCOMPLETION, resp.encode()?)?; + Ok(resp) + } + + fn process_irp_create( + &mut self, + device_io_request: DeviceIoRequest, + payload: &mut Payload, + ) -> RdpResult>> { + // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L207 + let rdp_req = ServerCreateDriveRequest::decode(device_io_request, payload)?; + debug!("got: {:?}", rdp_req); + + // Send a TDP Shared Directory Info Request + // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L210 + (self.tdp_sd_info_request)(SharedDirectoryInfoRequest::from(rdp_req.clone()))?; + + // Add a TDP Shared Directory Info Response handler to the handler cache. + // When we receive a TDP Shared Directory Info Response with this completion_id, + // this handler will be called. + self.pending_sd_info_resp_handlers.insert( + rdp_req.device_io_request.completion_id, + Box::new( + |cli: &mut Self, res: SharedDirectoryInfoResponse| -> RdpResult>> { + let rdp_req = rdp_req; + debug!("got {:?}", res); + if res.err_code == TdpErrCode::Nil { + // The file exists + // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L214 + if res.fso.file_type == FileType::Directory { + if rdp_req.create_disposition == flags::CreateDisposition::FILE_CREATE { + // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L221 return cli.prep_device_create_response( &rdp_req, - NTSTATUS::STATUS_UNSUCCESSFUL, + NTSTATUS::STATUS_OBJECT_NAME_COLLISION, 0, ); } - // The actual creation of files and error mapping badevice_io_request.completion_id happens here, for reference: - // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/winpr/libwinpr/file/file.c#L781 - if rdp_req.create_disposition - == flags::CreateDisposition::FILE_SUPERSEDE - { - // If the file already exists, replace it with the given file. If it does not, create the given file. - } else if rdp_req.create_disposition - == flags::CreateDisposition::FILE_OPEN - { - // If the file already exists, open it instead of creating a new file. If it does not, fail the request and do not create a new file. - if res.err_code == 0 { - let file_id = cli.generate_file_id(); - cli.file_cache.insert( - file_id, - FileCacheObject::new(rdp_req.path.clone(), res.fso), - ); - return cli.prep_device_create_response( - &rdp_req, - NTSTATUS::STATUS_SUCCESS, - file_id, - ); - } else { - return cli.prep_device_create_response( - &rdp_req, - NTSTATUS::STATUS_NO_SUCH_FILE, - 0, - ); - } - } else if rdp_req.create_disposition - == flags::CreateDisposition::FILE_CREATE - { - // If the file already exists, fail the request and do not create or open the given file. If it does not, create the given file. - if res.err_code == 0 { - return cli.prep_device_create_response( - &rdp_req, - NTSTATUS::STATUS_OBJECT_NAME_COLLISION, - 0, - ); - } else { - return cli.tdp_sd_create(rdp_req, 0, res.fso); - } - } else if rdp_req.create_disposition - == flags::CreateDisposition::FILE_OPEN_IF - { - // If the file already exists, open it. If it does not, create the given file. - if res.err_code == 0 { - let file_id = cli.generate_file_id(); - cli.file_cache.insert( - file_id, - FileCacheObject::new(rdp_req.path.clone(), res.fso), - ); - return cli.prep_device_create_response( - &rdp_req, - NTSTATUS::STATUS_SUCCESS, - file_id, - ); - } else { - return cli.tdp_sd_create(rdp_req, 0, res.fso); - } - } else if rdp_req.create_disposition - == flags::CreateDisposition::FILE_OVERWRITE - { - // If the file already exists, open it and overwrite it. If it does not, fail the request. - if res.err_code == 0 { - return cli.tdp_sd_overwrite(rdp_req, res.fso); - } else { - return cli.prep_device_create_response( - &rdp_req, - NTSTATUS::STATUS_NO_SUCH_FILE, - 0, - ); - } - } else if rdp_req.create_disposition - == flags::CreateDisposition::FILE_OVERWRITE_IF + if rdp_req + .create_options + .contains(flags::CreateOptions::FILE_NON_DIRECTORY_FILE) { - // If the file already exists, open it and overwrite it. If it does not, create the given file. - if res.err_code == 0 { - return cli.tdp_sd_overwrite(rdp_req, res.fso); - } else { - return cli.tdp_sd_create(rdp_req, 0, res.fso); - } + // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L227 + return cli.prep_device_create_response( + &rdp_req, + NTSTATUS::STATUS_ACCESS_DENIED, + 0, + ); } - Ok(vec![]) - }, - ), - ); - Ok(vec![]) - } - MajorFunction::IRP_MJ_QUERY_INFORMATION => { - // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_main.c#L373 - let rdp_req = - ServerDriveQueryInformationRequest::decode(device_io_request, payload)?; - debug!("got {:?}", rdp_req); - let f = self.get_file_by_id(rdp_req.device_io_request.file_id); - let code = if f.is_some() { - NTSTATUS::STATUS_SUCCESS - } else { - NTSTATUS::STATUS_UNSUCCESSFUL - }; - self.prep_query_info_response(&rdp_req, f, code) - } - MajorFunction::IRP_MJ_CLOSE => { - // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_main.c#L236 - let rdp_req = DeviceCloseRequest::decode(device_io_request); - debug!("got {:?}", rdp_req); - // Remove the file from our cache - if let Some(file) = self.remove_file_by_id(rdp_req.device_io_request.file_id) { - if file.delete_pending { - self.tdp_sd_delete(rdp_req, file) + } else if rdp_req + .create_options + .contains(flags::CreateOptions::FILE_DIRECTORY_FILE) + { + // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L237 + return cli.prep_device_create_response( + &rdp_req, + NTSTATUS::STATUS_NOT_A_DIRECTORY, + 0, + ); + } + } else if res.err_code == TdpErrCode::DoesNotExist { + // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L242 + if rdp_req + .create_options + .contains(flags::CreateOptions::FILE_DIRECTORY_FILE) + { + if rdp_req.create_disposition.intersects( + flags::CreateDisposition::FILE_OPEN_IF + | flags::CreateDisposition::FILE_CREATE, + ) { + // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L252 + return cli.tdp_sd_create(rdp_req, FileType::Directory, res.fso); + } else { + // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L258 + return cli.prep_device_create_response( + &rdp_req, + NTSTATUS::STATUS_NO_SUCH_FILE, + 0, + ); + } + } } else { - self.prep_device_close_response(rdp_req, NTSTATUS::STATUS_SUCCESS) + // generic RDP error + return cli.prep_device_create_response( + &rdp_req, + NTSTATUS::STATUS_UNSUCCESSFUL, + 0, + ); } - } else { - self.prep_device_close_response(rdp_req, NTSTATUS::STATUS_UNSUCCESSFUL) - } + + // The actual creation of files and error mapping in FreeRDP happens here, for reference: + // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/winpr/libwinpr/file/file.c#L781 + match rdp_req.create_disposition { + flags::CreateDisposition::FILE_SUPERSEDE => { + // If the file already exists, replace it with the given file. If it does not, create the given file. + } + flags::CreateDisposition::FILE_OPEN => { + // If the file already exists, open it instead of creating a new file. If it does not, fail the request and do not create a new file. + if res.err_code == TdpErrCode::Nil { + let file_id = cli.generate_file_id(); + cli.file_cache.insert( + file_id, + FileCacheObject::new(rdp_req.path.clone(), res.fso), + ); + return cli.prep_device_create_response( + &rdp_req, + NTSTATUS::STATUS_SUCCESS, + file_id, + ); + } else { + return cli.prep_device_create_response( + &rdp_req, + NTSTATUS::STATUS_NO_SUCH_FILE, + 0, + ); + } + } + flags::CreateDisposition::FILE_CREATE => { + // If the file already exists, fail the request and do not create or open the given file. If it does not, create the given file. + if res.err_code == TdpErrCode::Nil { + return cli.prep_device_create_response( + &rdp_req, + NTSTATUS::STATUS_OBJECT_NAME_COLLISION, + 0, + ); + } else { + return cli.tdp_sd_create(rdp_req, FileType::File, res.fso); + } + } + flags::CreateDisposition::FILE_OPEN_IF => { + // If the file already exists, open it. If it does not, create the given file. + if res.err_code == TdpErrCode::Nil { + let file_id = cli.generate_file_id(); + cli.file_cache.insert( + file_id, + FileCacheObject::new(rdp_req.path.clone(), res.fso), + ); + return cli.prep_device_create_response( + &rdp_req, + NTSTATUS::STATUS_SUCCESS, + file_id, + ); + } else { + return cli.tdp_sd_create(rdp_req, FileType::File, res.fso); + } + } + flags::CreateDisposition::FILE_OVERWRITE => { + // If the file already exists, open it and overwrite it. If it does not, fail the request. + if res.err_code == TdpErrCode::Nil { + return cli.tdp_sd_overwrite(rdp_req, res.fso); + } else { + return cli.prep_device_create_response( + &rdp_req, + NTSTATUS::STATUS_NO_SUCH_FILE, + 0, + ); + } + } + flags::CreateDisposition::FILE_OVERWRITE_IF => { + // If the file already exists, open it and overwrite it. If it does not, create the given file. + if res.err_code == TdpErrCode::Nil { + return cli.tdp_sd_overwrite(rdp_req, res.fso); + } else { + return cli.tdp_sd_create(rdp_req, FileType::File, res.fso); + } + } + _ => { + return Err(invalid_data_error(&format!( + "received unknown CreateDisposition value for RDP {:?}", + rdp_req + ))); + } + } + + Ok(vec![]) + }, + ), + ); + Ok(vec![]) + } + + fn process_irp_query_information( + &mut self, + device_io_request: DeviceIoRequest, + payload: &mut Payload, + ) -> RdpResult>> { + // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_main.c#L373 + let rdp_req = ServerDriveQueryInformationRequest::decode(device_io_request, payload)?; + debug!("received RDP: {:?}", rdp_req); + let f = self.get_file_by_id(rdp_req.device_io_request.file_id); + let code = if f.is_some() { + NTSTATUS::STATUS_SUCCESS + } else { + NTSTATUS::STATUS_UNSUCCESSFUL + }; + self.prep_query_info_response(&rdp_req, f, code) + } + + fn process_irp_close(&mut self, device_io_request: DeviceIoRequest) -> RdpResult>> { + // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_main.c#L236 + let rdp_req = DeviceCloseRequest::decode(device_io_request); + debug!("received RDP: {:?}", rdp_req); + // Remove the file from our cache + if let Some(file) = self.remove_file_by_id(rdp_req.device_io_request.file_id) { + if file.delete_pending { + self.tdp_sd_delete(rdp_req, file) + } else { + self.prep_device_close_response(rdp_req, NTSTATUS::STATUS_SUCCESS) } - _ => Err(invalid_data_error(&format!( - // TODO(isaiah): send back a not implemented response(?) - "got unsupported major_function in DeviceIoRequest: {:?}", - &major_function - ))), + } else { + self.prep_device_close_response(rdp_req, NTSTATUS::STATUS_UNSUCCESSFUL) } } @@ -518,7 +525,7 @@ impl Client { mcs: &mut mcs::Client, ) -> RdpResult<()> { self.push_active_device_id(req.device_list[0].device_id)?; - debug!("sending new drive for redirection: {:?}", req); + debug!("sending new drive for redirection over RDP: {:?}", req); let responses = self.add_headers_and_chunkify(PacketId::PAKID_CORE_DEVICELIST_ANNOUNCE, req.encode()?)?; @@ -535,7 +542,7 @@ impl Client { res: SharedDirectoryInfoResponse, mcs: &mut mcs::Client, ) -> RdpResult<()> { - debug!("got {:?}", res); + debug!("received TDP: {:?}", res); if let Some(tdp_resp_handler) = self .pending_sd_info_resp_handlers .remove(&res.completion_id) @@ -559,7 +566,7 @@ impl Client { res: SharedDirectoryCreateResponse, mcs: &mut mcs::Client, ) -> RdpResult<()> { - debug!("got {:?}", res); + debug!("received TDP: {:?}", res); if let Some(tdp_resp_handler) = self .pending_sd_create_resp_handlers .remove(&res.completion_id) @@ -583,7 +590,7 @@ impl Client { res: SharedDirectoryDeleteResponse, mcs: &mut mcs::Client, ) -> RdpResult<()> { - debug!("got {:?}", res); + debug!("received TDP: {:?}", res); if let Some(tdp_resp_handler) = self .pending_sd_delete_resp_handlers .remove(&res.completion_id) @@ -609,7 +616,7 @@ impl Client { new_file_id: u32, ) -> RdpResult>> { let resp = DeviceCreateResponse::new(req, io_status, new_file_id); - debug!("replying with: {:?}", resp); + debug!("sending RDP: {:?}", resp); let resp = self .add_headers_and_chunkify(PacketId::PAKID_CORE_DEVICE_IOCOMPLETION, resp.encode()?)?; Ok(resp) @@ -622,7 +629,7 @@ impl Client { io_status: NTSTATUS, ) -> RdpResult>> { let resp = ClientDriveQueryInformationResponse::new(req, file, io_status)?; - debug!("replying with: {:?}", resp); + debug!("sending RDP: {:?}", resp); let resp = self .add_headers_and_chunkify(PacketId::PAKID_CORE_DEVICE_IOCOMPLETION, resp.encode()?)?; Ok(resp) @@ -645,22 +652,25 @@ impl Client { fn tdp_sd_create( &mut self, rdp_req: DeviceCreateRequest, - file_type: u32, + file_type: FileType, fso: FileSystemObject, ) -> RdpResult>> { - (self.tdp_sd_create_request)(SharedDirectoryCreateRequest { + let tdp_req = SharedDirectoryCreateRequest { completion_id: rdp_req.device_io_request.completion_id, directory_id: rdp_req.device_io_request.device_id, file_type, path: rdp_req.path.clone(), - })?; + }; + debug!("sending TDP: {:?}", tdp_req); + (self.tdp_sd_create_request)(tdp_req)?; + self.pending_sd_create_resp_handlers.insert( rdp_req.device_io_request.completion_id, Box::new( move |cli: &mut Self, res: SharedDirectoryCreateResponse| -> RdpResult>> { - if res.err_code == 0 { + if res.err_code == TdpErrCode::Nil { let file_id = cli.generate_file_id(); cli.file_cache .insert(file_id, FileCacheObject::new(rdp_req.path.clone(), fso)); @@ -682,17 +692,19 @@ impl Client { rdp_req: DeviceCreateRequest, fso: FileSystemObject, ) -> RdpResult>> { - (self.tdp_sd_delete_request)(SharedDirectoryDeleteRequest { + let tdp_req = SharedDirectoryDeleteRequest { completion_id: rdp_req.device_io_request.completion_id, directory_id: rdp_req.device_io_request.device_id, path: rdp_req.path.clone(), - })?; + }; + debug!("sending TDP: {:?}", tdp_req); + (self.tdp_sd_delete_request)(tdp_req)?; self.pending_sd_delete_resp_handlers.insert( rdp_req.device_io_request.completion_id, Box::new( |cli: &mut Self, res: SharedDirectoryDeleteResponse| -> RdpResult>> { - if res.err_code == 0 { - cli.tdp_sd_create(rdp_req, 0, fso) + if res.err_code == TdpErrCode::Nil { + cli.tdp_sd_create(rdp_req, FileType::File, fso) } else { cli.prep_device_create_response(&rdp_req, NTSTATUS::STATUS_UNSUCCESSFUL, 0) } @@ -707,21 +719,23 @@ impl Client { rdp_req: DeviceCloseRequest, file: FileCacheObject, ) -> RdpResult>> { - (self.tdp_sd_delete_request)(SharedDirectoryDeleteRequest { + let tdp_req = SharedDirectoryDeleteRequest { completion_id: rdp_req.device_io_request.completion_id, directory_id: rdp_req.device_io_request.device_id, path: file.path, - })?; + }; + debug!("sending TDP: {:?}", tdp_req); + (self.tdp_sd_delete_request)(tdp_req)?; self.pending_sd_delete_resp_handlers.insert( rdp_req.device_io_request.completion_id, Box::new( |cli: &mut Self, res: SharedDirectoryDeleteResponse| -> RdpResult>> { - let resp = if res.err_code == 0 { + let code = if res.err_code == TdpErrCode::Nil { NTSTATUS::STATUS_SUCCESS } else { NTSTATUS::STATUS_UNSUCCESSFUL }; - cli.prep_device_close_response(rdp_req, resp) + cli.prep_device_close_response(rdp_req, code) }, ), ); @@ -1792,7 +1806,7 @@ impl ClientDriveQueryInformationResponse { last_access_time: to_windows_time(file.fso.last_modified), last_write_time: to_windows_time(file.fso.last_modified), change_time: to_windows_time(file.fso.last_modified), - file_attributes: if file.fso.file_type == 0 { + file_attributes: if file.fso.file_type == FileType::File { flags::FileAttributes::FILE_ATTRIBUTE_NORMAL } else { flags::FileAttributes::FILE_ATTRIBUTE_DIRECTORY @@ -1812,7 +1826,7 @@ impl ClientDriveQueryInformationResponse { } else { Boolean::False }, - directory: if file.fso.file_type == 0 { + directory: if file.fso.file_type == FileType::File { Boolean::False } else { Boolean::True @@ -1824,7 +1838,7 @@ impl ClientDriveQueryInformationResponse { Some(FILE_ATTRIBUTE_TAG_INFO_SIZE), Some(FsInformationClass::FileAttributeTagInformation( FileAttributeTagInformation { - file_attributes: if file.fso.file_type == 0 { + file_attributes: if file.fso.file_type == FileType::File { flags::FileAttributes::FILE_ATTRIBUTE_NORMAL } else { flags::FileAttributes::FILE_ATTRIBUTE_DIRECTORY