TDP Shared Directory Announce and Acknowledge (#12405)

Co-authored-by: Zac Bergquist <zmb3@users.noreply.github.com>
This commit is contained in:
Isaiah Becker-Mayer 2022-06-14 18:42:19 -04:00 committed by GitHub
parent aafe27c0cd
commit c018cd7deb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 1661 additions and 178 deletions

10
Cargo.lock generated
View file

@ -980,6 +980,7 @@ dependencies = [
"rand_chacha 0.3.1",
"rdp-rs",
"rsa",
"utf16string",
"uuid",
]
@ -1324,6 +1325,15 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "utf16string"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b62a1e85e12d5d712bf47a85f426b73d303e2d00a90de5f3004df3596e9d216"
dependencies = [
"byteorder",
]
[[package]]
name = "uuid"
version = "1.1.2"

View file

@ -22,3 +22,5 @@ rand_chacha = "0.3.1"
rsa = "0.6.1"
rdp-rs = { git = "https://github.com/gravitational/rdp-rs", rev = "17ec446ecb73c58b77ac47c6fc8598153f673076" }
uuid = { version = "1.1.2", features = ["v4"] }
utf16string = "0.2.0"

View file

@ -369,6 +369,18 @@ func (c *Client) start() {
} else {
c.cfg.Log.Warning("Recieved an empty clipboard message")
}
case tdp.SharedDirectoryAnnounce:
if c.cfg.AllowDirectorySharing {
driveName := C.CString(m.Name)
defer C.free(unsafe.Pointer(driveName))
if err := C.handle_tdp_sd_announce(c.rustClient, C.CGOSharedDirectoryAnnounce{
directory_id: C.uint32_t(m.DirectoryID),
name: driveName,
}); err != C.ErrCodeSuccess {
c.cfg.Log.Errorf("Device announce failed: %v", err)
return
}
}
default:
c.cfg.Log.Warningf("Skipping unimplemented TDP message type %T", msg)
}
@ -434,6 +446,25 @@ func (c *Client) handleRemoteCopy(data []byte) C.CGOErrCode {
return C.ErrCodeSuccess
}
//export tdp_sd_acknowledge
func tdp_sd_acknowledge(handle C.uintptr_t, ack *C.CGOSharedDirectoryAcknowledge) C.CGOErrCode {
return cgo.Handle(handle).Value().(*Client).sharedDirectoryAcknowledge(tdp.SharedDirectoryAcknowledge{
Err: uint32(ack.err),
DirectoryID: uint32(ack.directory_id),
})
}
// 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 {
c.cfg.Log.Errorf("failed to send SharedDirectoryAcknowledge: %v", err)
return C.ErrCodeFailure
}
}
return C.ErrCodeSuccess
}
// Wait blocks until the client disconnects and runs the cleanup.
func (c *Client) Wait() error {
c.wg.Wait()

View file

@ -7,18 +7,20 @@
#define SPECIAL_NO_RESPONSE 4294967295
#define SCARD_DEVICE_ID 1
#define VERSION_MAJOR 1
#define VERSION_MINOR 12
#define SMARTCARD_CAPABILITY_VERSION_01 1
#define DRIVE_CAPABILITY_VERSION_02 2
#define GENERAL_CAPABILITY_VERSION_01 1
#define GENERAL_CAPABILITY_VERSION_02 2
#define SCARD_DEVICE_ID 1
/**
* The default maximum chunk size for virtual channel data.
*
@ -68,6 +70,11 @@ typedef struct ClientOrError {
enum CGOErrCode err;
} ClientOrError;
typedef struct CGOSharedDirectoryAnnounce {
uint32_t directory_id;
char *name;
} CGOSharedDirectoryAnnounce;
/**
* CGOMousePointerEvent is a CGO-compatible version of PointerEvent that we pass back to Go.
* PointerEvent is a mouse move or click update from the user.
@ -107,6 +114,15 @@ typedef struct CGOBitmap {
uintptr_t data_cap;
} CGOBitmap;
/**
* CGOSharedDirectoryAcknowledge is a CGO-compatible version of
* the TDP Shared Directory Knowledge message that we pass back to Go.
*/
typedef struct CGOSharedDirectoryAcknowledge {
uint32_t err;
uint32_t directory_id;
} CGOSharedDirectoryAcknowledge;
void init(void);
/**
@ -141,6 +157,17 @@ struct ClientOrError connect_rdp(uintptr_t go_ref,
*/
enum CGOErrCode update_clipboard(struct Client *client_ptr, uint8_t *data, uint32_t len);
/**
* handle_tdp_sd_announce announces a new drive that's ready to be
* redirected over RDP.
*
* # Safety
*
* The caller must ensure that sd_announce.name points to a valid buffer.
*/
enum CGOErrCode handle_tdp_sd_announce(struct Client *client_ptr,
struct CGOSharedDirectoryAnnounce sd_announce);
/**
* `read_rdp_output` reads incoming RDP bitmap frames from client at client_ref and forwards them to
* handle_bitmap.
@ -192,3 +219,9 @@ void free_rust_string(char *s);
extern enum CGOErrCode handle_bitmap(uintptr_t client_ref, struct CGOBitmap *b);
extern enum CGOErrCode handle_remote_copy(uintptr_t client_ref, uint8_t *data, uint32_t len);
/**
* Shared Directory Acknowledge
*/
extern enum CGOErrCode tdp_sd_acknowledge(uintptr_t client_ref,
struct CGOSharedDirectoryAcknowledge *ack);

View file

@ -564,7 +564,7 @@ fn encode_clipboard(mut data: String) -> (Vec<u8>, ClipboardFormat) {
(data.into_bytes(), ClipboardFormat::CF_TEXT)
} else {
let encoded = util::to_nul_terminated_utf16le(&data);
let encoded = util::to_unicode(&data, true);
(encoded, ClipboardFormat::CF_UNICODETEXT)
}
}
@ -676,7 +676,7 @@ impl FormatName for LongFormatName {
// must be encoded as a single Unicode null character (two zero bytes)
None => w.write_u16::<LittleEndian>(0)?,
Some(name) => {
w.append(&mut util::to_nul_terminated_utf16le(name));
w.append(&mut util::to_unicode(name, true));
}
};
@ -1037,7 +1037,7 @@ mod tests {
#[test]
fn responds_to_format_data_request_hasdata() {
// a null-terminated utf-16 string, represented as a Vec<u8>
let test_data = util::to_nul_terminated_utf16le("test");
let test_data = util::to_unicode("test", true);
let mut c: Client = Default::default();
c.clipboard

View file

@ -19,10 +19,18 @@ pub fn invalid_data_error(msg: &str) -> Error {
Error::RdpError(RdpError::new(RdpErrorKind::InvalidData, msg))
}
pub fn not_implemented_error(msg: &str) -> Error {
Error::RdpError(RdpError::new(RdpErrorKind::NotImplemented, msg))
}
pub fn try_error(msg: &str) -> Error {
Error::TryError(msg.to_string())
}
pub fn rejected_by_server_error(msg: &str) -> Error {
Error::RdpError(RdpError::new(RdpErrorKind::RejectedByServer, msg))
}
// NTSTATUS_OK is a Windows NTStatus value that means "success".
pub const NTSTATUS_OK: u32 = 0;
// SPECIAL_NO_RESPONSE is our custom (not defined by Windows) NTStatus value that means "don't send

View file

@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
pub mod cliprdr;
pub mod errors;
pub mod piv;
pub mod rdpdr;
pub mod util;
pub mod vchan;
mod cliprdr;
mod errors;
mod piv;
mod rdpdr;
mod util;
mod vchan;
#[macro_use]
extern crate log;
@ -251,12 +251,27 @@ fn connect_rdp_inner(
KeyboardLayout::US,
"rdp-rs",
);
// Client for the "rdpdr" channel - smartcard emulation.
let tdp_sd_acknowledge = Box::new(move |ack: SharedDirectoryAcknowledge| -> RdpResult<()> {
unsafe {
if tdp_sd_acknowledge(go_ref, &mut CGOSharedDirectoryAcknowledge::from(ack))
!= CGOErrCode::ErrCodeSuccess
{
return Err(RdpError::TryError(String::from(
"call to tdp_sd_acknowledge failed",
)));
}
}
Ok(())
});
// Client for the "rdpdr" channel - smartcard emulation and drive redirection.
let rdpdr = rdpdr::Client::new(
params.cert_der,
params.key_der,
pin,
params.allow_directory_sharing,
tdp_sd_acknowledge,
);
// Client for the "cliprdr" channel - clipboard sharing.
@ -336,6 +351,14 @@ impl<S: Read + Write> RdpClient<S> {
}
}
pub fn write_client_device_list_announce(
&mut self,
req: rdpdr::ClientDeviceListAnnounce,
) -> RdpResult<()> {
self.rdpdr
.write_client_device_list_announce(req, &mut self.mcs)
}
pub fn shutdown(&mut self) -> RdpResult<()> {
self.mcs.shutdown()
}
@ -458,6 +481,38 @@ pub unsafe extern "C" fn update_clipboard(
}
}
/// handle_tdp_sd_announce announces a new drive that's ready to be
/// redirected over RDP.
///
/// # Safety
///
/// The caller must ensure that sd_announce.name points to a valid buffer.
#[no_mangle]
pub unsafe extern "C" fn handle_tdp_sd_announce(
client_ptr: *mut Client,
sd_announce: CGOSharedDirectoryAnnounce,
) -> CGOErrCode {
let client = match Client::from_ptr(client_ptr) {
Ok(client) => client,
Err(cgo_error) => {
return cgo_error;
}
};
let drive_name = from_go_string(sd_announce.name);
let new_drive =
rdpdr::ClientDeviceListAnnounce::new_drive(sd_announce.directory_id, drive_name);
let mut rdp_client = client.rdp_client.lock().unwrap();
match rdp_client.write_client_device_list_announce(new_drive) {
Ok(()) => CGOErrCode::ErrCodeSuccess,
Err(e) => {
error!("failed to announce new drive: {:?}", e);
CGOErrCode::ErrCodeFailure
}
}
}
/// `read_rdp_output` reads incoming RDP bitmap frames from client at client_ref and forwards them to
/// handle_bitmap.
///
@ -521,7 +576,7 @@ fn read_rdp_output_inner(client: &Client) -> Option<String> {
match res {
Err(RdpError::Io(io_err)) if io_err.kind() == ErrorKind::UnexpectedEof => return None,
Err(e) => {
return Some(format!("failed forwarding RDP bitmap frame: {:?}", e));
return Some(format!("RDP read failed: {:?}", e));
}
_ => {}
}
@ -700,6 +755,8 @@ pub unsafe extern "C" fn free_rust_string(s: *mut c_char) {
/// # Safety
///
/// s must be a C-style null terminated string.
/// s is copied here, and the caller is responsible for ensuring
/// that the original memory is freed
unsafe fn from_go_string(s: *mut c_char) -> String {
CStr::from_ptr(s).to_string_lossy().into_owned()
}
@ -718,11 +775,43 @@ pub enum CGOErrCode {
ErrCodeFailure = 1,
}
#[repr(C)]
pub struct CGOSharedDirectoryAnnounce {
pub directory_id: u32,
pub name: *mut c_char,
}
pub struct SharedDirectoryAcknowledge {
pub err: u32,
pub directory_id: u32,
}
/// CGOSharedDirectoryAcknowledge is a CGO-compatible version of
/// the TDP Shared Directory Knowledge message that we pass back to Go.
#[repr(C)]
pub struct CGOSharedDirectoryAcknowledge {
pub err: u32,
pub directory_id: u32,
}
impl From<SharedDirectoryAcknowledge> for CGOSharedDirectoryAcknowledge {
fn from(ack: SharedDirectoryAcknowledge) -> CGOSharedDirectoryAcknowledge {
CGOSharedDirectoryAcknowledge {
err: ack.err,
directory_id: ack.directory_id,
}
}
}
// These functions are defined on the Go side. Look for functions with '//export funcname'
// comments.
extern "C" {
fn handle_bitmap(client_ref: usize, b: *mut CGOBitmap) -> CGOErrCode;
fn handle_remote_copy(client_ref: usize, data: *mut u8, len: u32) -> CGOErrCode;
/// Shared Directory Acknowledge
fn tdp_sd_acknowledge(client_ref: usize, ack: *mut CGOSharedDirectoryAcknowledge)
-> CGOErrCode;
}
/// Payload is a generic type used to represent raw incoming RDP messages for parsing.

View file

@ -14,6 +14,19 @@
pub const CHANNEL_NAME: &str = "rdpdr";
// Each redirected device requires a unique ID. We only share
// one permanent smartcard device, so we can give it hardcoded ID 1.
pub const SCARD_DEVICE_ID: u32 = 1;
pub const VERSION_MAJOR: u16 = 0x0001;
pub const VERSION_MINOR: u16 = 0x000c;
pub const SMARTCARD_CAPABILITY_VERSION_01: u32 = 0x00000001;
pub const DRIVE_CAPABILITY_VERSION_02: u32 = 0x00000002;
#[allow(dead_code)]
pub const GENERAL_CAPABILITY_VERSION_01: u32 = 0x00000001;
pub const GENERAL_CAPABILITY_VERSION_02: u32 = 0x00000002;
#[derive(Debug, FromPrimitive, ToPrimitive)]
#[allow(non_camel_case_types)]
pub enum Component {
@ -39,14 +52,6 @@ pub enum PacketId {
PAKID_PRN_USING_XPS = 0x5543,
}
pub const VERSION_MAJOR: u16 = 0x0001;
pub const VERSION_MINOR: u16 = 0x000c;
pub const SMARTCARD_CAPABILITY_VERSION_01: u32 = 0x00000001;
#[allow(dead_code)]
pub const GENERAL_CAPABILITY_VERSION_01: u32 = 0x00000001;
pub const GENERAL_CAPABILITY_VERSION_02: u32 = 0x00000002;
#[derive(Debug, FromPrimitive, ToPrimitive)]
#[allow(non_camel_case_types)]
pub enum CapabilityType {
@ -57,10 +62,6 @@ pub enum CapabilityType {
CAP_SMARTCARD_TYPE = 0x0005,
}
// If there were multiple redirected devices, they would need unique IDs. In our case there is only
// one permanent smartcard device, so we hardcode an ID 1.
pub const SCARD_DEVICE_ID: u32 = 1;
#[derive(Debug, FromPrimitive, ToPrimitive)]
#[allow(non_camel_case_types)]
pub enum DeviceType {
@ -71,7 +72,8 @@ pub enum DeviceType {
RDPDR_DTYP_SMARTCARD = 0x00000020,
}
#[derive(Debug, FromPrimitive, ToPrimitive)]
/// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/a087ffa8-d0d5-4874-ac7b-0494f63e2d5d
#[derive(Debug, FromPrimitive, ToPrimitive, PartialEq, Clone)]
#[allow(non_camel_case_types)]
pub enum MajorFunction {
IRP_MJ_CREATE = 0x00000000,
@ -94,3 +96,72 @@ pub enum MinorFunction {
IRP_MN_QUERY_DIRECTORY = 0x00000001,
IRP_MN_NOTIFY_CHANGE_DIRECTORY = 0x00000002,
}
/// Windows defines an absolutely massive list of potential NTSTATUS values.
/// This enum includes the basic ones we support for communicating with the windows machine.
/// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55
#[derive(ToPrimitive, Debug)]
#[repr(u32)]
#[allow(non_camel_case_types)]
#[allow(dead_code)]
pub enum NTSTATUS {
STATUS_SUCCESS = 0x00000000,
STATUS_UNSUCCESSFUL = 0xC0000001,
STATUS_NOT_IMPLEMENTED = 0xC0000002,
STATUS_NO_MORE_FILES = 0x80000006,
}
/// 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)]
#[repr(u32)]
#[allow(clippy::enum_variant_names)]
pub enum FsInformationClassLevel {
FileAccessInformation = 8,
FileAlignmentInformation = 17,
FileAllInformation = 18,
FileAllocationInformation = 19,
FileAlternateNameInformation = 21,
FileAttributeTagInformation = 35,
FileBasicInformation = 4,
FileBothDirectoryInformation = 3,
FileCompressionInformation = 28,
FileDirectoryInformation = 1,
FileDispositionInformation = 13,
FileEaInformation = 7,
FileEndOfFileInformation = 20,
FileFullDirectoryInformation = 2,
FileFullEaInformation = 15,
FileHardLinkInformation = 46,
FileIdBothDirectoryInformation = 37,
FileIdExtdDirectoryInformation = 60,
FileIdFullDirectoryInformation = 38,
FileIdGlobalTxDirectoryInformation = 50,
FileIdInformation = 59,
FileInternalInformation = 6,
FileLinkInformation = 11,
FileMailslo = 26,
FileMailslotSetInformation = 27,
FileModeInformation = 16,
FileMoveClusterInformation = 31,
FileNameInformation = 9,
FileNamesInformation = 12,
FileNetworkOpenInformation = 34,
FileNormalizedNameInformation = 48,
FileObjectIdInformation = 29,
FilePipeInformation = 23,
FilePipInformation = 24,
FilePipeRemoteInformation = 25,
FilePositionInformation = 14,
FileQuotaInformation = 32,
FileRenameInformation = 10,
FileReparsePointInformation = 33,
FileSfioReserveInformation = 44,
FileSfioVolumeInformation = 45,
FileShortNameInformation = 40,
FileStandardInformation = 5,
FileStandardLinkInformation = 54,
FileStreamInformation = 22,
FileTrackingInformation = 36,
FileValidDataLengthInformation = 39,
}

View file

@ -0,0 +1,200 @@
// Copyright 2022 Gravitational, Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use bitflags::bitflags;
bitflags! {
/// DesiredAccess can be interpreted as either
/// 2.2.13.1.1 File_Pipe_Printer_Access_Mask [MS-SMB2] (https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/77b36d0f-6016-458a-a7a0-0f4a72ae1534)
/// or
/// 2.2.13.1.2 Directory_Access_Mask [MS-SMB2] (https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/0a5934b1-80f1-4da0-b1bf-5e021c309b71)
///
/// This implements the combination of the two. For flags where the names and/or functions are distinct between the two,
/// the names are appended with an "_OR_", and the File_Pipe_Printer_Access_Mask functionality is described on the top line comment,
/// and the Directory_Access_Mask functionality is described on the bottom (2nd) line comment.
pub struct DesiredAccess: u32 {
/// This value indicates the right to read data from the file or named pipe.
/// This value indicates the right to enumerate the contents of the directory.
const FILE_READ_DATA_OR_FILE_LIST_DIRECTORY = 0x00000001;
/// This value indicates the right to write data into the file or named pipe beyond the end of the file.
/// This value indicates the right to create a file under the directory.
const FILE_WRITE_DATA_OR_FILE_ADD_FILE = 0x00000002;
/// This value indicates the right to append data into the file or named pipe.
/// This value indicates the right to add a sub-directory under the directory.
const FILE_APPEND_DATA_OR_FILE_ADD_SUBDIRECTORY = 0x00000004;
/// This value indicates the right to read the extended attributes of the file or named pipe.
const FILE_READ_EA = 0x00000008;
/// This value indicates the right to write or change the extended attributes to the file or named pipe.
const FILE_WRITE_EA = 0x00000010;
/// This value indicates the right to traverse this directory if the server enforces traversal checking.
const FILE_TRAVERSE = 0x00000020;
/// This value indicates the right to delete entries within a directory.
const FILE_DELETE_CHILD = 0x00000040;
/// This value indicates the right to execute the file/directory.
const FILE_EXECUTE = 0x00000020;
/// This value indicates the right to read the attributes of the file/directory.
const FILE_READ_ATTRIBUTES = 0x00000080;
/// This value indicates the right to change the attributes of the file/directory.
const FILE_WRITE_ATTRIBUTES = 0x00000100;
/// This value indicates the right to delete the file/directory.
const DELETE = 0x00010000;
/// This value indicates the right to read the security descriptor for the file/directory or named pipe.
const READ_CONTROL = 0x00020000;
/// This value indicates the right to change the discretionary access control list (DACL) in the security descriptor for the file/directory or named pipe. For the DACL data pub structure, see ACL in [MS-DTYP].
const WRITE_DAC = 0x00040000;
/// This value indicates the right to change the owner in the security descriptor for the file/directory or named pipe.
const WRITE_OWNER = 0x00080000;
/// SMB2 clients set this flag to any value. SMB2 servers SHOULD ignore this flag.
const SYNCHRONIZE = 0x00100000;
/// This value indicates the right to read or change the system access control list (SACL) in the security descriptor for the file/directory or named pipe. For the SACL data pub structure, see ACL in [MS-DTYP].
const ACCESS_SYSTEM_SECURITY = 0x01000000;
/// This value indicates that the client is requesting an open to the file with the highest level of access the client has on this file. If no access is granted for the client on this file, the server MUST fail the open with STATUS_ACCESS_DENIED.
const MAXIMUM_ALLOWED = 0x02000000;
/// This value indicates a request for all the access flags that are previously listed except MAXIMUM_ALLOWED and ACCESS_SYSTEM_SECURITY.
const GENERIC_ALL = 0x10000000;
/// This value indicates a request for the following combination of access flags listed above: FILE_READ_ATTRIBUTES| FILE_EXECUTE| SYNCHRONIZE| READ_CONTROL.
const GENERIC_EXECUTE = 0x20000000;
/// This value indicates a request for the following combination of access flags listed above: FILE_WRITE_DATA| FILE_APPEND_DATA| FILE_WRITE_ATTRIBUTES| FILE_WRITE_EA| SYNCHRONIZE| READ_CONTROL.
const GENERIC_WRITE = 0x40000000;
/// This value indicates a request for the following combination of access flags listed above: FILE_READ_DATA| FILE_READ_ATTRIBUTES| FILE_READ_EA| SYNCHRONIZE| READ_CONTROL.
const GENERIC_READ = 0x80000000;
}
}
bitflags! {
/// 2.6 File Attributes [MS-FSCC]
/// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/ca28ec38-f155-4768-81d6-4bfeb8586fc9
pub struct FileAttributes: u32 {
const FILE_ATTRIBUTE_READONLY = 0x00000001;
const FILE_ATTRIBUTE_HIDDEN = 0x00000002;
const FILE_ATTRIBUTE_SYSTEM = 0x00000004;
const FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
const FILE_ATTRIBUTE_ARCHIVE = 0x00000020;
const FILE_ATTRIBUTE_NORMAL = 0x00000080;
const FILE_ATTRIBUTE_TEMPORARY = 0x00000100;
const FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200;
const FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400;
const FILE_ATTRIBUTE_COMPRESSED = 0x00000800;
const FILE_ATTRIBUTE_OFFLINE = 0x00001000;
const FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000;
const FILE_ATTRIBUTE_ENCRYPTED = 0x00004000;
const FILE_ATTRIBUTE_INTEGRITY_STREAM = 0x00008000;
const FILE_ATTRIBUTE_NO_SCRUB_DATA = 0x00020000;
const FILE_ATTRIBUTE_RECALL_ON_OPEN = 0x00040000;
const FILE_ATTRIBUTE_PINNED = 0x00080000;
const FILE_ATTRIBUTE_UNPINNED = 0x00100000;
const FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS = 0x00400000;
}
}
bitflags! {
/// Specifies the sharing mode for the open. If ShareAccess values of FILE_SHARE_READ, FILE_SHARE_WRITE and FILE_SHARE_DELETE are set for a printer file or a named pipe, the server SHOULD<35> ignore these values. The field MUST be pub constructed using a combination of zero or more of the following bit values.
/// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/e8fb45c1-a03d-44ca-b7ae-47385cfd7997
pub struct SharedAccess: u32 {
const FILE_SHARE_READ = 0x00000001;
const FILE_SHARE_WRITE = 0x00000002;
const FILE_SHARE_DELETE = 0x00000004;
}
}
bitflags! {
/// Defines the action the server MUST take if the file that is specified in the name field already exists. For opening named pipes, this field can be set to any value by the client and MUST be ignored by the server. For other files, this field MUST contain one of the following values.
/// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/e8fb45c1-a03d-44ca-b7ae-47385cfd7997
/// See https://github.com/FreeRDP/FreeRDP/blob/511444a65e7aa2f537c5e531fa68157a50c1bd4d/channels/drive/client/drive_file.c#L207
/// for information about how these should be interpreted.
pub struct CreateDisposition: u32 {
const FILE_SUPERSEDE = 0x00000000;
const FILE_OPEN = 0x00000001;
const FILE_CREATE = 0x00000002;
const FILE_OPEN_IF = 0x00000003;
const FILE_OVERWRITE = 0x00000004;
const FILE_OVERWRITE_IF = 0x00000005;
}
}
bitflags! {
/// Specifies the options to be applied when creating or opening the file. Combinations of the bit positions listed below are valid, unless otherwise noted. This field MUST be pub constructed using the following values.
/// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/e8fb45c1-a03d-44ca-b7ae-47385cfd7997
pub struct CreateOptions: u32 {
const FILE_DIRECTORY_FILE = 0x00000001;
const FILE_WRITE_THROUGH = 0x00000002;
const FILE_SEQUENTIAL_ONLY = 0x00000004;
const FILE_NO_INTERMEDIATE_BUFFERING = 0x00000008;
const FILE_SYNCHRONOUS_IO_ALERT = 0x00000010;
const FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020;
const FILE_NON_DIRECTORY_FILE = 0x00000040;
const FILE_COMPLETE_IF_OPLOCKED = 0x00000100;
const FILE_NO_EA_KNOWLEDGE = 0x00000200;
const FILE_RANDOM_ACCESS = 0x00000800;
const FILE_DELETE_ON_CLOSE = 0x00001000;
const FILE_OPEN_BY_FILE_ID = 0x00002000;
const FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000;
const FILE_NO_COMPRESSION = 0x00008000;
const FILE_OPEN_REMOTE_INSTANCE = 0x00000400;
const FILE_OPEN_REQUIRING_OPLOCK = 0x00010000;
const FILE_DISALLOW_EXCLUSIVE = 0x00020000;
const FILE_RESERVE_OPFILTER = 0x00100000;
const FILE_OPEN_REPARSE_POINT = 0x00200000;
const FILE_OPEN_NO_RECALL = 0x00400000;
const FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000;
}
}
bitflags! {
/// An unsigned 8-bit integer. This field indicates the success of the Device Create Request (section 2.2.1.4.1).
/// The value of the Information field depends on the value of CreateDisposition field in the Device Create Request
/// (section 2.2.1.4.1). If the IoStatus field is set to 0x00000000, this field MAY be skipped, in which case the
/// server MUST assume that the Information field is set to 0x00.
/// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/99e5fca5-b37a-41e4-bc69-8d7da7860f76
pub struct Information: u8 {
/// A new file was created.
const FILE_SUPERSEDED = 0x00000000;
/// An existing file was opened.
const FILE_OPENED = 0x00000001;
/// An existing file was overwritten.
const FILE_OVERWRITTEN = 0x00000003;
}
}
bitflags! {
/// Specifies the types of changes to monitor. It is valid to choose multiple trigger conditions.
/// In this case, if any condition is met, the client is notified of the change and the CHANGE_NOTIFY operation is completed.
/// See CompletionFilter at: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/598f395a-e7a2-4cc8-afb3-ccb30dd2df7c
pub struct CompletionFilter: u32 {
/// The client is notified if a file-name changes.
const FILE_NOTIFY_CHANGE_FILE_NAME = 0x00000001;
/// The client is notified if a directory name changes.
const FILE_NOTIFY_CHANGE_DIR_NAME = 0x00000002;
/// The client is notified if a file's attributes change. Possible file attribute values are specified in [MS-FSCC] section 2.6.
const FILE_NOTIFY_CHANGE_ATTRIBUTES = 0x00000004;
/// The client is notified if a file's size changes.
const FILE_NOTIFY_CHANGE_SIZE = 0x00000008;
/// The client is notified if the last write time of a file changes.
const FILE_NOTIFY_CHANGE_LAST_WRITE = 0x00000010;
/// The client is notified if the last access time of a file changes.
const FILE_NOTIFY_CHANGE_LAST_ACCESS = 0x00000020;
/// The client is notified if the creation time of a file changes.
const FILE_NOTIFY_CHANGE_CREATION = 0x00000040;
/// The client is notified if a file's extended attributes (EAs) change.
const FILE_NOTIFY_CHANGE_EA = 0x00000080;
/// The client is notified of a file's access control list (ACL) settings change.
const FILE_NOTIFY_CHANGE_SECURITY = 0x00000100;
/// The client is notified if a named stream is added to a file.
const FILE_NOTIFY_CHANGE_STREAM_NAME = 0x00000200;
/// The client is notified if the size of a named stream is changed.
const FILE_NOTIFY_CHANGE_STREAM_SIZE = 0x00000400;
/// The client is notified if a named stream is modified.
const FILE_NOTIFY_CHANGE_STREAM_WRITE = 0x00000800;
}
}

File diff suppressed because it is too large Load diff

View file

@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::errors::invalid_data_error;
use rdp::model::error::RdpResult;
use utf16string::{WString, LE};
/// According to [MS-RDPEFS] 1.1 Glossary:
/// Unless otherwise specified, all Unicode strings follow the UTF-16LE
/// encoding scheme with no Byte Order Mark (BOM).
@ -20,9 +24,48 @@
/// UTF-16LE encoded Vec<u8>, which is useful in cases where we want
/// to handle some data in the code as a &str (or String), and later
/// convert it to RDP's preferred format and send it over the wire.
pub fn to_nul_terminated_utf16le(s: &str) -> Vec<u8> {
s.encode_utf16()
.chain([0])
.flat_map(|v| v.to_le_bytes())
.collect()
pub fn to_unicode(s: &str, with_null_term: bool) -> Vec<u8> {
let mut buf = WString::<LE>::from(s).as_bytes().to_vec();
if with_null_term {
let mut null_terminator: Vec<u8> = vec![0, 0];
buf.append(&mut null_terminator);
}
buf
}
#[allow(clippy::bind_instead_of_map)]
pub fn from_unicode(s: Vec<u8>) -> RdpResult<String> {
let mut with_null_terminator = WString::from_utf16le(s)
.or_else(|_| Err(invalid_data_error("invalid Unicode")))?
.to_utf8();
with_null_terminator.pop();
let without_null_terminator = with_null_terminator;
Ok(without_null_terminator)
}
/// Converts a &str into a null-terminated UTF-8 encoded Vec<u8>
pub fn to_utf8(s: &str) -> Vec<u8> {
format!("{}\x00", s).into_bytes()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn to_and_from() {
let hello_vec = to_unicode("hello", true);
assert_eq!(
hello_vec,
vec![104, 0, 101, 0, 108, 0, 108, 0, 111, 0, 0, 0]
);
let hello_string = from_unicode(hello_vec).unwrap();
assert_eq!(hello_string, "hello");
}
#[test]
fn from_unicode_empty_vector() {
assert_eq!(from_unicode(vec![]).unwrap(), "");
}
}

View file

@ -45,16 +45,18 @@ type MessageType byte
// For descriptions of each message type see:
// https://github.com/gravitational/teleport/blob/master/rfd/0037-desktop-access-protocol.md#message-types
const (
TypeClientScreenSpec = MessageType(1)
TypePNGFrame = MessageType(2)
TypeMouseMove = MessageType(3)
TypeMouseButton = MessageType(4)
TypeKeyboardButton = MessageType(5)
TypeClipboardData = MessageType(6)
TypeClientUsername = MessageType(7)
TypeMouseWheel = MessageType(8)
TypeError = MessageType(9)
TypeMFA = MessageType(10)
TypeClientScreenSpec = MessageType(1)
TypePNGFrame = MessageType(2)
TypeMouseMove = MessageType(3)
TypeMouseButton = MessageType(4)
TypeKeyboardButton = MessageType(5)
TypeClipboardData = MessageType(6)
TypeClientUsername = MessageType(7)
TypeMouseWheel = MessageType(8)
TypeError = MessageType(9)
TypeMFA = MessageType(10)
TypeSharedDirectoryAnnounce = MessageType(11)
TypeSharedDirectoryAcknowledge = MessageType(12)
)
// Message is a Go representation of a desktop protocol message.
@ -107,6 +109,10 @@ func decode(in peekReader) (Message, error) {
return decodeError(in)
case TypeMFA:
return DecodeMFA(in)
case TypeSharedDirectoryAnnounce:
return decodeSharedDirectoryAnnounce(in)
case TypeSharedDirectoryAcknowledge:
return decodeSharedDirectoryAcknowledge(in)
default:
return nil, trace.BadParameter("unsupported desktop protocol message type %d", t)
}
@ -597,6 +603,76 @@ func DecodeMFAChallenge(in peekReader) (*MFA, error) {
}, nil
}
type SharedDirectoryAnnounce struct {
DirectoryID uint32
Name string
}
func (s SharedDirectoryAnnounce) Encode() ([]byte, error) {
buf := new(bytes.Buffer)
buf.WriteByte(byte(TypeSharedDirectoryAnnounce))
binary.Write(buf, binary.BigEndian, s.DirectoryID)
if err := encodeString(buf, s.Name); err != nil {
return nil, trace.Wrap(err)
}
return buf.Bytes(), nil
}
func decodeSharedDirectoryAnnounce(in peekReader) (SharedDirectoryAnnounce, error) {
t, err := in.ReadByte()
if err != nil {
return SharedDirectoryAnnounce{}, trace.Wrap(err)
}
if t != byte(TypeSharedDirectoryAnnounce) {
return SharedDirectoryAnnounce{}, trace.BadParameter("got message type %v, expected SharedDirectoryAnnounce(%v)", t, TypeSharedDirectoryAnnounce)
}
var completionID, directoryID uint32
err = binary.Read(in, binary.BigEndian, &completionID)
if err != nil {
return SharedDirectoryAnnounce{}, trace.Wrap(err)
}
err = binary.Read(in, binary.BigEndian, &directoryID)
if err != nil {
return SharedDirectoryAnnounce{}, trace.Wrap(err)
}
name, err := decodeString(in, windowsMaxUsernameLength)
if err != nil {
return SharedDirectoryAnnounce{}, trace.Wrap(err)
}
return SharedDirectoryAnnounce{
DirectoryID: directoryID,
Name: name,
}, nil
}
type SharedDirectoryAcknowledge struct {
Err uint32
DirectoryID uint32
}
func decodeSharedDirectoryAcknowledge(in peekReader) (SharedDirectoryAcknowledge, error) {
t, err := in.ReadByte()
if err != nil {
return SharedDirectoryAcknowledge{}, trace.Wrap(err)
}
if t != byte(TypeSharedDirectoryAcknowledge) {
return SharedDirectoryAcknowledge{}, trace.BadParameter("got message type %v, expected SharedDirectoryAcknowledge(%v)", t, TypeSharedDirectoryAnnounce)
}
var s SharedDirectoryAcknowledge
err = binary.Read(in, binary.BigEndian, &s)
return s, trace.Wrap(err)
}
func (s SharedDirectoryAcknowledge) Encode() ([]byte, error) {
buf := new(bytes.Buffer)
buf.WriteByte(byte(TypeSharedDirectoryAcknowledge))
binary.Write(buf, binary.BigEndian, s.Err)
binary.Write(buf, binary.BigEndian, s.DirectoryID)
return buf.Bytes(), nil
}
// 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

View file

@ -107,8 +107,6 @@ const (
// actionPlayPause toggles the playback state
// between playing and paused
actionPlayPause = playbackAction("play/pause")
// TODO(isaiah): support playbackAction("seek")
)
// actionMessage is a message passed from the playback client