rdp-rs worked poc

This commit is contained in:
Jovi Hsu 2022-10-23 10:05:56 +08:00
parent f79b272ffd
commit c6aa71107c
7 changed files with 3430 additions and 150 deletions

3257
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -13,11 +13,21 @@ default = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2.63"
js-sys = "0.3"
untrusted = "0.9"
md4 = "0.10.2"
hmac = "0.12.1"
md-5 = "0.10.5"
x509-parser = "0.14.0"
rdp-rs = { path = "./rdp-rs", default-features = false }
ws_stream_wasm = { version = "^0.7", features = ["tokio_io"] }
async_io_stream = { version = "^0.3", features = ["tokio_io"] }
pharos = "0.5.3"
wasm-bindgen-futures = "0.4.33"
futures = "0.3.25"
async-trait = "0.1.58"
tokio = { version = "^1", features = [
"sync",
"macros",
"io-util",
"rt",
"time"
]}
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires

@ -1 +1 @@
Subproject commit e7a7693b46c7ba56483e464a2a2e478d08c2a9ea
Subproject commit c340a749868f70fde328bf15c751f843da6e119e

View File

@ -1,12 +1,9 @@
mod canvas;
mod rdp_client;
mod rdp_ws;
mod utils;
use canvas::CanvasUtils;
use rdp_client::Rdp;
use rdp_ws::Rdp;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{ErrorEvent, HtmlButtonElement, MessageEvent, WebSocket};
use wasm_bindgen_futures::spawn_local;
#[macro_export]
macro_rules! console_log {
@ -22,81 +19,8 @@ extern "C" {
pub fn getClipBoard() -> String;
}
fn rdp_close_handle(rdp: &Rdp, canvas: &CanvasUtils, msg: &str) {
rdp.close();
// unsafe {
// REFRESHER.take();
// }
canvas.close();
let status = web_sys::window()
.unwrap()
.document()
.unwrap()
.get_element_by_id("rdp_status")
.unwrap();
status.set_text_content(Some(msg));
}
fn rdp_out_handler(ws: &WebSocket, rdp: &Rdp, canvas: &CanvasUtils) {
let out = rdp.get_output();
if let Some(out) = out {
for ref o in out {
match o {
rdp_client::RdpOutput::Err(err) => {
console_log!("Err {}", err);
rdp_close_handle(rdp, canvas, err);
}
rdp_client::RdpOutput::WsBuf(buf) => match ws.send_with_u8_array(buf) {
Ok(_) => {}
Err(err) => {
let err = format!("error sending message: {:?}", err);
rdp_close_handle(rdp, canvas, &err);
}
},
rdp_client::RdpOutput::RequireSSL => match ws.send_with_str("SSL") {
Ok(_) => {}
Err(err) => {
let err = format!("error launching ssl: {:?}", err);
rdp_close_handle(rdp, canvas, &err);
}
},
rdp_client::RdpOutput::RequirePassword => {
// let pwd = prompt("Please input the password");
// rdp.set_credential(&pwd);
// rdp_out_handler(ws, rdp, canvas);
}
rdp_client::RdpOutput::RenderImage(ri) => {
canvas.draw(ri);
}
rdp_client::RdpOutput::SetResolution(x, y) => {
canvas.init(*x as u32, *y as u32);
canvas.bind(rdp);
// rdp.require_frame(0);
rdp_out_handler(ws, rdp, canvas);
// let vnc_cloned = rdp.clone();
// let ws_cloned = ws.clone();
// let canvas_cloned = canvas.clone();
// set a interval for fps enhance
// let refresh = move || {
// vnc_cloned.require_frame(1);
// rdp_out_handler(&ws_cloned, &vnc_cloned, &canvas_cloned);
// };
// let refersher = Interval::new(20, refresh);
// unsafe {
// REFRESHER = Some(refersher);
// }
}
rdp_client::RdpOutput::SetClipboard(text) => {
setClipBoard(text.to_owned());
// ConsoleService::log(&self.error_msg);
}
}
}
}
fn read_credentials() {
unimplemented!();
}
fn start_websocket() -> Result<(), JsValue> {
@ -115,72 +39,11 @@ fn start_websocket() -> Result<(), JsValue> {
},
host = web_sys::window().unwrap().location().host()?
);
let ws = WebSocket::new_with_str(&url, "binary")?;
let canvas = CanvasUtils::new();
let rdp = Rdp::new();
let clipboard = web_sys::window()
.unwrap()
.document()
.unwrap()
.get_element_by_id("clipboardsend")
.unwrap()
.dyn_into::<HtmlButtonElement>()
.map_err(|_| ())
.unwrap();
let rdp_cloned = rdp.clone();
let onclickcb = Closure::<dyn FnMut()>::new(move || {
console_log!("Send {:?}", getClipBoard());
rdp_cloned.set_clipboard(&getClipBoard());
spawn_local(async move {
let mut rdp = Rdp::new(&url, "", "", "");
while !rdp.start().await {}
});
clipboard.set_onclick(Some(onclickcb.as_ref().unchecked_ref()));
onclickcb.forget();
ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
// on message
let cloned_ws = ws.clone();
let rdp_cloned = rdp.clone();
let canvas_cloned = canvas.clone();
let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |e: MessageEvent| {
if let Ok(abuf) = e.data().dyn_into::<js_sys::ArrayBuffer>() {
let array = js_sys::Uint8Array::new(&abuf);
// let mut canvas_ctx = None;
rdp_cloned.do_input(array.to_vec());
rdp_out_handler(&cloned_ws, &rdp_cloned, &canvas_cloned);
} else {
console_log!("message event, received Unknown: {:?}", e.data());
}
});
ws.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
// forget the callback to keep it alive
onmessage_callback.forget();
// onerror
let onerror_callback = Closure::<dyn FnMut(_)>::new(move |e: ErrorEvent| {
console_log!("error event: {:?}", e);
});
ws.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
onerror_callback.forget();
// onopen
let rdp_cloned = rdp.clone();
let ws_cloned = ws.clone();
let canvas_cloned = canvas.clone();
let onopen_callback = Closure::<dyn FnMut()>::new(move || {
console_log!("socket opened");
rdp_cloned.init();
rdp_out_handler(&ws_cloned, &rdp_cloned, &canvas_cloned);
});
ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
onopen_callback.forget();
let onclose_callback = Closure::<dyn FnMut()>::new(move || {
console_log!("socket close");
rdp_close_handle(&rdp, &canvas, "Disconnected");
});
ws.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
onclose_callback.forget();
Ok(())
}

3
webrdp/src/rdp_ws/mod.rs Normal file
View File

@ -0,0 +1,3 @@
mod rdp_client;
mod ws_bio;
pub use rdp_client::Rdp;

View File

@ -0,0 +1,100 @@
use rdp::{
core::client::{Connector, RdpClient},
model::{
error::{RdpError, RdpErrorKind, RdpResult},
link::{self, AsyncSecureBio},
},
};
use web_sys::Element;
use crate::{console_log, log};
use super::ws_bio::WsSecureBio;
const RDP_HOSTNAME: &str = "webrdp";
pub struct Rdp {
url: String,
status_bar: Element,
username: String,
password: String,
domain: String,
}
impl Rdp {
pub fn new(url: &str, username: &str, password: &str, domain: &str) -> Self {
let status_bar = web_sys::window()
.unwrap()
.document()
.unwrap()
.get_element_by_id("rdp_status")
.unwrap();
Self {
url: url.to_owned(),
status_bar,
username: username.to_owned(),
password: password.to_owned(),
domain: domain.to_owned(),
}
}
pub fn set_user(&mut self, username: &str) {
self.username = username.to_owned();
}
pub fn set_password(&mut self, password: &str) {
self.password = password.to_owned();
}
pub fn set_domain(&mut self, domain: &str) {
self.domain = domain.to_owned();
}
pub async fn start(&mut self) -> bool {
let ws_stream = WsSecureBio::new(&self.url).await;
let body = web_sys::window()
.unwrap()
.document()
.unwrap()
.body()
.unwrap();
let mut rdp_connector = Connector::new()
.screen(body.client_width() as u16, body.client_height() as u16)
.credentials(
self.domain.clone(),
self.username.clone(),
self.password.clone(),
)
.set_restricted_admin_mode(false)
.auto_logon(false)
.blank_creds(false)
.check_certificate(false)
.name(RDP_HOSTNAME.to_string())
.use_nla(true);
let mut rdp_client = rdp_connector.connect(Box::new(ws_stream)).await.unwrap();
console_log!("Rdp Started");
loop {
if let Err(rdp::model::error::Error::RdpError(e)) = rdp_client
.read(|event| match event {
_ => console_log!("ignore event"),
})
.await
{
match e.kind() {
RdpErrorKind::Disconnect => {
console_log!("Server ask for disconnect");
}
_ => console_log!("{:?}", e),
}
}
}
true
}
fn disconnect_with_msg(&self, msg: &str) {
self.status_bar.set_text_content(Some(msg));
}
}

View File

@ -0,0 +1,47 @@
use async_io_stream::IoStream;
use async_trait::async_trait;
use rdp::model::{error::RdpResult, link::AsyncSecureBio};
use tokio::io::AsyncReadExt;
use ws_stream_wasm::*;
use crate::{console_log, log};
use {futures::stream::StreamExt, pharos::*, wasm_bindgen::UnwrapThrowExt, ws_stream_wasm::*};
pub struct WsSecureBio {
peer_cert: Vec<u8>,
ws_stream: IoStream<WsStreamIo, Vec<u8>>,
ws_meta: WsMeta,
}
#[async_trait]
impl AsyncSecureBio<IoStream<WsStreamIo, Vec<u8>>> for WsSecureBio {
async fn start_ssl(&mut self, _check_certificate: bool) -> RdpResult<()> {
let _ = self.ws_meta.wrapped().send_with_str("SSL");
let mut ber_cert = [0; 1500];
let size = self.ws_stream.read(&mut ber_cert).await.unwrap();
console_log!("Read {} byte public cert", size);
self.peer_cert = ber_cert.to_vec();
Ok(())
}
fn get_peer_certificate_der(&self) -> RdpResult<Option<Vec<u8>>> {
Ok(Some(self.peer_cert.clone()))
}
async fn shutdown(&mut self) -> std::io::Result<()> {
unimplemented!()
}
fn get_io(&mut self) -> &mut IoStream<WsStreamIo, Vec<u8>> {
&mut self.ws_stream
}
}
impl WsSecureBio {
pub async fn new(url: &str) -> Self {
let (ws, wsio) = WsMeta::connect(url, vec!["binary"]).await.unwrap();
Self {
peer_cert: vec![],
ws_stream: wsio.into_io(),
ws_meta: ws,
}
}
}