mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-01-07 22:58:04 +08:00
7dcb28ce33
Signed-off-by: dignow <linlong1265@gmail.com>
411 lines
14 KiB
Rust
411 lines
14 KiB
Rust
use super::*;
|
|
use crate::{
|
|
flutter::{self, APP_TYPE_CM, APP_TYPE_MAIN, SESSIONS},
|
|
ui_interface::get_api_server,
|
|
};
|
|
use hbb_common::{lazy_static, log, message_proto::PluginRequest};
|
|
use serde_derive::{Deserialize, Serialize};
|
|
use serde_json;
|
|
use std::{
|
|
collections::HashMap,
|
|
ffi::{c_char, c_void},
|
|
sync::Arc,
|
|
thread,
|
|
time::Duration,
|
|
};
|
|
|
|
const MSG_TO_RUSTDESK_TARGET: &str = "rustdesk";
|
|
const MSG_TO_PEER_TARGET: &str = "peer";
|
|
const MSG_TO_UI_TARGET: &str = "ui";
|
|
const MSG_TO_CONFIG_TARGET: &str = "config";
|
|
const MSG_TO_EXT_SUPPORT_TARGET: &str = "ext-support";
|
|
|
|
const MSG_TO_RUSTDESK_SIGNATURE_VERIFICATION: &str = "signature_verification";
|
|
|
|
#[allow(dead_code)]
|
|
const MSG_TO_UI_FLUTTER_CHANNEL_MAIN: u16 = 0x01 << 0;
|
|
#[allow(dead_code)]
|
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
const MSG_TO_UI_FLUTTER_CHANNEL_CM: u16 = 0x01 << 1;
|
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
const MSG_TO_UI_FLUTTER_CHANNEL_CM: u16 = 0x01;
|
|
const MSG_TO_UI_FLUTTER_CHANNEL_REMOTE: u16 = 0x01 << 2;
|
|
#[allow(dead_code)]
|
|
const MSG_TO_UI_FLUTTER_CHANNEL_TRANSFER: u16 = 0x01 << 3;
|
|
#[allow(dead_code)]
|
|
const MSG_TO_UI_FLUTTER_CHANNEL_FORWARD: u16 = 0x01 << 4;
|
|
|
|
lazy_static::lazy_static! {
|
|
static ref MSG_TO_UI_FLUTTER_CHANNELS: Arc<HashMap<u16, String>> = {
|
|
let channels = HashMap::from([
|
|
(MSG_TO_UI_FLUTTER_CHANNEL_MAIN, APP_TYPE_MAIN.to_string()),
|
|
(MSG_TO_UI_FLUTTER_CHANNEL_CM, APP_TYPE_CM.to_string()),
|
|
]);
|
|
Arc::new(channels)
|
|
};
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct MsgToRustDesk {
|
|
pub r#type: String,
|
|
pub data: Vec<u8>,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct SignatureVerification {
|
|
pub version: String,
|
|
pub data: Vec<u8>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct ConfigToUi {
|
|
channel: u16,
|
|
location: String,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct MsgToConfig {
|
|
r#type: String,
|
|
key: String,
|
|
value: String,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
ui: Option<ConfigToUi>, // If not None, send msg to ui.
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub(super) struct MsgToExtSupport {
|
|
pub r#type: String,
|
|
pub data: Vec<u8>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
struct PluginSignReq {
|
|
plugin_id: String,
|
|
version: String,
|
|
msg: Vec<u8>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct PluginSignResp {
|
|
signed_msg: Vec<u8>,
|
|
}
|
|
|
|
macro_rules! cb_msg_field {
|
|
($field: ident) => {
|
|
let $field = match cstr_to_string($field) {
|
|
Err(e) => {
|
|
let msg = format!("Failed to convert {} to string, {}", stringify!($field), e);
|
|
log::error!("{}", &msg);
|
|
return PluginReturn::new(errno::ERR_CALLBACK_INVALID_ARGS, &msg);
|
|
}
|
|
Ok(v) => v,
|
|
};
|
|
};
|
|
}
|
|
|
|
macro_rules! early_return_value {
|
|
($e:expr, $code: ident, $($arg:tt)*) => {
|
|
match $e {
|
|
Err(e) => return PluginReturn::new(
|
|
errno::$code,
|
|
&format!("Failed to {} '{}'", format_args!($($arg)*), e),
|
|
),
|
|
Ok(v) => v,
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Callback to send message to peer or ui.
|
|
/// peer, target, id are utf8 strings(null terminated).
|
|
///
|
|
/// peer: The peer id.
|
|
/// target: "peer" or "ui".
|
|
/// id: The id of this plugin.
|
|
/// content: The content.
|
|
/// len: The length of the content.
|
|
///
|
|
/// Return null ptr if success.
|
|
/// Return the error message if failed. `i32-String` without dash, i32 is a signed little-endian number, the String is utf8 string.
|
|
/// The plugin allocate memory with `libc::malloc` and return the pointer.
|
|
#[no_mangle]
|
|
pub(super) extern "C" fn cb_msg(
|
|
peer: *const c_char,
|
|
target: *const c_char,
|
|
id: *const c_char,
|
|
content: *const c_void,
|
|
len: usize,
|
|
) -> PluginReturn {
|
|
cb_msg_field!(target);
|
|
cb_msg_field!(id);
|
|
|
|
match &target as _ {
|
|
MSG_TO_PEER_TARGET => {
|
|
cb_msg_field!(peer);
|
|
if let Some(session) = SESSIONS.write().unwrap().get_mut(&peer) {
|
|
let content_slice =
|
|
unsafe { std::slice::from_raw_parts(content as *const u8, len) };
|
|
let content_vec = Vec::from(content_slice);
|
|
let request = PluginRequest {
|
|
id,
|
|
content: bytes::Bytes::from(content_vec),
|
|
..Default::default()
|
|
};
|
|
session.send_plugin_request(request);
|
|
PluginReturn::success()
|
|
} else {
|
|
PluginReturn::new(
|
|
errno::ERR_CALLBACK_PEER_NOT_FOUND,
|
|
&format!("Failed to find session for peer '{}'", peer),
|
|
)
|
|
}
|
|
}
|
|
MSG_TO_UI_TARGET => {
|
|
cb_msg_field!(peer);
|
|
let content_slice = unsafe { std::slice::from_raw_parts(content as *const u8, len) };
|
|
let channel = u16::from_le_bytes([content_slice[0], content_slice[1]]);
|
|
let content = std::string::String::from_utf8(content_slice[2..].to_vec())
|
|
.unwrap_or("".to_string());
|
|
push_event_to_ui(channel, &peer, &content);
|
|
PluginReturn::success()
|
|
}
|
|
MSG_TO_CONFIG_TARGET => {
|
|
cb_msg_field!(peer);
|
|
let s = early_return_value!(
|
|
std::str::from_utf8(unsafe { std::slice::from_raw_parts(content as _, len) }),
|
|
ERR_CALLBACK_INVALID_MSG,
|
|
"parse msg string"
|
|
);
|
|
// No need to merge the msgs. Handling the msg one by one is ok.
|
|
let msg = early_return_value!(
|
|
serde_json::from_str::<MsgToConfig>(s),
|
|
ERR_CALLBACK_INVALID_MSG,
|
|
"parse msg '{}'",
|
|
s
|
|
);
|
|
match &msg.r#type as _ {
|
|
config::CONFIG_TYPE_SHARED => {
|
|
let _r = early_return_value!(
|
|
config::SharedConfig::set(&id, &msg.key, &msg.value),
|
|
ERR_CALLBACK_INVALID_MSG,
|
|
"set local config"
|
|
);
|
|
if let Some(ui) = &msg.ui {
|
|
// No need to set the peer id for location config.
|
|
push_option_to_ui(ui.channel, &id, "", &msg, ui);
|
|
}
|
|
PluginReturn::success()
|
|
}
|
|
config::CONFIG_TYPE_PEER => {
|
|
let _r = early_return_value!(
|
|
config::PeerConfig::set(&id, &peer, &msg.key, &msg.value),
|
|
ERR_CALLBACK_INVALID_MSG,
|
|
"set peer config"
|
|
);
|
|
if let Some(ui) = &msg.ui {
|
|
push_option_to_ui(ui.channel, &id, &peer, &msg, ui);
|
|
}
|
|
PluginReturn::success()
|
|
}
|
|
_ => PluginReturn::new(
|
|
errno::ERR_CALLBACK_TARGET_TYPE,
|
|
&format!("Unknown target type '{}'", &msg.r#type),
|
|
),
|
|
}
|
|
}
|
|
MSG_TO_EXT_SUPPORT_TARGET => {
|
|
cb_msg_field!(peer);
|
|
let s = early_return_value!(
|
|
std::str::from_utf8(unsafe { std::slice::from_raw_parts(content as _, len) }),
|
|
ERR_CALLBACK_INVALID_MSG,
|
|
"parse msg string"
|
|
);
|
|
let msg = early_return_value!(
|
|
serde_json::from_str::<MsgToExtSupport>(s),
|
|
ERR_CALLBACK_INVALID_MSG,
|
|
"parse msg '{}'",
|
|
s
|
|
);
|
|
super::callback_ext::ext_support_callback(&id, &peer, &msg)
|
|
}
|
|
MSG_TO_RUSTDESK_TARGET => handle_msg_to_rustdesk(id, content, len),
|
|
_ => PluginReturn::new(
|
|
errno::ERR_CALLBACK_TARGET,
|
|
&format!("Unknown target '{}'", target),
|
|
),
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn is_peer_channel(channel: u16) -> bool {
|
|
channel & MSG_TO_UI_FLUTTER_CHANNEL_REMOTE != 0
|
|
|| channel & MSG_TO_UI_FLUTTER_CHANNEL_TRANSFER != 0
|
|
|| channel & MSG_TO_UI_FLUTTER_CHANNEL_FORWARD != 0
|
|
}
|
|
|
|
fn handle_msg_to_rustdesk(id: String, content: *const c_void, len: usize) -> PluginReturn {
|
|
let s = early_return_value!(
|
|
std::str::from_utf8(unsafe { std::slice::from_raw_parts(content as _, len) }),
|
|
ERR_CALLBACK_INVALID_MSG,
|
|
"parse msg string"
|
|
);
|
|
let msg_to_rustdesk = early_return_value!(
|
|
serde_json::from_str::<MsgToRustDesk>(s),
|
|
ERR_CALLBACK_INVALID_MSG,
|
|
"parse msg '{}'",
|
|
s
|
|
);
|
|
match &msg_to_rustdesk.r#type as &str {
|
|
MSG_TO_RUSTDESK_SIGNATURE_VERIFICATION => request_plugin_sign(id, msg_to_rustdesk),
|
|
t => PluginReturn::new(
|
|
errno::ERR_CALLBACK_TARGET_TYPE,
|
|
&format!(
|
|
"Unknown target type '{}' for target {}",
|
|
t, MSG_TO_RUSTDESK_TARGET
|
|
),
|
|
),
|
|
}
|
|
}
|
|
|
|
fn request_plugin_sign(id: String, msg_to_rustdesk: MsgToRustDesk) -> PluginReturn {
|
|
let signature_data = early_return_value!(
|
|
std::str::from_utf8(&msg_to_rustdesk.data),
|
|
ERR_CALLBACK_INVALID_MSG,
|
|
"parse signature data string"
|
|
);
|
|
let signature_data = early_return_value!(
|
|
serde_json::from_str::<SignatureVerification>(signature_data),
|
|
ERR_CALLBACK_INVALID_MSG,
|
|
"parse signature data '{}'",
|
|
signature_data
|
|
);
|
|
thread::spawn(move || {
|
|
let sign_url = format!("{}/lic/web/api/plugin-sign", get_api_server());
|
|
let client = reqwest::blocking::Client::new();
|
|
let req = PluginSignReq {
|
|
plugin_id: id.clone(),
|
|
version: signature_data.version,
|
|
msg: signature_data.data,
|
|
};
|
|
match client
|
|
.post(sign_url)
|
|
.json(&req)
|
|
.timeout(Duration::from_secs(10))
|
|
.send()
|
|
{
|
|
Ok(response) => match response.json::<PluginSignResp>() {
|
|
Ok(sign_resp) => {
|
|
match super::plugins::plugin_call(
|
|
&id,
|
|
super::plugins::METHOD_HANDLE_SIGNATURE_VERIFICATION,
|
|
"",
|
|
&sign_resp.signed_msg,
|
|
) {
|
|
Ok(..) => {
|
|
match super::plugins::plugin_call_get_return(
|
|
&id,
|
|
super::plugins::METHOD_HANDLE_STATUS,
|
|
"",
|
|
&[],
|
|
) {
|
|
Ok(ret) => {
|
|
debug_assert!(!ret.msg.is_null(), "msg is null");
|
|
if ret.msg.is_null() {
|
|
// unreachable
|
|
log::error!(
|
|
"The returned message pointer of plugin status is null, plugin id: '{}', code: {}",
|
|
id,
|
|
ret.code,
|
|
);
|
|
return;
|
|
}
|
|
let msg = cstr_to_string(ret.msg).unwrap_or_default();
|
|
free_c_ptr(ret.msg as _);
|
|
if ret.code == super::errno::ERR_SUCCESS {
|
|
log::info!("Plugin '{}' status: '{}'", id, msg);
|
|
} else {
|
|
log::error!(
|
|
"Failed to handle plugin event, id: {}, method: {}, code: {}, msg: {}",
|
|
id,
|
|
std::string::String::from_utf8(super::plugins::METHOD_HANDLE_STATUS.to_vec()).unwrap_or_default(),
|
|
ret.code,
|
|
msg
|
|
);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
log::error!(
|
|
"Failed to call status for plugin '{}': {}",
|
|
&id,
|
|
e
|
|
);
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
log::error!(
|
|
"Failed to call signature verification for plugin '{}': {}",
|
|
&id,
|
|
e
|
|
);
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
log::error!("Failed to decode response for plugin '{}': {}", &id, e);
|
|
}
|
|
},
|
|
Err(e) => {
|
|
log::error!("Failed to request sign for plugin '{}', {}", &id, e);
|
|
}
|
|
}
|
|
});
|
|
PluginReturn::success()
|
|
}
|
|
|
|
fn push_event_to_ui(channel: u16, peer: &str, content: &str) {
|
|
let mut m = HashMap::new();
|
|
m.insert("name", MSG_TO_UI_TYPE_PLUGIN_EVENT);
|
|
m.insert("peer", &peer);
|
|
m.insert("content", &content);
|
|
let event = serde_json::to_string(&m).unwrap_or("".to_string());
|
|
// Send to main and cm
|
|
for (k, v) in MSG_TO_UI_FLUTTER_CHANNELS.iter() {
|
|
if channel & k != 0 {
|
|
let _res = flutter::push_global_event(v as _, event.to_string());
|
|
}
|
|
}
|
|
if !peer.is_empty() && is_peer_channel(channel) {
|
|
let _res = flutter::push_session_event(
|
|
&peer,
|
|
MSG_TO_UI_TYPE_PLUGIN_EVENT,
|
|
vec![("peer", &peer), ("content", &content)],
|
|
);
|
|
}
|
|
}
|
|
|
|
fn push_option_to_ui(channel: u16, id: &str, peer: &str, msg: &MsgToConfig, ui: &ConfigToUi) {
|
|
let v = [
|
|
("id", id),
|
|
("location", &ui.location),
|
|
("key", &msg.key),
|
|
("value", &msg.value),
|
|
];
|
|
|
|
// Send main and cm
|
|
let mut m = HashMap::from(v);
|
|
m.insert("name", MSG_TO_UI_TYPE_PLUGIN_OPTION);
|
|
let event = serde_json::to_string(&m).unwrap_or("".to_string());
|
|
for (k, v) in MSG_TO_UI_FLUTTER_CHANNELS.iter() {
|
|
if channel & k != 0 {
|
|
let _res = flutter::push_global_event(v as _, event.to_string());
|
|
}
|
|
}
|
|
|
|
// Send remote, transfer and forward
|
|
if !peer.is_empty() && is_peer_channel(channel) {
|
|
let mut v = v.to_vec();
|
|
v.push(("peer", &peer));
|
|
let _res = flutter::push_session_event(&peer, MSG_TO_UI_TYPE_PLUGIN_OPTION, v);
|
|
}
|
|
}
|