This commit is contained in:
Jovi Hsu 2021-11-10 22:41:11 +08:00
parent a0b1157c04
commit c8bb376d80
7 changed files with 232 additions and 36 deletions

View File

@ -1,3 +1,4 @@
use super::input::Input;
use anyhow; use anyhow;
use serde_json::{json, Value}; use serde_json::{json, Value};
use std::fmt::Debug; use std::fmt::Debug;
@ -116,28 +117,19 @@ impl Component for AuthComponents {
fn view(&self) -> Html { fn view(&self) -> Html {
let link = &self.link; let link = &self.link;
let update_uname = link.callback(|e: ChangeData| match e { let update_uname = link.callback(|v| AuthMsg::UpdateUsername(v));
ChangeData::Value(val) => AuthMsg::UpdateUsername(val),
_ => panic!("unexpected message"),
});
let update_pword = link.callback(|e: ChangeData| match e { let update_pword = link.callback(|v| AuthMsg::UpdatePassword(v));
ChangeData::Value(val) => AuthMsg::UpdatePassword(val),
_ => panic!("unexpected message"),
});
let auth_post = link.callback(|_| { let auth_post = link.callback(|_| AuthMsg::AuthRequest);
// ConsoleService::log("Auth post");
AuthMsg::AuthRequest
});
html! { html! {
<div class="horizontal-centre vertical-centre"> <div class="horizontal-centre vertical-centre">
<label for="username">{"Username: "}</label> <label for="username">{"Username: "}</label>
<input id="username" type="text" placeholder="Username" onchange={update_uname} /> <Input id="username" type_="text" placeholder="Username" on_change={update_uname} />
<br /> <br />
<label for="password">{"Password: "}</label> <label for="password">{"Password: "}</label>
<input id="password" type="password" placeholder="Password" onchange={update_pword} /> <Input id="password" type_="password" placeholder="Password" on_change={update_pword} />
<br /> <br />
<button type="submit" onclick={auth_post}>{"Login"}</button> <button type="submit" onclick={auth_post}>{"Login"}</button>
<br /> <br />

View File

@ -0,0 +1,68 @@
use yew::prelude::*;
// message on update
pub enum InputMsg {
Update(String),
}
// props on_change
#[derive(Clone, PartialEq, Properties)]
pub struct InputProps {
pub on_change: Callback<String>,
pub id: String,
pub type_: String,
pub placeholder: String,
}
// component input
pub struct Input {
link: ComponentLink<Self>,
on_change: Callback<String>,
id: String,
type_: String,
placeholder: String,
}
impl Component for Input {
type Message = InputMsg;
type Properties = InputProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
Input {
link,
on_change: props.on_change,
id: props.id,
type_: props.type_,
placeholder: props.placeholder,
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
InputMsg::Update(text) => {
self.on_change.emit(text);
true
}
}
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}
fn view(&self) -> Html {
let on_change = self.link.callback(|e: ChangeData| match e {
ChangeData::Value(v) => InputMsg::Update(v),
_ => panic!("unexpected message"),
});
html! {
<input
id={self.id.clone()}
type={self.type_.clone()}
placeholder={self.placeholder.clone()}
onchange={on_change}
/>
}
}
}

View File

@ -1,3 +1,4 @@
pub mod auth; pub mod auth;
pub mod host; pub mod host;
pub mod input;
pub mod ws; pub mod ws;

View File

@ -104,6 +104,7 @@ impl Component for WebsocketCtx {
fn rendered(&mut self, first_render: bool) { fn rendered(&mut self, first_render: bool) {
if first_render && self.ws.is_none() { if first_render && self.ws.is_none() {
ConsoleService::log(&format!("Start websocket"));
self.link.send_message(WebsocketMsg::Connect); self.link.send_message(WebsocketMsg::Connect);
} }
} }

View File

@ -9,9 +9,8 @@ use yew::{
}, },
}; };
use crate::components::ws::WebsocketMsg;
use crate::{ use crate::{
components, components::{self, input::Input, ws::WebsocketMsg},
protocal::{common::*, vnc::VncHandler}, protocal::{common::*, vnc::VncHandler},
utils::WeakComponentLink, utils::WeakComponentLink,
}; };
@ -24,6 +23,10 @@ pub struct PageRemote {
connected: bool, connected: bool,
handler: ProtocalHandler<VncHandler>, handler: ProtocalHandler<VncHandler>,
ws_link: WeakComponentLink<components::ws::WebsocketCtx>, ws_link: WeakComponentLink<components::ws::WebsocketCtx>,
request_username: bool,
request_password: bool,
username: String,
password: String,
} }
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
@ -35,6 +38,9 @@ pub enum RemoteMsg {
Connected, Connected,
Recv(Vec<u8>), Recv(Vec<u8>),
Send(Vec<u8>), Send(Vec<u8>),
UpdateUsername(String),
UpdatePassword(String),
SendCredential,
} }
impl Component for PageRemote { impl Component for PageRemote {
@ -50,6 +56,10 @@ impl Component for PageRemote {
connected: false, connected: false,
handler: ProtocalHandler::new(), handler: ProtocalHandler::new(),
ws_link: WeakComponentLink::default(), ws_link: WeakComponentLink::default(),
request_username: false,
request_password: false,
username: String::from(""),
password: String::from(""),
} }
} }
@ -115,6 +125,11 @@ impl Component for PageRemote {
self.link.send_message(RemoteMsg::Send(out)); self.link.send_message(RemoteMsg::Send(out));
false false
} }
ProtocalHandlerOutput::RequirePassword => {
self.request_password = true;
true
}
_ => unimplemented!(),
} }
} }
RemoteMsg::Send(v) => { RemoteMsg::Send(v) => {
@ -125,6 +140,20 @@ impl Component for PageRemote {
.send_message(WebsocketMsg::Send(Ok(v))); .send_message(WebsocketMsg::Send(Ok(v)));
false false
} }
RemoteMsg::UpdateUsername(username) => {
self.username = username;
true
}
RemoteMsg::UpdatePassword(password) => {
self.password = password;
true
}
RemoteMsg::SendCredential => {
self.request_username = false;
self.request_password = false;
self.handler.set_credential(&self.username, &self.password);
true
}
} }
} }
@ -146,11 +175,61 @@ impl Component for PageRemote {
let ws_link = &self.ws_link; let ws_link = &self.ws_link;
html! { html! {
<> <>
<div class="horizontal-centre vertical-centre">
{self.username_view()}
{self.password_view()}
{self.button_connect_view()}
<components::ws::WebsocketCtx <components::ws::WebsocketCtx
weak_link=ws_link onrecv=recv_msg/> weak_link=ws_link onrecv=recv_msg/>
{self.error_msg.clone()} {self.error_msg.clone()}
</div>
</> </>
} }
} }
} }
} }
// impl PageRemote
impl PageRemote {
fn username_view(&self) -> Html {
if self.request_username {
let update_username = self.link.callback(|v| RemoteMsg::UpdateUsername(v));
html! {
<>
<Input id="username" type_="text" placeholder="username" on_change={update_username}/>
<br/>
</>
}
} else {
html! {}
}
}
fn password_view(&self) -> Html {
if self.request_password {
let update_password = self.link.callback(|v| RemoteMsg::UpdatePassword(v));
html! {
<>
<Input id="password" type_="password" placeholder="password" on_change={update_password}/>
<br/>
</>
}
} else {
html! {}
}
}
fn button_connect_view(&self) -> Html {
if self.request_username || self.request_password {
let send_credential = self.link.callback(|_| RemoteMsg::SendCredential);
html! {
<>
<button type="submit" onclick={send_credential}>{"Connect"}</button>
<br/>
</>
}
} else {
html! {}
}
}
}

