diff --git a/lib/srv/desktop/rdp/rdpclient/client.go b/lib/srv/desktop/rdp/rdpclient/client.go index 088728627c4..af70b963132 100644 --- a/lib/srv/desktop/rdp/rdpclient/client.go +++ b/lib/srv/desktop/rdp/rdpclient/client.go @@ -509,6 +509,16 @@ func (c *Client) start() { return } } + case tdp.SharedDirectoryMoveResponse: + if c.cfg.AllowDirectorySharing { + if errCode := C.handle_tdp_sd_move_response(c.rustClient, C.CGOSharedDirectoryMoveResponse{ + completion_id: C.uint32_t(m.CompletionID), + err_code: m.ErrCode, + }); errCode != C.ErrCodeSuccess { + c.cfg.Log.Errorf("SharedDirectoryMoveResponse failed: %v", errCode) + return + } + } default: c.cfg.Log.Warningf("Skipping unimplemented TDP message type %T", msg) } @@ -585,15 +595,15 @@ func tdp_sd_acknowledge(handle C.uintptr_t, ack *C.CGOSharedDirectoryAcknowledge // sharedDirectoryAcknowledge is sent by the TDP server to the client // to acknowledge that a SharedDirectoryAnnounce was received. func (c *Client) sharedDirectoryAcknowledge(ack tdp.SharedDirectoryAcknowledge) C.CGOErrCode { - if c.cfg.AllowDirectorySharing { - if err := c.cfg.Conn.OutputMessage(ack); err != nil { - c.cfg.Log.Errorf("failed to send SharedDirectoryAcknowledge: %v", err) - return C.ErrCodeFailure - } - return C.ErrCodeSuccess + if !c.cfg.AllowDirectorySharing { + return C.ErrCodeFailure } - return C.ErrCodeFailure + if err := c.cfg.Conn.OutputMessage(ack); err != nil { + c.cfg.Log.Errorf("failed to send SharedDirectoryAcknowledge: %v", err) + return C.ErrCodeFailure + } + return C.ErrCodeSuccess } //export tdp_sd_info_request @@ -608,15 +618,15 @@ func tdp_sd_info_request(handle C.uintptr_t, req *C.CGOSharedDirectoryInfoReques // sharedDirectoryInfoRequest is sent from the TDP server to the client // to request information about a file or directory at a given path. func (c *Client) sharedDirectoryInfoRequest(req tdp.SharedDirectoryInfoRequest) C.CGOErrCode { - if c.cfg.AllowDirectorySharing { - if err := c.cfg.Conn.OutputMessage(req); err != nil { - c.cfg.Log.Errorf("failed to send SharedDirectoryAcknowledge: %v", err) - return C.ErrCodeFailure - } - return C.ErrCodeSuccess + if !c.cfg.AllowDirectorySharing { + return C.ErrCodeFailure } - return C.ErrCodeFailure + if err := c.cfg.Conn.OutputMessage(req); err != nil { + c.cfg.Log.Errorf("failed to send SharedDirectoryAcknowledge: %v", err) + return C.ErrCodeFailure + } + return C.ErrCodeSuccess } //export tdp_sd_create_request @@ -632,15 +642,15 @@ func tdp_sd_create_request(handle C.uintptr_t, req *C.CGOSharedDirectoryCreateRe // sharedDirectoryCreateRequest is sent by the TDP server to // the client to request the creation of a new file or directory. func (c *Client) sharedDirectoryCreateRequest(req tdp.SharedDirectoryCreateRequest) C.CGOErrCode { - if c.cfg.AllowDirectorySharing { - if err := c.cfg.Conn.OutputMessage(req); err != nil { - c.cfg.Log.Errorf("failed to send SharedDirectoryCreateRequest: %v", err) - return C.ErrCodeFailure - } - return C.ErrCodeSuccess + if !c.cfg.AllowDirectorySharing { + return C.ErrCodeFailure } - return C.ErrCodeFailure + if err := c.cfg.Conn.OutputMessage(req); err != nil { + c.cfg.Log.Errorf("failed to send SharedDirectoryCreateRequest: %v", err) + return C.ErrCodeFailure + } + return C.ErrCodeSuccess } //export tdp_sd_delete_request @@ -655,15 +665,15 @@ func tdp_sd_delete_request(handle C.uintptr_t, req *C.CGOSharedDirectoryDeleteRe // sharedDirectoryDeleteRequest is sent by the TDP server to the client // to request the deletion of a file or directory at path. func (c *Client) sharedDirectoryDeleteRequest(req tdp.SharedDirectoryDeleteRequest) C.CGOErrCode { - if c.cfg.AllowDirectorySharing { - if err := c.cfg.Conn.OutputMessage(req); err != nil { - c.cfg.Log.Errorf("failed to send SharedDirectoryDeleteRequest: %v", err) - return C.ErrCodeFailure - } - return C.ErrCodeSuccess + if !c.cfg.AllowDirectorySharing { + return C.ErrCodeFailure } - return C.ErrCodeFailure + if err := c.cfg.Conn.OutputMessage(req); err != nil { + c.cfg.Log.Errorf("failed to send SharedDirectoryDeleteRequest: %v", err) + return C.ErrCodeFailure + } + return C.ErrCodeSuccess } //export tdp_sd_list_request @@ -678,15 +688,15 @@ func tdp_sd_list_request(handle C.uintptr_t, req *C.CGOSharedDirectoryListReques // sharedDirectoryListRequest is sent by the TDP server to the client // to request the contents of a directory. func (c *Client) sharedDirectoryListRequest(req tdp.SharedDirectoryListRequest) C.CGOErrCode { - if c.cfg.AllowDirectorySharing { - if err := c.cfg.Conn.OutputMessage(req); err != nil { - c.cfg.Log.Errorf("failed to send SharedDirectoryListRequest: %v", err) - return C.ErrCodeFailure - } - return C.ErrCodeSuccess + if !c.cfg.AllowDirectorySharing { + return C.ErrCodeFailure } - return C.ErrCodeFailure + if err := c.cfg.Conn.OutputMessage(req); err != nil { + c.cfg.Log.Errorf("failed to send SharedDirectoryListRequest: %v", err) + return C.ErrCodeFailure + } + return C.ErrCodeSuccess } //export tdp_sd_read_request @@ -695,7 +705,6 @@ func tdp_sd_read_request(handle C.uintptr_t, req *C.CGOSharedDirectoryReadReques CompletionID: uint32(req.completion_id), DirectoryID: uint32(req.directory_id), Path: C.GoString(req.path), - PathLength: uint32(req.path_length), Offset: uint64(req.offset), Length: uint32(req.length), }) @@ -704,14 +713,15 @@ func tdp_sd_read_request(handle C.uintptr_t, req *C.CGOSharedDirectoryReadReques // SharedDirectoryReadRequest is sent by the TDP server to the client // to request the contents of a file. func (c *Client) sharedDirectoryReadRequest(req tdp.SharedDirectoryReadRequest) C.CGOErrCode { - if c.cfg.AllowDirectorySharing { - if err := c.cfg.Conn.OutputMessage(req); err != nil { - c.cfg.Log.Errorf("failed to send SharedDirectoryReadRequest: %v", err) - return C.ErrCodeFailure - } - return C.ErrCodeSuccess + if !c.cfg.AllowDirectorySharing { + return C.ErrCodeFailure } - return C.ErrCodeFailure + + if err := c.cfg.Conn.OutputMessage(req); err != nil { + c.cfg.Log.Errorf("failed to send SharedDirectoryReadRequest: %v", err) + return C.ErrCodeFailure + } + return C.ErrCodeSuccess } //export tdp_sd_write_request @@ -720,7 +730,6 @@ func tdp_sd_write_request(handle C.uintptr_t, req *C.CGOSharedDirectoryWriteRequ CompletionID: uint32(req.completion_id), DirectoryID: uint32(req.directory_id), Offset: uint64(req.offset), - PathLength: uint32(req.path_length), Path: C.GoString(req.path), WriteDataLength: uint32(req.write_data_length), WriteData: C.GoBytes(unsafe.Pointer(req.write_data), C.int(req.write_data_length)), @@ -730,14 +739,38 @@ func tdp_sd_write_request(handle C.uintptr_t, req *C.CGOSharedDirectoryWriteRequ // SharedDirectoryWriteRequest is sent by the TDP server to the client // to write to a file. func (c *Client) sharedDirectoryWriteRequest(req tdp.SharedDirectoryWriteRequest) C.CGOErrCode { - if c.cfg.AllowDirectorySharing { - if err := c.cfg.Conn.OutputMessage(req); err != nil { - c.cfg.Log.Errorf("failed to send SharedDirectoryWriteRequest: %v", err) - return C.ErrCodeFailure - } - return C.ErrCodeSuccess + if !c.cfg.AllowDirectorySharing { + return C.ErrCodeFailure } - return C.ErrCodeFailure + + if err := c.cfg.Conn.OutputMessage(req); err != nil { + c.cfg.Log.Errorf("failed to send SharedDirectoryWriteRequest: %v", err) + return C.ErrCodeFailure + } + return C.ErrCodeSuccess +} + +//export tdp_sd_move_request +func tdp_sd_move_request(handle C.uintptr_t, req *C.CGOSharedDirectoryMoveRequest) C.CGOErrCode { + return cgo.Handle(handle).Value().(*Client).sharedDirectoryMoveRequest(tdp.SharedDirectoryMoveRequest{ + CompletionID: uint32(req.completion_id), + DirectoryID: uint32(req.directory_id), + OriginalPath: C.GoString(req.original_path), + NewPath: C.GoString(req.new_path), + }) +} + +func (c *Client) sharedDirectoryMoveRequest(req tdp.SharedDirectoryMoveRequest) C.CGOErrCode { + if !c.cfg.AllowDirectorySharing { + return C.ErrCodeFailure + } + + if err := c.cfg.Conn.OutputMessage(req); err != nil { + c.cfg.Log.Errorf("failed to send SharedDirectoryMoveRequest: %v", err) + return C.ErrCodeFailure + } + return C.ErrCodeSuccess + } // close closes the RDP client connection and diff --git a/lib/srv/desktop/rdp/rdpclient/src/lib.rs b/lib/srv/desktop/rdp/rdpclient/src/lib.rs index 71a65ba1974..bd8b6321021 100644 --- a/lib/srv/desktop/rdp/rdpclient/src/lib.rs +++ b/lib/srv/desktop/rdp/rdpclient/src/lib.rs @@ -504,6 +504,46 @@ fn connect_rdp_inner( } }); + let tdp_sd_move_request = Box::new(move |req: SharedDirectoryMoveRequest| -> RdpResult<()> { + debug!("sending TDP SharedDirectoryMoveRequest: {:?}", req); + match req.original_path.to_cstring() { + Ok(original_path) => match req.new_path.to_cstring() { + Ok(new_path) => { + unsafe { + let err = tdp_sd_move_request( + go_ref, + &mut CGOSharedDirectoryMoveRequest { + completion_id: req.completion_id, + directory_id: req.directory_id, + original_path: original_path.as_ptr(), + new_path: new_path.as_ptr(), + }, + ); + + if err != CGOErrCode::ErrCodeSuccess { + return Err(RdpError::TryError(String::from( + "call to tdp_sd_Move_failed", + ))); + } + } + Ok(()) + } + Err(_) => { + return Err(RdpError::TryError(format!( + "new_path contained characters that couldn't be converted to a C string: {:?}", + req.new_path + ))); + } + }, + Err(_) => { + return Err(RdpError::TryError(format!( + "original_path contained characters that couldn't be converted to a C string: {:?}", + req.original_path + ))); + } + } + }); + // Client for the "rdpdr" channel - smartcard emulation and drive redirection. let rdpdr = rdpdr::Client::new(rdpdr::Config { cert_der: params.cert_der, @@ -517,6 +557,7 @@ fn connect_rdp_inner( tdp_sd_list_request, tdp_sd_read_request, tdp_sd_write_request, + tdp_sd_move_request, }); // Client for the "cliprdr" channel - clipboard sharing. @@ -653,6 +694,13 @@ impl RdpClient { self.rdpdr.handle_tdp_sd_write_response(res, &mut self.mcs) } + pub fn handle_tdp_sd_move_response( + &mut self, + res: SharedDirectoryMoveResponse, + ) -> RdpResult<()> { + self.rdpdr.handle_tdp_sd_move_response(res, &mut self.mcs) + } + pub fn shutdown(&mut self) -> RdpResult<()> { self.mcs.shutdown() } @@ -1033,6 +1081,43 @@ pub unsafe extern "C" fn handle_tdp_sd_write_response( } } +/// handle_tdp_sd_move_response handles a TDP Shared Directory Move Response +/// message +/// +/// # Safety +/// +/// client_ptr MUST be a valid pointer. +/// (validity defined by https://doc.rust-lang.org/nightly/core/primitive.pointer.html#method.as_ref-1) +#[no_mangle] +pub unsafe extern "C" fn handle_tdp_sd_move_response( + client_ptr: *mut Client, + res: CGOSharedDirectoryMoveResponse, +) -> CGOErrCode { + // # Safety + // + // This function MUST NOT hang on to any of the pointers passed in to it after it returns. + // In other words, all pointer data that needs to persist after this function returns MUST + // be copied into Rust-owned memory. + + let res: SharedDirectoryMoveResponse = res; + + let client = match Client::from_ptr(client_ptr) { + Ok(client) => client, + Err(cgo_error) => { + return cgo_error; + } + }; + + let mut rdp_client = client.rdp_client.lock().unwrap(); + match rdp_client.handle_tdp_sd_move_response(res) { + Ok(()) => CGOErrCode::ErrCodeSuccess, + Err(e) => { + error!("failed to handle Shared Directory Move Response: {:?}", e); + CGOErrCode::ErrCodeFailure + } + } +} + /// `read_rdp_output` reads incoming RDP bitmap frames from client at client_ref and forwards them to /// handle_bitmap. /// @@ -1641,6 +1726,24 @@ pub struct CGOSharedDirectoryListResponse { fso_list: *mut CGOFileSystemObject, } +/// SharedDirectoryMoveRequest is sent from the TDP server to the client +/// to request a file at original_path be moved to new_path. +#[derive(Debug)] +pub struct SharedDirectoryMoveRequest { + completion_id: u32, + directory_id: u32, + original_path: UnixPath, + new_path: UnixPath, +} + +#[repr(C)] +pub struct CGOSharedDirectoryMoveRequest { + pub completion_id: u32, + pub directory_id: u32, + pub original_path: *const c_char, + pub new_path: *const c_char, +} + pub type CGOSharedDirectoryCreateResponse = SharedDirectoryCreateResponse; /// SharedDirectoryDeleteRequest is sent by the TDP server to the client /// to request the deletion of a file or directory at path. @@ -1654,6 +1757,10 @@ pub type CGOSharedDirectoryDeleteResponse = SharedDirectoryCreateResponse; /// to request the contents of a directory. pub type SharedDirectoryListRequest = SharedDirectoryInfoRequest; pub type CGOSharedDirectoryListRequest = CGOSharedDirectoryInfoRequest; +/// SharedDirectoryMoveResponse is sent by the TDP client to the server +/// to acknowledge a SharedDirectoryMoveRequest was received and executed. +pub type SharedDirectoryMoveResponse = SharedDirectoryCreateResponse; +pub type CGOSharedDirectoryMoveResponse = CGOSharedDirectoryCreateResponse; // These functions are defined on the Go side. Look for functions with '//export funcname' // comments. @@ -1687,6 +1794,10 @@ extern "C" { client_ref: usize, req: *mut CGOSharedDirectoryWriteRequest, ) -> CGOErrCode; + fn tdp_sd_move_request( + client_ref: usize, + req: *mut CGOSharedDirectoryMoveRequest, + ) -> CGOErrCode; } /// Payload is a generic type used to represent raw incoming RDP messages for parsing. diff --git a/lib/srv/desktop/rdp/rdpclient/src/rdpdr/consts.rs b/lib/srv/desktop/rdp/rdpclient/src/rdpdr/consts.rs index 0a3eb699b94..379cc445b2d 100644 --- a/lib/srv/desktop/rdp/rdpclient/src/rdpdr/consts.rs +++ b/lib/srv/desktop/rdp/rdpclient/src/rdpdr/consts.rs @@ -124,7 +124,7 @@ pub enum NTSTATUS { /// 2.4 File Information Classes [MS-FSCC] /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/4718fc40-e539-4014-8e33-b675af74e3e1 -#[derive(FromPrimitive, Debug, PartialEq)] +#[derive(FromPrimitive, Debug, PartialEq, Clone)] #[repr(u32)] #[allow(clippy::enum_variant_names)] pub enum FileInformationClassLevel { diff --git a/lib/srv/desktop/rdp/rdpclient/src/rdpdr/mod.rs b/lib/srv/desktop/rdp/rdpclient/src/rdpdr/mod.rs index 20db61cf703..6823086c5a9 100644 --- a/lib/srv/desktop/rdp/rdpclient/src/rdpdr/mod.rs +++ b/lib/srv/desktop/rdp/rdpclient/src/rdpdr/mod.rs @@ -28,8 +28,9 @@ use crate::{ FileSystemObject, FileType, Payload, SharedDirectoryAcknowledge, SharedDirectoryCreateRequest, SharedDirectoryCreateResponse, SharedDirectoryDeleteRequest, SharedDirectoryDeleteResponse, SharedDirectoryInfoRequest, SharedDirectoryInfoResponse, SharedDirectoryListRequest, - SharedDirectoryListResponse, SharedDirectoryReadRequest, SharedDirectoryReadResponse, - SharedDirectoryWriteRequest, SharedDirectoryWriteResponse, TdpErrCode, + SharedDirectoryListResponse, SharedDirectoryMoveRequest, SharedDirectoryMoveResponse, + SharedDirectoryReadRequest, SharedDirectoryReadResponse, SharedDirectoryWriteRequest, + SharedDirectoryWriteResponse, TdpErrCode, }; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; @@ -78,6 +79,7 @@ pub struct Client { tdp_sd_list_request: SharedDirectoryListRequestSender, tdp_sd_read_request: SharedDirectoryReadRequestSender, tdp_sd_write_request: SharedDirectoryWriteRequestSender, + tdp_sd_move_request: SharedDirectoryMoveRequestSender, // CompletionId-indexed maps of handlers for tdp messages coming from the browser client. pending_sd_info_resp_handlers: HashMap, @@ -86,6 +88,7 @@ pub struct Client { pending_sd_list_resp_handlers: HashMap, pending_sd_read_resp_handlers: HashMap, pending_sd_write_resp_handlers: HashMap, + pending_sd_move_resp_handlers: HashMap, } pub struct Config { @@ -101,6 +104,7 @@ pub struct Config { pub tdp_sd_list_request: SharedDirectoryListRequestSender, pub tdp_sd_read_request: SharedDirectoryReadRequestSender, pub tdp_sd_write_request: SharedDirectoryWriteRequestSender, + pub tdp_sd_move_request: SharedDirectoryMoveRequestSender, } impl Client { @@ -126,6 +130,7 @@ impl Client { tdp_sd_list_request: cfg.tdp_sd_list_request, tdp_sd_read_request: cfg.tdp_sd_read_request, tdp_sd_write_request: cfg.tdp_sd_write_request, + tdp_sd_move_request: cfg.tdp_sd_move_request, pending_sd_info_resp_handlers: HashMap::new(), pending_sd_create_resp_handlers: HashMap::new(), @@ -133,6 +138,7 @@ impl Client { pending_sd_list_resp_handlers: HashMap::new(), pending_sd_read_resp_handlers: HashMap::new(), pending_sd_write_resp_handlers: HashMap::new(), + pending_sd_move_resp_handlers: HashMap::new(), } } /// Reads raw RDP messages sent on the rdpdr virtual channel and replies as necessary. @@ -695,46 +701,44 @@ impl Client { let rdp_req = ServerDriveQueryVolumeInformationRequest::decode(device_io_request, payload)?; debug!("received RDP: {:?}", rdp_req); if let Some(dir) = self.file_cache.get(rdp_req.device_io_request.file_id) { - // TODO(isaiah): we should support all of the fs_info_class_lvls that FreeRDP does: - // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_main.c#L468 - match rdp_req.fs_info_class_lvl { + let buffer = match rdp_req.fs_info_class_lvl { FileSystemInformationClassLevel::FileFsVolumeInformation => { - let buffer = Some(FileSystemInformationClass::FileFsVolumeInformation( + Some(FileSystemInformationClass::FileFsVolumeInformation( FileFsVolumeInformation::new(dir.fso.last_modified as i64), - )); - return self.prep_query_vol_info_response( - &rdp_req.device_io_request, - NTSTATUS::STATUS_SUCCESS, - buffer, - ); + )) } FileSystemInformationClassLevel::FileFsAttributeInformation => { - let buffer = Some(FileSystemInformationClass::FileFsAttributeInformation( + Some(FileSystemInformationClass::FileFsAttributeInformation( FileFsAttributeInformation::new(), - )); - return self.prep_query_vol_info_response( - &rdp_req.device_io_request, - NTSTATUS::STATUS_SUCCESS, - buffer, - ); + )) } - FileSystemInformationClassLevel::FileFsSizeInformation - | FileSystemInformationClassLevel::FileFsFullSizeInformation - | FileSystemInformationClassLevel::FileFsDeviceInformation => { - return Err(not_implemented_error(&format!( - "support for ServerDriveQueryVolumeInformationRequest with fs_info_class_lvl = {:?} is not implemented", - rdp_req.fs_info_class_lvl - ))); + FileSystemInformationClassLevel::FileFsFullSizeInformation => { + Some(FileSystemInformationClass::FileFsFullSizeInformation( + FileFsFullSizeInformation::new(), + )) } - _ => { - // https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_main.c#L574-L577 - return self.prep_query_vol_info_response( - &rdp_req.device_io_request, - NTSTATUS::STATUS_UNSUCCESSFUL, - None, - ); + FileSystemInformationClassLevel::FileFsDeviceInformation => { + Some(FileSystemInformationClass::FileFsDeviceInformation( + FileFsDeviceInformation::new(), + )) } - } + FileSystemInformationClassLevel::FileFsSizeInformation => Some( + FileSystemInformationClass::FileFsSizeInformation(FileFsSizeInformation::new()), + ), + _ => None, + }; + + let io_status = if buffer.is_some() { + NTSTATUS::STATUS_SUCCESS + } else { + NTSTATUS::STATUS_UNSUCCESSFUL + }; + + return self.prep_query_vol_info_response( + &rdp_req.device_io_request, + io_status, + buffer, + ); } // File not found in cache @@ -765,6 +769,7 @@ impl Client { self.tdp_sd_write(rdp_req) } + #[allow(clippy::wildcard_in_or_patterns)] fn process_irp_set_information( &mut self, device_io_request: DeviceIoRequest, @@ -772,25 +777,33 @@ impl Client { ) -> RdpResult>> { let rdp_req = ServerDriveSetInformationRequest::decode(device_io_request, payload)?; - let resp = match rdp_req.file_information_class_level { + match rdp_req.file_information_class_level { + FileInformationClassLevel::FileRenameInformation => match rdp_req.set_buffer { + FileInformationClass::FileRenameInformation(ref rename_info) => { + self.rename(rdp_req.clone(), rename_info) + } + _ => Err(invalid_data_error( + "FileInformationClass does not match FileInformationClassLevel", + )), + }, FileInformationClassLevel::FileBasicInformation | FileInformationClassLevel::FileEndOfFileInformation - | FileInformationClassLevel::FileAllocationInformation - | FileInformationClassLevel::FileDispositionInformation => { - ClientDriveSetInformationResponse::new(&rdp_req, NTSTATUS::STATUS_SUCCESS) + | FileInformationClassLevel::FileAllocationInformation => { + // Each of these ask us to change something we don't have control over at the browser + // level, so we just do nothing and send back a success. + // https://github.com/FreeRDP/FreeRDP/blob/dfa231c0a55b005af775b833f92f6bcd30363d77/channels/drive/client/drive_file.c#L579 + self.prep_set_info_response(&rdp_req, NTSTATUS::STATUS_SUCCESS) } - _ => { - return Err(not_implemented_error(&format!( + + // TODO(isaiah) or TODO(lkozlowski): implement FileDispositionInformation as is the case in FreeRDP. + // Remove the #[allow(clippy::wildcard_in_or_patterns)] macro above this function once completed. + FileInformationClassLevel::FileDispositionInformation | _ => { + Err(not_implemented_error(&format!( "support for ServerDriveSetInformationRequest with fs_info_class_lvl = {:?} is not implemented", rdp_req.file_information_class_level - ))); + ))) } - }; - - debug!("sending RDP: {:?}", resp); - let resp = self - .add_headers_and_chunkify(PacketId::PAKID_CORE_DEVICE_IOCOMPLETION, resp.encode()?)?; - Ok(resp) + } } pub fn write_client_device_list_announce( @@ -955,6 +968,30 @@ impl Client { ))) } + pub fn handle_tdp_sd_move_response( + &mut self, + res: SharedDirectoryMoveResponse, + mcs: &mut mcs::Client, + ) -> RdpResult<()> { + debug!("received TDP SharedDirectoryMoveResponse: {:?}", res); + if let Some(tdp_resp_handler) = self + .pending_sd_move_resp_handlers + .remove(&res.completion_id) + { + let rdp_responses = tdp_resp_handler(self, res)?; + let chan = &CHANNEL_NAME.to_string(); + for resp in rdp_responses { + mcs.write(chan, resp)?; + } + return Ok(()); + } + + Err(try_error(&format!( + "received invalid completion id: {}", + res.completion_id + ))) + } + fn prep_device_create_response( &mut self, req: &DeviceCreateRequest, @@ -1134,6 +1171,18 @@ impl Client { Ok(resp) } + fn prep_set_info_response( + &mut self, + req: &ServerDriveSetInformationRequest, + io_status: NTSTATUS, + ) -> RdpResult>> { + let resp = ClientDriveSetInformationResponse::new(req, io_status); + debug!("sending RDP: {:?}", resp); + let resp = self + .add_headers_and_chunkify(PacketId::PAKID_CORE_DEVICE_IOCOMPLETION, resp.encode()?)?; + Ok(resp) + } + /// Helper function for sending a TDP SharedDirectoryCreateRequest based on an /// RDP DeviceCreateRequest and handling the TDP SharedDirectoryCreateResponse. fn tdp_sd_create( @@ -1316,6 +1365,97 @@ impl Client { self.prep_write_response(rdp_req.device_io_request, NTSTATUS::STATUS_UNSUCCESSFUL, 0) } + fn rename( + &mut self, + rdp_req: ServerDriveSetInformationRequest, + rename_info: &FileRenameInformation, + ) -> RdpResult>> { + // https://github.com/FreeRDP/FreeRDP/blob/dfa231c0a55b005af775b833f92f6bcd30363d77/channels/drive/client/drive_file.c#L709 + match rename_info.replace_if_exists { + Boolean::True => self.rename_replace_if_exists(rdp_req, rename_info), + Boolean::False => self.rename_dont_replace_if_exists(rdp_req, rename_info), + } + } + + fn rename_replace_if_exists( + &mut self, + rdp_req: ServerDriveSetInformationRequest, + rename_info: &FileRenameInformation, + ) -> RdpResult>> { + // If replace_if_exists is true, we can just send a TDP SharedDirectoryMoveRequest, + // which works like the unix `mv` utility (meaning it will automatically replace if exists). + self.tdp_sd_move(rdp_req, rename_info) + } + + fn rename_dont_replace_if_exists( + &mut self, + rdp_req: ServerDriveSetInformationRequest, + rename_info: &FileRenameInformation, + ) -> RdpResult>> { + let new_path = UnixPath::from(&rename_info.file_name); + // If replace_if_exists is false, first check if the new_path exists. + (self.tdp_sd_info_request)(SharedDirectoryInfoRequest { + completion_id: rdp_req.device_io_request.completion_id, + directory_id: rdp_req.device_io_request.device_id, + path: new_path, + })?; + + let rename_info = (*rename_info).clone(); + self.pending_sd_info_resp_handlers.insert( + rdp_req.device_io_request.completion_id, + Box::new( + move |cli: &mut Self, + res: SharedDirectoryInfoResponse| + -> RdpResult>> { + if res.err_code == TdpErrCode::DoesNotExist { + // If the file doesn't already exist, send a move request. + return cli.tdp_sd_move(rdp_req, &rename_info); + } + // If it does, send back a name collision error, as is done in FreeRDP. + cli.prep_set_info_response(&rdp_req, NTSTATUS::STATUS_OBJECT_NAME_COLLISION) + }, + ), + ); + + Ok(vec![]) + } + + fn tdp_sd_move( + &mut self, + rdp_req: ServerDriveSetInformationRequest, + rename_info: &FileRenameInformation, + ) -> RdpResult>> { + if let Some(file) = self.file_cache.get(rdp_req.device_io_request.file_id) { + (self.tdp_sd_move_request)(SharedDirectoryMoveRequest { + completion_id: rdp_req.device_io_request.completion_id, + directory_id: rdp_req.device_io_request.device_id, + original_path: file.path.clone(), + new_path: UnixPath::from(&rename_info.file_name), + })?; + + self.pending_sd_move_resp_handlers.insert( + rdp_req.device_io_request.completion_id, + Box::new( + move |cli: &mut Self, + res: SharedDirectoryMoveResponse| + -> RdpResult>> { + if res.err_code != TdpErrCode::Nil { + return cli + .prep_set_info_response(&rdp_req, NTSTATUS::STATUS_UNSUCCESSFUL); + } + + cli.prep_set_info_response(&rdp_req, NTSTATUS::STATUS_SUCCESS) + }, + ), + ); + + return Ok(vec![]); + } + + // File not found in cache + self.prep_set_info_response(&rdp_req, NTSTATUS::STATUS_UNSUCCESSFUL) + } + /// add_headers_and_chunkify takes an encoded PDU ready to be sent over a virtual channel (payload), /// adds on the Shared Header based the passed packet_id, adds the appropriate (virtual) Channel PDU Header, /// and splits the entire payload into chunks if the payload exceeds the maximum size. @@ -1371,7 +1511,7 @@ impl Client { /// | -------- | ------------- | ---------------------------------------------------------| /// | 3 | IRP_MJ_CLOSE | The FCO is deleted from the cache | /// | -------- | ------------- | ---------------------------------------------------------| -#[derive(Debug)] +#[derive(Debug, Clone)] struct FileCacheObject { path: UnixPath, delete_pending: bool, @@ -2236,9 +2376,8 @@ struct ServerDriveQueryInformationRequest { impl ServerDriveQueryInformationRequest { fn decode(device_io_request: DeviceIoRequest, payload: &mut Payload) -> RdpResult { - if let Some(file_info_class_lvl) = - FileInformationClassLevel::from_u32(payload.read_u32::()?) - { + let n = payload.read_u32::()?; + if let Some(file_info_class_lvl) = FileInformationClassLevel::from_u32(n) { return Ok(Self { device_io_request, file_info_class_lvl, @@ -2246,14 +2385,18 @@ impl ServerDriveQueryInformationRequest { } Err(invalid_data_error( - "received invalid FileInformationClass in ServerDriveQueryInformationRequest", + format!( + "received invalid FileInformationClass in ServerDriveQueryInformationRequest: {}", + n + ) + .as_str(), )) } } /// 2.4 File Information Classes [MS-FSCC] /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/4718fc40-e539-4014-8e33-b675af74e3e1 -#[derive(Debug)] +#[derive(Debug, Clone)] #[allow(dead_code, clippy::enum_variant_names)] enum FileInformationClass { FileBasicInformation(FileBasicInformation), @@ -2336,7 +2479,7 @@ impl FileInformationClass { /// 2.4.7 FileBasicInformation [MS-FSCC] /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/16023025-8a78-492f-8b96-c873b042ac50 -#[derive(Debug)] +#[derive(Debug, Clone)] struct FileBasicInformation { creation_time: i64, last_access_time: i64, @@ -2385,7 +2528,7 @@ impl FileBasicInformation { /// 2.4.41 FileStandardInformation [MS-FSCC] /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/5afa7f66-619c-48f3-955f-68c4ece704ae -#[derive(Debug)] +#[derive(Debug, Clone)] struct FileStandardInformation { /// A 64-bit signed integer that contains the file allocation size, in bytes. The value of this field MUST be an /// integer multiple of the cluster size. @@ -2437,7 +2580,7 @@ impl FileStandardInformation { /// 2.4.6 FileAttributeTagInformation [MS-FSCC] /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e?redirectedfrom=MSDN -#[derive(Debug)] +#[derive(Debug, Clone)] struct FileAttributeTagInformation { file_attributes: flags::FileAttributes, reparse_tag: u32, @@ -2460,7 +2603,7 @@ impl FileAttributeTagInformation { /// 2.1.8 Boolean /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/8ce7b38c-d3cc-415d-ab39-944000ea77ff -#[derive(Debug, FromPrimitive, ToPrimitive)] +#[derive(Debug, FromPrimitive, ToPrimitive, PartialEq, Clone)] #[repr(u8)] enum Boolean { True = 1, @@ -2469,7 +2612,7 @@ enum Boolean { /// 2.4.8 FileBothDirectoryInformation /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/270df317-9ba5-4ccb-ba00-8d22be139bc5 -#[derive(Debug)] +#[derive(Debug, Clone)] struct FileBothDirectoryInformation { next_entry_offset: u32, file_index: u32, @@ -2572,7 +2715,7 @@ impl FileBothDirectoryInformation { /// 2.4.14 FileFullDirectoryInformation /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/e8d926d1-3a22-4654-be9c-58317a85540b -#[derive(Debug)] +#[derive(Debug, Clone)] struct FileFullDirectoryInformation { next_entry_offset: u32, file_index: u32, @@ -2666,7 +2809,7 @@ impl FileFullDirectoryInformation { // 2.4.13 FileEndOfFileInformation // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/75241cca-3167-472f-8058-a52d77c6bb17 -#[derive(Debug)] +#[derive(Debug, Clone)] struct FileEndOfFileInformation { end_of_file: i64, } @@ -2692,7 +2835,7 @@ impl FileEndOfFileInformation { // 2.4.11 FileDispositionInformation // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/12c3dd1c-14f6-4229-9d29-75fb2cb392f6 -#[derive(Debug)] +#[derive(Debug, Clone)] struct FileDispositionInformation { delete_pending: u8, } @@ -2718,10 +2861,11 @@ impl FileDispositionInformation { // 2.4.37 FileRenameInformation // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/1d2673a8-8fb9-4868-920a-775ccaa30cf8 -#[derive(Debug)] +#[derive(Debug, Clone)] struct FileRenameInformation { replace_if_exists: Boolean, - file_name: String, + /// file_name is the relative path to the new location of the file + file_name: WindowsPath, } impl FileRenameInformation { @@ -2738,7 +2882,7 @@ impl FileRenameInformation { // RootDirectory. For network operations, this value MUST be zero. w.write_u8(0)?; w.write_u32::(self.file_name.len() as u32)?; - w.extend_from_slice(&util::to_unicode(&self.file_name, false)); + w.extend_from_slice(&util::to_unicode(&self.file_name.path, false)); Ok(w) } @@ -2750,7 +2894,7 @@ impl FileRenameInformation { let file_name_length = payload.read_u32::()?; let mut file_name = vec![0u8; file_name_length as usize]; payload.read_exact(&mut file_name)?; - let file_name = util::from_unicode(file_name)?; + let file_name = WindowsPath::from(util::from_unicode(file_name)?); Ok(Self { replace_if_exists: Boolean::from_u8(replace_if_exists).unwrap(), @@ -2765,7 +2909,7 @@ impl FileRenameInformation { // 2.4.4 FileAllocationInformation // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/0201c69b-50db-412d-bab3-dd97aeede13b -#[derive(Debug)] +#[derive(Debug, Clone)] struct FileAllocationInformation { allocation_size: i64, } @@ -3388,7 +3532,7 @@ impl ClientDriveSetInformationResponse { /// 2.2.3.3.9 Server Drive Set Information Request (DR_DRIVE_SET_INFORMATION_REQ) /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/b5d3104b-0e42-4cf8-9059-e9fe86615e5c -#[derive(Debug)] +#[derive(Debug, Clone)] struct ServerDriveSetInformationRequest { /// The MajorFunction field in the DR_DEVICE_IOREQUEST header MUST be set to IRP_MJ_SET_INFORMATION. device_io_request: DeviceIoRequest, @@ -3743,6 +3887,7 @@ type SharedDirectoryDeleteRequestSender = type SharedDirectoryListRequestSender = Box RdpResult<()>>; type SharedDirectoryReadRequestSender = Box RdpResult<()>>; type SharedDirectoryWriteRequestSender = Box RdpResult<()>>; +type SharedDirectoryMoveRequestSender = Box RdpResult<()>>; type SharedDirectoryInfoResponseHandler = Box RdpResult>>>; @@ -3756,6 +3901,8 @@ type SharedDirectoryReadResponseHandler = Box RdpResult>>>; type SharedDirectoryWriteResponseHandler = Box RdpResult>>>; +type SharedDirectoryMoveResponseHandler = + Box RdpResult>>>; #[cfg(test)] mod tests { diff --git a/lib/srv/desktop/rdp/rdpclient/src/rdpdr/path.rs b/lib/srv/desktop/rdp/rdpclient/src/rdpdr/path.rs index 2c512263df6..494e9ea67c2 100644 --- a/lib/srv/desktop/rdp/rdpclient/src/rdpdr/path.rs +++ b/lib/srv/desktop/rdp/rdpclient/src/rdpdr/path.rs @@ -26,7 +26,13 @@ use std::ffi::{CString, NulError}; /// r"2018\January.xlsx": A relative path to a file in a subdirectory of the current directory. #[derive(Debug, Clone)] pub struct WindowsPath { - path: String, + pub path: String, +} + +impl WindowsPath { + pub fn len(&self) -> u32 { + self.path.len() as u32 + } } impl From for WindowsPath { diff --git a/lib/srv/desktop/tdp/proto.go b/lib/srv/desktop/tdp/proto.go index c37b09bcaf8..cf83c04abcd 100644 --- a/lib/srv/desktop/tdp/proto.go +++ b/lib/srv/desktop/tdp/proto.go @@ -67,6 +67,8 @@ const ( TypeSharedDirectoryReadResponse = MessageType(20) TypeSharedDirectoryWriteRequest = MessageType(21) TypeSharedDirectoryWriteResponse = MessageType(22) + TypeSharedDirectoryMoveRequest = MessageType(23) + TypeSharedDirectoryMoveResponse = MessageType(24) TypeSharedDirectoryListRequest = MessageType(25) TypeSharedDirectoryListResponse = MessageType(26) ) @@ -149,6 +151,10 @@ func decode(in peekReader) (Message, error) { return decodeSharedDirectoryWriteRequest(in) case TypeSharedDirectoryWriteResponse: return decodeSharedDirectoryWriteResponse(in) + case TypeSharedDirectoryMoveRequest: + return decodeSharedDirectoryMoveRequest(in) + case TypeSharedDirectoryMoveResponse: + return decodeSharedDirectoryMoveResponse(in) default: return nil, trace.BadParameter("unsupported desktop protocol message type %d", t) } @@ -1120,7 +1126,6 @@ type SharedDirectoryReadRequest struct { CompletionID uint32 DirectoryID uint32 Path string - PathLength uint32 Offset uint64 Length uint32 } @@ -1149,7 +1154,7 @@ func decodeSharedDirectoryReadRequest(in peekReader) (SharedDirectoryReadRequest return SharedDirectoryReadRequest{}, trace.BadParameter("got message type %v, expected TypeSharedDirectoryReadRequest(%v)", t, TypeSharedDirectoryReadRequest) } - var completionID, directoryID, pathLength, length uint32 + var completionID, directoryID, length uint32 var offset uint64 err = binary.Read(in, binary.BigEndian, &completionID) @@ -1167,11 +1172,6 @@ func decodeSharedDirectoryReadRequest(in peekReader) (SharedDirectoryReadRequest return SharedDirectoryReadRequest{}, trace.Wrap(err) } - err = binary.Read(in, binary.BigEndian, &pathLength) - if err != nil { - return SharedDirectoryReadRequest{}, trace.Wrap(err) - } - err = binary.Read(in, binary.BigEndian, &offset) if err != nil { return SharedDirectoryReadRequest{}, trace.Wrap(err) @@ -1186,7 +1186,6 @@ func decodeSharedDirectoryReadRequest(in peekReader) (SharedDirectoryReadRequest CompletionID: completionID, DirectoryID: directoryID, Path: path, - PathLength: pathLength, Offset: offset, Length: length, }, nil @@ -1259,7 +1258,6 @@ type SharedDirectoryWriteRequest struct { DirectoryID uint32 Offset uint64 Path string - PathLength uint32 WriteDataLength uint32 WriteData []byte } @@ -1291,7 +1289,7 @@ func decodeSharedDirectoryWriteRequest(in peekReader) (SharedDirectoryWriteReque return SharedDirectoryWriteRequest{}, trace.BadParameter("got message type %v, expected TypeSharedDirectoryWriteRequest(%v)", t, TypeSharedDirectoryWriteRequest) } - var completionID, directoryID, pathLength, writeDataLength uint32 + var completionID, directoryID, writeDataLength uint32 var offset uint64 err = binary.Read(in, binary.BigEndian, &completionID) @@ -1314,11 +1312,6 @@ func decodeSharedDirectoryWriteRequest(in peekReader) (SharedDirectoryWriteReque return SharedDirectoryWriteRequest{}, trace.Wrap(err) } - err = binary.Read(in, binary.BigEndian, &pathLength) - if err != nil { - return SharedDirectoryWriteRequest{}, trace.Wrap(err) - } - err = binary.Read(in, binary.BigEndian, &writeDataLength) if err != nil { return SharedDirectoryWriteRequest{}, trace.Wrap(err) @@ -1333,7 +1326,6 @@ func decodeSharedDirectoryWriteRequest(in peekReader) (SharedDirectoryWriteReque CompletionID: completionID, DirectoryID: directoryID, Path: path, - PathLength: pathLength, Offset: offset, WriteDataLength: writeDataLength, WriteData: writeData, @@ -1370,6 +1362,88 @@ func decodeSharedDirectoryWriteResponse(in peekReader) (SharedDirectoryWriteResp return res, err } +// SharedDirectoryMoveRequest is sent from the TDP server to the client +// to request a file at original_path be moved to new_path. +type SharedDirectoryMoveRequest struct { + CompletionID uint32 + DirectoryID uint32 + OriginalPath string + NewPath string +} + +func (s SharedDirectoryMoveRequest) Encode() ([]byte, error) { + buf := new(bytes.Buffer) + buf.WriteByte(byte(TypeSharedDirectoryMoveRequest)) + binary.Write(buf, binary.BigEndian, s.CompletionID) + binary.Write(buf, binary.BigEndian, s.DirectoryID) + if err := encodeString(buf, s.OriginalPath); err != nil { + return nil, trace.Wrap(err) + } + if err := encodeString(buf, s.NewPath); err != nil { + return nil, trace.Wrap(err) + } + return buf.Bytes(), nil +} + +func decodeSharedDirectoryMoveRequest(in peekReader) (SharedDirectoryMoveRequest, error) { + t, err := in.ReadByte() + if err != nil { + return SharedDirectoryMoveRequest{}, trace.Wrap(err) + } + if t != byte(TypeSharedDirectoryMoveRequest) { + return SharedDirectoryMoveRequest{}, trace.BadParameter("got message type %v, expected TypeClientUsername(%v)", t, TypeClientUsername) + } + var completionID, directoryID uint32 + err = binary.Read(in, binary.BigEndian, &completionID) + if err != nil { + return SharedDirectoryMoveRequest{}, trace.Wrap(err) + } + err = binary.Read(in, binary.BigEndian, &directoryID) + if err != nil { + return SharedDirectoryMoveRequest{}, trace.Wrap(err) + } + originalPath, err := decodeString(in, windowsMaxUsernameLength) + if err != nil { + return SharedDirectoryMoveRequest{}, trace.Wrap(err) + } + newPath, err := decodeString(in, windowsMaxUsernameLength) + if err != nil { + return SharedDirectoryMoveRequest{}, trace.Wrap(err) + } + return SharedDirectoryMoveRequest{ + CompletionID: completionID, + DirectoryID: directoryID, + OriginalPath: originalPath, + NewPath: newPath, + }, nil +} + +type SharedDirectoryMoveResponse struct { + CompletionID uint32 + ErrCode uint32 +} + +func (s SharedDirectoryMoveResponse) Encode() ([]byte, error) { + buf := new(bytes.Buffer) + buf.WriteByte(byte(TypeSharedDirectoryMoveResponse)) + binary.Write(buf, binary.BigEndian, s) + return buf.Bytes(), nil +} + +func decodeSharedDirectoryMoveResponse(in peekReader) (SharedDirectoryMoveResponse, error) { + t, err := in.ReadByte() + if err != nil { + return SharedDirectoryMoveResponse{}, trace.Wrap(err) + } + if t != byte(TypeSharedDirectoryMoveResponse) { + return SharedDirectoryMoveResponse{}, trace.BadParameter("got message type %v, expected SharedDirectoryMoveResponse(%v)", t, TypeSharedDirectoryMoveResponse) + } + + var res SharedDirectoryMoveResponse + err = binary.Read(in, binary.BigEndian, &res) + return res, err +} + // encodeString encodes strings for TDP. Strings are encoded as UTF-8 with // a 32-bit length prefix (in bytes): // https://github.com/gravitational/teleport/blob/master/rfd/0037-desktop-access-protocol.md#field-types