Skip to content

Commit

Permalink
TDP Shared Directory Announce and Acknowledge (#12405)
Browse files Browse the repository at this point in the history
Co-authored-by: Zac Bergquist <zmb3@users.noreply.github.com>
  • Loading branch information
Isaiah Becker-Mayer and zmb3 authored Jun 14, 2022
1 parent aafe27c commit c018cd7
Show file tree
Hide file tree
Showing 13 changed files with 1,658 additions and 175 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions lib/srv/desktop/rdp/rdpclient/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

31 changes: 31 additions & 0 deletions lib/srv/desktop/rdp/rdpclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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()
Expand Down
37 changes: 35 additions & 2 deletions lib/srv/desktop/rdp/rdpclient/librdprs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);

/**
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
6 changes: 3 additions & 3 deletions lib/srv/desktop/rdp/rdpclient/src/cliprdr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -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));
}
};

Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions lib/srv/desktop/rdp/rdpclient/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
105 changes: 97 additions & 8 deletions lib/srv/desktop/rdp/rdpclient/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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));
}
_ => {}
}
Expand Down Expand Up @@ -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()
}
Expand All @@ -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.
Expand Down
Loading

0 comments on commit c018cd7

Please sign in to comment.