View File

@ -3,6 +3,8 @@ pub enum ProtocalHandlerOutput {
Ok, Ok,
WsBuf(Vec<u8>), WsBuf(Vec<u8>),
Err(String), Err(String),
RequireUsername,
RequirePassword,
} }
pub struct ProtocalHandler<T> pub struct ProtocalHandler<T>
@ -23,11 +25,16 @@ where
pub fn handle(&mut self, input: &[u8]) -> ProtocalHandlerOutput { pub fn handle(&mut self, input: &[u8]) -> ProtocalHandlerOutput {
self.inner.handle(input) self.inner.handle(input)
} }
pub fn set_credential(&mut self, username: &str, password: &str) -> ProtocalHandlerOutput {
self.inner.set_credential(username, password)
}
} }
pub trait ProtocalImpl { pub trait ProtocalImpl {
fn new() -> Self; fn new() -> Self;
fn handle(&mut self, input: &[u8]) -> ProtocalHandlerOutput; fn handle(&mut self, input: &[u8]) -> ProtocalHandlerOutput;
fn set_credential(&mut self, username: &str, password: &str) -> ProtocalHandlerOutput;
} }
pub struct StreamReader<'a> { pub struct StreamReader<'a> {
@ -73,6 +80,12 @@ impl<'a> StreamReader<'a> {
Some(Self::read_u32(self).map(|b| b as i32)?) Some(Self::read_u32(self).map(|b| b as i32)?)
} }
pub fn extract_slice(&mut self, len: usize, buf: &mut [u8]) {
for x in self.inner.by_ref().take(len).enumerate() {
buf[x.0] = *x.1;
}
}
pub fn read_string_with_len(&mut self, len: usize) -> Option<String> { pub fn read_string_with_len(&mut self, len: usize) -> Option<String> {
let mut buf = vec![0u8; len as usize]; let mut buf = vec![0u8; len as usize];
self.inner self.inner
@ -85,11 +98,16 @@ impl<'a> StreamReader<'a> {
Some(String::from_utf8(buf).unwrap()) Some(String::from_utf8(buf).unwrap())
} }
pub fn read_string(&mut self) -> Option<String> { pub fn read_string_l16(&mut self) -> Option<String> {
let len = self.read_u16()? as usize; let len = self.read_u16()? as usize;
Some(self.read_string_with_len(len)?) Some(self.read_string_with_len(len)?)
} }
pub fn read_string_l32(&mut self) -> Option<String> {
let len = self.read_u32()? as usize;
Some(self.read_string_with_len(len)?)
}
pub fn eof(&mut self) -> bool { pub fn eof(&mut self) -> bool {
self.inner.next().is_none() self.inner.next().is_none()
} }

View File

@ -6,31 +6,42 @@ const VNC_RFB33: &[u8; 12] = b"RFB 003.003\n";
const VNC_RFB37: &[u8; 12] = b"RFB 003.007\n"; const VNC_RFB37: &[u8; 12] = b"RFB 003.007\n";
const VNC_RFB38: &[u8; 12] = b"RFB 003.008\n"; const VNC_RFB38: &[u8; 12] = b"RFB 003.008\n";
const VNC_VER_UNSUPPORTED: &str = "unsupported version"; const VNC_VER_UNSUPPORTED: &str = "unsupported version";
const VNC_FAILED: &str = "Connection failed with unknow reason";
enum VncState { enum VncState {
Handshake, Handshake,
Authentication, Authentication,
ClientInit, // auth done
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum SecurityType { pub enum SecurityType {
Unknown(u8), Invalid = 0,
Invalid, None = 1,
None, VncAuth = 2,
VncAuth, RA2 = 5,
AppleRdp, RA2ne = 6,
Tight = 16,
Ultra = 17,
TLS = 18,
VeNCrypt = 19,
} }
pub struct VncHandler { pub struct VncHandler {
state: VncState, state: VncState,
// output: Vec<u8>, challenge: [u8; 16],
security_type: SecurityType,
password: String,
} }
impl ProtocalImpl for VncHandler { impl ProtocalImpl for VncHandler {
fn new() -> Self { fn new() -> Self {
VncHandler { VncHandler {
state: VncState::Handshake, state: VncState::Handshake,
// output: Vec::new(), challenge: [0u8; 16],
security_type: SecurityType::Invalid,
password: String::new(),
} }
} }
@ -47,11 +58,19 @@ impl ProtocalImpl for VncHandler {
} }
VncState::Authentication => { VncState::Authentication => {
ConsoleService::log(&format!("{:?}", input)); ConsoleService::log(&format!("{:?}", input));
return ProtocalHandlerOutput::Ok; return self.start_authenticate(input);
} }
_ => panic!("unsupported version"), _ => panic!("unsupported version"),
} }
} }
fn set_credential(&mut self, _username: &str, password: &str) -> ProtocalHandlerOutput {
ConsoleService::log(&format!("{:?}", password));
ConsoleService::log(&format!("{:?}", self.challenge));
// since vnc do not require username, so we just ignore it
self.password = password.to_string();
self.continue_authenticate()
}
} }
// private methods // private methods
@ -59,17 +78,35 @@ impl VncHandler {
fn handle_handshake(&self, rfbversion: &[u8]) -> Result<&'static [u8], &'static str> { fn handle_handshake(&self, rfbversion: &[u8]) -> Result<&'static [u8], &'static str> {
match rfbversion { match rfbversion {
b"RFB 003.003\n" => Ok(VNC_RFB33), b"RFB 003.003\n" => Ok(VNC_RFB33),
b"RFB 003.007\n" => Ok(VNC_RFB37), b"RFB 003.007\n" => Ok(VNC_RFB33),
b"RFB 003.008\n" => Ok(VNC_RFB38), b"RFB 003.008\n" => Ok(VNC_RFB33),
_ => Err(VNC_VER_UNSUPPORTED), _ => Err(VNC_VER_UNSUPPORTED),
} }
} }
fn handle_authenticate(&mut self, auth: &[u8]) -> Result<&'static [u8], &'static str> { fn start_authenticate(&mut self, auth: &[u8]) -> ProtocalHandlerOutput {
match auth { let mut sr = StreamReader::new(auth);
b"NONE\n" => Ok(b"\x00"), match sr.read_u32() {
b"VNCAUTH\n" => Ok(b"\x01"), Some(0) => {
b"APPLETALK\n" => Ok(b"\x02"), let err_msg = sr.read_string_l32().unwrap();
_ => Err(VNC_VER_UNSUPPORTED), ProtocalHandlerOutput::Err(err_msg)
} }
Some(1) => {
self.state = VncState::ClientInit;
self.client_initialisation()
}
Some(2) => {
sr.extract_slice(16, &mut self.challenge);
ProtocalHandlerOutput::RequirePassword
}
_ => ProtocalHandlerOutput::Err(VNC_FAILED.to_string()),
}
}
fn client_initialisation(&mut self) -> ProtocalHandlerOutput {
ProtocalHandlerOutput::Ok
}
fn continue_authenticate(&mut self) -> ProtocalHandlerOutput {
ProtocalHandlerOutput::Ok
} }
} }