Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d2f0a5011 | ||
|
|
21556aeb04 | ||
|
|
5fd4b5e255 | ||
|
|
cfe8d30e3b |
Generated
+7
-8
@@ -273,9 +273,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "astral-tokio-tar"
|
name = "astral-tokio-tar"
|
||||||
version = "0.5.6"
|
version = "0.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec179a06c1769b1e42e1e2cbe74c7dcdb3d6383c838454d063eaac5bbb7ebbe5"
|
checksum = "4ce73b17c62717c4b6a9af10b43e87c578b0cac27e00666d48304d3b7d2c0693"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"filetime",
|
"filetime",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -982,9 +982,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorutils-rs"
|
name = "colorutils-rs"
|
||||||
version = "0.7.6"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e2fc25857fa523662de5cae84225b0e7bfb24a2a3f9ed8802fecf03df7252b1"
|
checksum = "69abc9a8ed011e2b7946769f460b9e76e8b659ece9ef4001b9d8bba3489f796d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"erydanos",
|
"erydanos",
|
||||||
"half",
|
"half",
|
||||||
@@ -1422,7 +1422,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deltachat"
|
name = "deltachat"
|
||||||
version = "2.44.0-dev"
|
version = "2.50.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"astral-tokio-tar",
|
"astral-tokio-tar",
|
||||||
@@ -1475,7 +1475,6 @@ dependencies = [
|
|||||||
"ratelimit",
|
"ratelimit",
|
||||||
"regex",
|
"regex",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"rustls-pki-types",
|
|
||||||
"sanitize-filename",
|
"sanitize-filename",
|
||||||
"sdp",
|
"sdp",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -5732,9 +5731,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sdp"
|
name = "sdp"
|
||||||
version = "0.10.0"
|
version = "0.17.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32c374dceda16965d541c8800ce9cc4e1c14acfd661ddf7952feeedc3411e5c6"
|
checksum = "22c3b0257608d7de4de4c4ea650ccc2e6e3e45e3cd80039fcdee768bcb449253"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
"substring",
|
"substring",
|
||||||
|
|||||||
+1
-1
Submodule chatmail-core updated: 0150d38ddd...6fb2f27831
@@ -0,0 +1,10 @@
|
|||||||
|
max_width = 120
|
||||||
|
hard_tabs = false
|
||||||
|
tab_spaces = 4
|
||||||
|
edition = "2021"
|
||||||
|
reorder_imports = true
|
||||||
|
reorder_modules = true
|
||||||
|
format_strings = true
|
||||||
|
normalize_comments = true
|
||||||
|
wrap_comments = true
|
||||||
|
comment_width = 80
|
||||||
@@ -80,11 +80,7 @@ mod tests {
|
|||||||
fn parses_double_quoted_argument() {
|
fn parses_double_quoted_argument() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
split_command_line(r#"/cmd arg1 "arg2 value""#),
|
split_command_line(r#"/cmd arg1 "arg2 value""#),
|
||||||
vec![
|
vec!["/cmd".to_string(), "arg1".to_string(), "arg2 value".to_string()]
|
||||||
"/cmd".to_string(),
|
|
||||||
"arg1".to_string(),
|
|
||||||
"arg2 value".to_string()
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+21
-81
@@ -12,10 +12,7 @@ use deltachat::{
|
|||||||
message::Message,
|
message::Message,
|
||||||
};
|
};
|
||||||
use eui48::MacAddress;
|
use eui48::MacAddress;
|
||||||
use russh::{
|
use russh::{client::AuthResult, keys::PrivateKeyWithHashAlg};
|
||||||
client::AuthResult,
|
|
||||||
keys::PrivateKeyWithHashAlg,
|
|
||||||
};
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
time::{Instant, timeout, timeout_at},
|
time::{Instant, timeout, timeout_at},
|
||||||
@@ -44,10 +41,7 @@ pub async fn global_usage(ctx: Arc<Mutex<BotContext>>) -> String {
|
|||||||
|
|
||||||
usage += "\nCommands from plugins:\n";
|
usage += "\nCommands from plugins:\n";
|
||||||
for cmd in ctx_lock.plugin_commands.values() {
|
for cmd in ctx_lock.plugin_commands.values() {
|
||||||
usage += &format!(
|
usage += &format!("{} (from {}) - {}\n", &cmd.usage, &cmd.plugin_id, &cmd.description);
|
||||||
"{} (from {}) - {}\n",
|
|
||||||
&cmd.usage, &cmd.plugin_id, &cmd.description
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
usage += "\nDeltaChat Remote Control Bot by slavasil, 2026\n";
|
usage += "\nDeltaChat Remote Control Bot by slavasil, 2026\n";
|
||||||
@@ -68,12 +62,7 @@ pub async fn auth_command(
|
|||||||
let dchat_ctx_lock = dchat_ctx.lock().await;
|
let dchat_ctx_lock = dchat_ctx.lock().await;
|
||||||
|
|
||||||
if !AUTH_REQUIRED {
|
if !AUTH_REQUIRED {
|
||||||
chat::send_text_msg(
|
chat::send_text_msg(&dchat_ctx_lock, chat_id, "Authentication is disabled".to_owned()).await?;
|
||||||
&dchat_ctx_lock,
|
|
||||||
chat_id,
|
|
||||||
"Authentication is disabled".to_owned(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,23 +72,13 @@ pub async fn auth_command(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let Some(password) = args.first() else {
|
let Some(password) = args.first() else {
|
||||||
chat::send_text_msg(
|
chat::send_text_msg(&dchat_ctx_lock, chat_id, "Usage: /auth <password>".to_owned()).await?;
|
||||||
&dchat_ctx_lock,
|
|
||||||
chat_id,
|
|
||||||
"Usage: /auth <password>".to_owned(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
if *password == ctx_lock.config.auth.password {
|
if *password == ctx_lock.config.auth.password {
|
||||||
ctx_lock.authed_contacts.insert(contact_id);
|
ctx_lock.authed_contacts.insert(contact_id);
|
||||||
chat::send_text_msg(
|
chat::send_text_msg(&dchat_ctx_lock, chat_id, "Authentication successful!".to_owned()).await?;
|
||||||
&dchat_ctx_lock,
|
|
||||||
chat_id,
|
|
||||||
"Authentication successful!".to_owned(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
} else {
|
} else {
|
||||||
chat::send_text_msg(&dchat_ctx_lock, chat_id, "Incorrect password".to_owned()).await?;
|
chat::send_text_msg(&dchat_ctx_lock, chat_id, "Incorrect password".to_owned()).await?;
|
||||||
}
|
}
|
||||||
@@ -204,12 +183,7 @@ async fn send_magic_packet(mac: MacAddress) -> AnyhowResult<()> {
|
|||||||
|
|
||||||
fn ssh_unlock_disk_usage(config: &BotConfig) -> String {
|
fn ssh_unlock_disk_usage(config: &BotConfig) -> String {
|
||||||
if !config.machines.is_empty() {
|
if !config.machines.is_empty() {
|
||||||
let list = config
|
let list = config.machines.keys().cloned().collect::<Vec<_>>().join("|");
|
||||||
.machines
|
|
||||||
.keys()
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("|");
|
|
||||||
if config.default_machine().is_some() {
|
if config.default_machine().is_some() {
|
||||||
format!("Usage: /ssh-unlock-disk [{list}] <password>")
|
format!("Usage: /ssh-unlock-disk [{list}] <password>")
|
||||||
} else {
|
} else {
|
||||||
@@ -238,12 +212,7 @@ pub async fn ssh_unlock_disk_command(
|
|||||||
|
|
||||||
if args.is_empty() || args.len() > 2 {
|
if args.is_empty() || args.len() > 2 {
|
||||||
log::warn!("wrong number of args");
|
log::warn!("wrong number of args");
|
||||||
chat::send_text_msg(
|
chat::send_text_msg(&dchat_ctx_lock, chat_id, ssh_unlock_disk_usage(&ctx_lock.config)).await?;
|
||||||
&dchat_ctx_lock,
|
|
||||||
chat_id,
|
|
||||||
ssh_unlock_disk_usage(&ctx_lock.config),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,12 +227,7 @@ pub async fn ssh_unlock_disk_command(
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}) else {
|
}) else {
|
||||||
chat::send_text_msg(
|
chat::send_text_msg(&dchat_ctx_lock, chat_id, ssh_unlock_disk_usage(&ctx_lock.config)).await?;
|
||||||
&dchat_ctx_lock,
|
|
||||||
chat_id,
|
|
||||||
ssh_unlock_disk_usage(&ctx_lock.config),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -287,8 +251,7 @@ pub async fn ssh_unlock_disk_command(
|
|||||||
|
|
||||||
let password = args[args.len() - 1].to_owned();
|
let password = args[args.len() - 1].to_owned();
|
||||||
let host = machine.static_ip.to_string();
|
let host = machine.static_ip.to_string();
|
||||||
let ssh_key =
|
let ssh_key = russh::keys::PrivateKey::from_openssh(ssh_key).context("Invalid remote unlock SSH key")?;
|
||||||
russh::keys::PrivateKey::from_openssh(ssh_key).context("Invalid remote unlock SSH key")?;
|
|
||||||
|
|
||||||
drop(ctx_lock);
|
drop(ctx_lock);
|
||||||
drop(dchat_ctx_lock);
|
drop(dchat_ctx_lock);
|
||||||
@@ -314,10 +277,7 @@ pub async fn ssh_unlock_disk_command(
|
|||||||
SSH_TIMEOUT,
|
SSH_TIMEOUT,
|
||||||
connection.authenticate_publickey(
|
connection.authenticate_publickey(
|
||||||
"root",
|
"root",
|
||||||
PrivateKeyWithHashAlg::new(
|
PrivateKeyWithHashAlg::new(Arc::new(ssh_key), connection.best_supported_rsa_hash().await?.flatten()),
|
||||||
Arc::new(ssh_key),
|
|
||||||
connection.best_supported_rsa_hash().await?.flatten(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -353,10 +313,7 @@ pub async fn ssh_unlock_disk_command(
|
|||||||
.context("Cannot send password via SSH")?;
|
.context("Cannot send password via SSH")?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let Some(msg) = timeout(SSH_TIMEOUT, channel.wait())
|
let Some(msg) = timeout(SSH_TIMEOUT, channel.wait()).await.context("Timed out")? else {
|
||||||
.await
|
|
||||||
.context("Timed out")?
|
|
||||||
else {
|
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
log::info!("Received message from SSH server: {:?}", msg);
|
log::info!("Received message from SSH server: {:?}", msg);
|
||||||
@@ -375,12 +332,7 @@ pub async fn ssh_unlock_disk_command(
|
|||||||
|
|
||||||
fn ssh_exec_usage(config: &BotConfig) -> String {
|
fn ssh_exec_usage(config: &BotConfig) -> String {
|
||||||
if !config.machines.is_empty() {
|
if !config.machines.is_empty() {
|
||||||
let list = config
|
let list = config.machines.keys().cloned().collect::<Vec<_>>().join("|");
|
||||||
.machines
|
|
||||||
.keys()
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("|");
|
|
||||||
format!("Usage: /ssh-exec <username>@({list}|<hostname>|<IP address>) <command...>")
|
format!("Usage: /ssh-exec <username>@({list}|<hostname>|<IP address>) <command...>")
|
||||||
} else {
|
} else {
|
||||||
"Command /ssh-exec unavailable: no machines configured".to_string()
|
"Command /ssh-exec unavailable: no machines configured".to_string()
|
||||||
@@ -502,24 +454,16 @@ pub async fn ssh_exec_command(
|
|||||||
russh::ChannelMsg::Data { data } => {
|
russh::ChannelMsg::Data { data } => {
|
||||||
let dchat_ctx_lock = dchat_ctx.lock().await;
|
let dchat_ctx_lock = dchat_ctx.lock().await;
|
||||||
report_text += &String::from_utf8_lossy(&data);
|
report_text += &String::from_utf8_lossy(&data);
|
||||||
chat::send_edit_request(
|
chat::send_edit_request(&dchat_ctx_lock, report_message_id, report_text.clone())
|
||||||
&dchat_ctx_lock,
|
.await
|
||||||
report_message_id,
|
.context("Cannot edit message")?;
|
||||||
report_text.clone(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("Cannot edit message")?;
|
|
||||||
}
|
}
|
||||||
russh::ChannelMsg::ExtendedData { data, ext: 1 } => {
|
russh::ChannelMsg::ExtendedData { data, ext: 1 } => {
|
||||||
let dchat_ctx_lock = dchat_ctx.lock().await;
|
let dchat_ctx_lock = dchat_ctx.lock().await;
|
||||||
report_text += &String::from_utf8_lossy(&data);
|
report_text += &String::from_utf8_lossy(&data);
|
||||||
chat::send_edit_request(
|
chat::send_edit_request(&dchat_ctx_lock, report_message_id, report_text.clone())
|
||||||
&dchat_ctx_lock,
|
.await
|
||||||
report_message_id,
|
.context("Cannot edit message")?;
|
||||||
report_text.clone(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("Cannot edit message")?;
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
log::info!("Message from SSH channel: {msg:?}");
|
log::info!("Message from SSH channel: {msg:?}");
|
||||||
@@ -534,13 +478,9 @@ pub async fn ssh_exec_command(
|
|||||||
|
|
||||||
let dchat_ctx_lock = dchat_ctx.lock().await;
|
let dchat_ctx_lock = dchat_ctx.lock().await;
|
||||||
report_text += "\n\nRead timed out";
|
report_text += "\n\nRead timed out";
|
||||||
chat::send_edit_request(
|
chat::send_edit_request(&dchat_ctx_lock, report_message_id, report_text.clone())
|
||||||
&dchat_ctx_lock,
|
.await
|
||||||
report_message_id,
|
.context("Cannot edit message")?;
|
||||||
report_text.clone(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("Cannot edit message")?;
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-3
@@ -7,78 +7,107 @@ pub(crate) fn yaml_to_json(yaml: &Value) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_yaml_to_json(yaml: &Value, out: &mut String) {
|
fn write_yaml_to_json(yaml: &Value, out: &mut String) {
|
||||||
|
eprintln!("write_yaml_to_json {:?}", yaml);
|
||||||
match yaml {
|
match yaml {
|
||||||
Value::Null => {
|
Value::Null => {
|
||||||
|
eprintln!("write null");
|
||||||
out.push_str("null");
|
out.push_str("null");
|
||||||
}
|
}
|
||||||
Value::Bool(b) => {
|
Value::Bool(b) => {
|
||||||
|
eprintln!("write true/false");
|
||||||
out.push_str(if *b { "true" } else { "false" });
|
out.push_str(if *b { "true" } else { "false" });
|
||||||
}
|
}
|
||||||
Value::Number(n) => {
|
Value::Number(n) => {
|
||||||
|
eprintln!("write {n}");
|
||||||
out.push_str(&n.to_string());
|
out.push_str(&n.to_string());
|
||||||
}
|
}
|
||||||
Value::String(s) => {
|
Value::String(s) => {
|
||||||
|
eprintln!("write \"");
|
||||||
out.push('"');
|
out.push('"');
|
||||||
for c in s.encode_utf16() {
|
for c in s.encode_utf16() {
|
||||||
match c {
|
match c {
|
||||||
0x005C | 0x0022 => {
|
0x005C | 0x0022 => {
|
||||||
|
eprintln!("write \\{}", char::from_u32(c as u32).unwrap());
|
||||||
out.push('\\');
|
out.push('\\');
|
||||||
out.push(char::from_u32(c as u32).unwrap());
|
out.push(char::from_u32(c as u32).unwrap());
|
||||||
}
|
}
|
||||||
0x0008 => {
|
0x0008 => {
|
||||||
|
eprintln!("write \\b");
|
||||||
out.push('\\');
|
out.push('\\');
|
||||||
out.push('b');
|
out.push('b');
|
||||||
}
|
}
|
||||||
0x0009 => {
|
0x0009 => {
|
||||||
|
eprintln!("write \\t");
|
||||||
out.push('\\');
|
out.push('\\');
|
||||||
out.push('t');
|
out.push('t');
|
||||||
}
|
}
|
||||||
0x000a => {
|
0x000a => {
|
||||||
|
eprintln!("write \\n");
|
||||||
out.push('\\');
|
out.push('\\');
|
||||||
out.push('n');
|
out.push('n');
|
||||||
}
|
}
|
||||||
0x000C => {
|
0x000C => {
|
||||||
|
eprintln!("write \\f");
|
||||||
out.push('\\');
|
out.push('\\');
|
||||||
out.push('f');
|
out.push('f');
|
||||||
}
|
}
|
||||||
0x0000..=0x001F | 0x0080..=0xFFFF => {
|
0x0000..=0x001F | 0x0080..=0xFFFF => {
|
||||||
|
eprintln!("write \\u{c:04x}");
|
||||||
out.push_str(&format!("\\u{c:04x}"));
|
out.push_str(&format!("\\u{c:04x}"));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
eprintln!("write {}", char::from_u32(c as u32).unwrap());
|
||||||
out.push(char::from_u32(c as u32).unwrap());
|
out.push(char::from_u32(c as u32).unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.push_str(s); // TODO escape
|
eprintln!("write \"");
|
||||||
out.push('"');
|
out.push('"');
|
||||||
}
|
}
|
||||||
Value::Tagged(_) => {}
|
Value::Tagged(_) => {}
|
||||||
Value::Sequence(list) => {
|
Value::Sequence(list) => {
|
||||||
|
eprintln!("write [");
|
||||||
out.push('[');
|
out.push('[');
|
||||||
if !list.is_empty() {
|
if !list.is_empty() {
|
||||||
write_yaml_to_json(&list[0], out);
|
write_yaml_to_json(&list[0], out);
|
||||||
for i in 1..list.len() {
|
for i in 1..list.len() {
|
||||||
|
eprintln!("write ,");
|
||||||
out.push(',');
|
out.push(',');
|
||||||
write_yaml_to_json(&list[i], out);
|
write_yaml_to_json(&list[i], out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
eprintln!("write ]");
|
||||||
out.push(']');
|
out.push(']');
|
||||||
}
|
}
|
||||||
Value::Mapping(map) => {
|
Value::Mapping(map) => {
|
||||||
|
eprintln!("write {{");
|
||||||
out.push('{');
|
out.push('{');
|
||||||
let mut iter = map.iter();
|
let mut iter = map.iter();
|
||||||
if let Some(kv) = iter.next() {
|
if let Some(kv) = iter.next() {
|
||||||
write_yaml_to_json(&Value::String(yaml_to_json(kv.0)), out);
|
let key = match kv.0 {
|
||||||
|
Value::String(_) => kv.0,
|
||||||
|
_ => &Value::String(yaml_to_json(kv.0)),
|
||||||
|
};
|
||||||
|
write_yaml_to_json(key, out);
|
||||||
|
eprintln!("write :");
|
||||||
out.push(':');
|
out.push(':');
|
||||||
write_yaml_to_json(kv.1, out);
|
write_yaml_to_json(kv.1, out);
|
||||||
}
|
}
|
||||||
for kv in iter {
|
for kv in iter {
|
||||||
|
eprintln!("write ,");
|
||||||
out.push(',');
|
out.push(',');
|
||||||
write_yaml_to_json(&Value::String(yaml_to_json(kv.0)), out);
|
let key = match kv.0 {
|
||||||
|
Value::String(_) => kv.0,
|
||||||
|
_ => &Value::String(yaml_to_json(kv.0)),
|
||||||
|
};
|
||||||
|
write_yaml_to_json(key, out);
|
||||||
|
eprintln!("write :");
|
||||||
out.push(':');
|
out.push(':');
|
||||||
write_yaml_to_json(kv.1, out);
|
write_yaml_to_json(kv.1, out);
|
||||||
}
|
}
|
||||||
|
eprintln!("write }}");
|
||||||
out.push('}');
|
out.push('}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
eprintln!("written {:?}", yaml);
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-81
@@ -7,10 +7,7 @@ mod ssh;
|
|||||||
mod proto {
|
mod proto {
|
||||||
pub(crate) mod deltachat_remotecontrol_bot {
|
pub(crate) mod deltachat_remotecontrol_bot {
|
||||||
pub(crate) mod plugin {
|
pub(crate) mod plugin {
|
||||||
include!(concat!(
|
include!(concat!(env!("OUT_DIR"), "/deltachat_remotecontrol_bot.plugin.rs"));
|
||||||
env!("OUT_DIR"),
|
|
||||||
"/deltachat_remotecontrol_bot.plugin.rs"
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(crate) use deltachat_remotecontrol_bot::plugin::*;
|
pub(crate) use deltachat_remotecontrol_bot::plugin::*;
|
||||||
@@ -23,7 +20,7 @@ use deltachat::{
|
|||||||
EventType, Events,
|
EventType, Events,
|
||||||
chat::{self, ChatId},
|
chat::{self, ChatId},
|
||||||
config::Config,
|
config::Config,
|
||||||
contact::ContactId,
|
contact::{Contact, ContactId},
|
||||||
context::Context,
|
context::Context,
|
||||||
message::{Message, MsgId},
|
message::{Message, MsgId},
|
||||||
securejoin,
|
securejoin,
|
||||||
@@ -82,14 +79,9 @@ async fn run_bot(bot_context: Arc<Mutex<BotContext>>) -> AnyhowResult<()> {
|
|||||||
|
|
||||||
let dchat_db_filename = dchat_db_dir.join("deltachat.sqlite");
|
let dchat_db_filename = dchat_db_dir.join("deltachat.sqlite");
|
||||||
|
|
||||||
let dchat_ctx = Context::new(
|
let dchat_ctx = Context::new(&dchat_db_filename, 0, Events::default(), StockStrings::default())
|
||||||
&dchat_db_filename,
|
.await
|
||||||
0,
|
.context("Failed to open Delta Chat client DB")?;
|
||||||
Events::default(),
|
|
||||||
StockStrings::default(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("Failed to open Delta Chat client DB")?;
|
|
||||||
|
|
||||||
let ctx_lock = bot_context.lock().await;
|
let ctx_lock = bot_context.lock().await;
|
||||||
|
|
||||||
@@ -114,9 +106,7 @@ async fn run_bot(bot_context: Arc<Mutex<BotContext>>) -> AnyhowResult<()> {
|
|||||||
avatar_path = data_path().join(&avatar_path);
|
avatar_path = data_path().join(&avatar_path);
|
||||||
}
|
}
|
||||||
if let Some(avatar_str) = avatar_path.to_str() {
|
if let Some(avatar_str) = avatar_path.to_str() {
|
||||||
dchat_ctx
|
dchat_ctx.set_config(Config::Selfavatar, Some(avatar_str)).await?;
|
||||||
.set_config(Config::Selfavatar, Some(avatar_str))
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,12 +146,7 @@ async fn send_command_not_found(
|
|||||||
chat_id: ChatId,
|
chat_id: ChatId,
|
||||||
) -> AnyhowResult<()> {
|
) -> AnyhowResult<()> {
|
||||||
let dchat_ctx_lock = dchat_ctx.lock().await;
|
let dchat_ctx_lock = dchat_ctx.lock().await;
|
||||||
chat::send_text_msg(
|
chat::send_text_msg(&dchat_ctx_lock, chat_id, commands::global_usage(bot_ctx).await).await?;
|
||||||
&dchat_ctx_lock,
|
|
||||||
chat_id,
|
|
||||||
commands::global_usage(bot_ctx).await,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,9 +193,7 @@ async fn handle_message(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let Some(plugin) = ctx_lock.plugins.get(&cmd.plugin_id) else {
|
let Some(plugin) = ctx_lock.plugins.get(&cmd.plugin_id) else {
|
||||||
bail!(
|
bail!("Inconsistency in plugin_cmd_aliases hashmap: command references a plugin that is not loaded");
|
||||||
"Inconsistency in plugin_cmd_aliases hashmap: command references a plugin that is not loaded"
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
let (plugin_conn, plugin_name) = {
|
let (plugin_conn, plugin_name) = {
|
||||||
let lock = plugin.lock().await;
|
let lock = plugin.lock().await;
|
||||||
@@ -218,14 +201,16 @@ async fn handle_message(
|
|||||||
bail!("Plugin disconnected");
|
bail!("Plugin disconnected");
|
||||||
};
|
};
|
||||||
let plugin_name = lock.plugin_id.clone();
|
let plugin_name = lock.plugin_id.clone();
|
||||||
log::info!(
|
log::info!("Delegating command /{} to plugin {}", &cmd.name, &plugin_name);
|
||||||
"Delegating command /{} to plugin {}",
|
|
||||||
&cmd.name,
|
|
||||||
&plugin_name
|
|
||||||
);
|
|
||||||
(conn, plugin_name)
|
(conn, plugin_name)
|
||||||
};
|
};
|
||||||
let issuer_id = msg.get_from_id().to_string();
|
let contact_id = msg.get_from_id();
|
||||||
|
let dchat_ctx_lock = dchat_ctx.lock().await;
|
||||||
|
let issuer_id = Contact::get_by_id(&dchat_ctx_lock, contact_id)
|
||||||
|
.await
|
||||||
|
.map(|contact| contact.get_addr().to_string())
|
||||||
|
.unwrap_or_else(|_| contact_id.to_string());
|
||||||
|
drop(dchat_ctx_lock);
|
||||||
let cmd_name = cmd.name.clone();
|
let cmd_name = cmd.name.clone();
|
||||||
let chat_id = msg.get_chat_id();
|
let chat_id = msg.get_chat_id();
|
||||||
let dchat_ctx = Arc::clone(&dchat_ctx);
|
let dchat_ctx = Arc::clone(&dchat_ctx);
|
||||||
@@ -233,16 +218,13 @@ async fn handle_message(
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
log::debug!("Started plugin command task");
|
log::debug!("Started plugin command task");
|
||||||
match plugin_conn
|
match plugin_conn
|
||||||
.execute_command(
|
.execute_command(cmd_name.clone(), issuer_id, commands::args::split_command_line(&text))
|
||||||
cmd_name.clone(),
|
|
||||||
issuer_id,
|
|
||||||
commands::args::split_command_line(&text),
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(mut replies) => {
|
Ok(mut replies) => {
|
||||||
let mut last_message_id = None;
|
let mut last_message_id = None;
|
||||||
while let Some(reply) = replies.recv().await {
|
while let Some(reply) = replies.recv().await {
|
||||||
|
log::trace!("reply recv'ed: Some({:?})", reply);
|
||||||
match reply {
|
match reply {
|
||||||
Ok(reply) => {
|
Ok(reply) => {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
@@ -253,21 +235,15 @@ async fn handle_message(
|
|||||||
);
|
);
|
||||||
if reply.edit {
|
if reply.edit {
|
||||||
if let Some(msg_id) = last_message_id {
|
if let Some(msg_id) = last_message_id {
|
||||||
if let Some(proto::command_reply::Reply::Text(text)) =
|
if let Some(proto::command_reply::Reply::Text(text)) = reply.reply {
|
||||||
reply.reply
|
|
||||||
{
|
|
||||||
let dchat_ctx_lock = dchat_ctx.lock().await;
|
let dchat_ctx_lock = dchat_ctx.lock().await;
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Command {} :: /{}: editing last message",
|
"Command {} :: /{}: editing last message",
|
||||||
&plugin_name,
|
&plugin_name,
|
||||||
&cmd_name
|
&cmd_name
|
||||||
);
|
);
|
||||||
if let Err(e) = chat::send_edit_request(
|
if let Err(e) =
|
||||||
&dchat_ctx_lock,
|
chat::send_edit_request(&dchat_ctx_lock, msg_id, text).await
|
||||||
msg_id,
|
|
||||||
text,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
{
|
||||||
log::error!("Cannot edit message: {e}");
|
log::error!("Cannot edit message: {e}");
|
||||||
continue;
|
continue;
|
||||||
@@ -279,44 +255,27 @@ async fn handle_message(
|
|||||||
&plugin_name
|
&plugin_name
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else if let Some(proto::command_reply::Reply::Text(text)) = reply.reply {
|
||||||
if let Some(proto::command_reply::Reply::Text(text)) =
|
let dchat_ctx_lock = dchat_ctx.lock().await;
|
||||||
reply.reply
|
log::debug!("Command {} :: /{}: sending message", &plugin_name, &cmd_name);
|
||||||
|
let msg_id = match chat::send_text_msg(&dchat_ctx_lock, chat_id, text).await
|
||||||
{
|
{
|
||||||
let dchat_ctx_lock = dchat_ctx.lock().await;
|
Ok(msg_id) => msg_id,
|
||||||
log::debug!(
|
Err(e) => {
|
||||||
"Command {} :: /{}: sending message",
|
log::error!("Cannot send new message: {e}");
|
||||||
&plugin_name,
|
continue;
|
||||||
&cmd_name
|
}
|
||||||
);
|
};
|
||||||
let msg_id = match chat::send_text_msg(
|
last_message_id = Some(msg_id);
|
||||||
&dchat_ctx_lock,
|
|
||||||
chat_id,
|
|
||||||
text,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(msg_id) => msg_id,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Cannot send new message: {e}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
last_message_id = Some(msg_id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!(
|
log::error!("Error while executing {} :: /{}: {}", &plugin_name, &cmd_name, e);
|
||||||
"Error while executing {} :: /{}: {}",
|
|
||||||
&plugin_name,
|
|
||||||
&cmd_name,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log::debug!("{} :: /{} command execution finished", &plugin_name, &cmd_name);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!(
|
log::error!(
|
||||||
@@ -368,12 +327,7 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let requested_plugins: Vec<PluginConfig> = config
|
let requested_plugins: Vec<PluginConfig> = config.plugins.clone().into_iter().filter(|p| p.enabled).collect();
|
||||||
.plugins
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.filter(|p| p.enabled)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let bot_context = Arc::new(Mutex::new(BotContext::new(config)));
|
let bot_context = Arc::new(Mutex::new(BotContext::new(config)));
|
||||||
|
|
||||||
@@ -397,6 +351,10 @@ async fn main() {
|
|||||||
log::error!("Failed to load plugin \"{}\": {e}", &plugin.name);
|
log::error!("Failed to load plugin \"{}\": {e}", &plugin.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let ctx_lock = bot_context.lock().await;
|
||||||
|
log::debug!("Plugins: {:?}", &ctx_lock.plugins);
|
||||||
|
log::debug!("Plugin commands: {:?}", &ctx_lock.plugin_commands);
|
||||||
|
log::debug!("Plugin command aliases: {:?}", &ctx_lock.plugin_cmd_aliases);
|
||||||
}
|
}
|
||||||
|
|
||||||
run_bot(bot_context).await.expect("error");
|
run_bot(bot_context).await.expect("error");
|
||||||
|
|||||||
+2
-13
@@ -16,11 +16,7 @@ pub(crate) fn default_config_paths() -> Vec<PathBuf> {
|
|||||||
paths.push(config_home.join(APP_CONFIG_DIR).join(CONFIG_FILENAME));
|
paths.push(config_home.join(APP_CONFIG_DIR).join(CONFIG_FILENAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
paths.push(
|
paths.push(PathBuf::from("/etc").join(APP_CONFIG_DIR).join(CONFIG_FILENAME));
|
||||||
PathBuf::from("/etc")
|
|
||||||
.join(APP_CONFIG_DIR)
|
|
||||||
.join(CONFIG_FILENAME),
|
|
||||||
);
|
|
||||||
|
|
||||||
paths
|
paths
|
||||||
}
|
}
|
||||||
@@ -28,14 +24,7 @@ pub(crate) fn default_config_paths() -> Vec<PathBuf> {
|
|||||||
pub(crate) fn data_path() -> PathBuf {
|
pub(crate) fn data_path() -> PathBuf {
|
||||||
std::env::var("XDG_DATA_HOME")
|
std::env::var("XDG_DATA_HOME")
|
||||||
.map(|dh| PathBuf::from(dh).join(APP_DATA_DIR))
|
.map(|dh| PathBuf::from(dh).join(APP_DATA_DIR))
|
||||||
.or_else(|_| {
|
.or_else(|_| std::env::var("HOME").map(|h| PathBuf::from(h).join(".local").join("share").join(APP_DATA_DIR)))
|
||||||
std::env::var("HOME").map(|h| {
|
|
||||||
PathBuf::from(h)
|
|
||||||
.join(".local")
|
|
||||||
.join("share")
|
|
||||||
.join(APP_DATA_DIR)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.unwrap_or(PathBuf::from("."))
|
.unwrap_or(PathBuf::from("."))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-26
@@ -14,6 +14,7 @@ use tokio::sync::{Mutex, mpsc};
|
|||||||
|
|
||||||
use crate::{BotContext, paths::plugins_path, proto};
|
use crate::{BotContext, paths::plugins_path, proto};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub(crate) struct PluginCommand {
|
pub(crate) struct PluginCommand {
|
||||||
pub plugin_id: String,
|
pub plugin_id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -22,7 +23,7 @@ pub(crate) struct PluginCommand {
|
|||||||
pub description: String,
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, Debug)]
|
||||||
pub(crate) struct LoadedPlugin {
|
pub(crate) struct LoadedPlugin {
|
||||||
pub plugin_id: String,
|
pub plugin_id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -56,30 +57,24 @@ impl Display for PluginRequestType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub(crate) trait PluginConnection: Send + Sync {
|
pub(crate) trait PluginConnection: Send + Sync + Debug {
|
||||||
async fn initialize_plugin(
|
async fn initialize_plugin(&self, config: String)
|
||||||
&self,
|
-> Result<proto::PluginInitializeResponse, PluginConnectionError>;
|
||||||
config: String,
|
|
||||||
) -> Result<proto::PluginInitializeResponse, PluginConnectionError>;
|
|
||||||
|
|
||||||
async fn request_plugin_command_list(
|
async fn request_plugin_command_list(&self) -> Result<proto::PluginCommandListResponse, PluginConnectionError>;
|
||||||
&self,
|
|
||||||
) -> Result<proto::PluginCommandListResponse, PluginConnectionError>;
|
|
||||||
|
|
||||||
async fn execute_command(
|
async fn execute_command(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
command_id: String,
|
command_id: String,
|
||||||
issuer_id: String,
|
issuer_id: String,
|
||||||
argv: Vec<String>,
|
argv: Vec<String>,
|
||||||
) -> Result<
|
) -> Result<mpsc::Receiver<Result<proto::CommandReply, PluginConnectionError>>, PluginConnectionError>;
|
||||||
mpsc::Receiver<Result<proto::CommandReply, PluginConnectionError>>,
|
|
||||||
PluginConnectionError,
|
|
||||||
>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum PluginConnectionError {
|
pub(crate) enum PluginConnectionError {
|
||||||
SendRequest(String, PluginRequestType, Box<dyn Error + Send + Sync>),
|
SendRequest(String, PluginRequestType, Box<dyn Error + Send + Sync>),
|
||||||
|
Timeout(String, PluginRequestType),
|
||||||
ReadResponse(String, PluginRequestType, Box<dyn Error + Send + Sync>),
|
ReadResponse(String, PluginRequestType, Box<dyn Error + Send + Sync>),
|
||||||
DecodeResponse(String, PluginRequestType, Option<DecodeError>),
|
DecodeResponse(String, PluginRequestType, Option<DecodeError>),
|
||||||
InvalidMessageLength,
|
InvalidMessageLength,
|
||||||
@@ -91,6 +86,9 @@ impl Display for PluginConnectionError {
|
|||||||
Self::SendRequest(plugin_name, req_type, e) => f.write_fmt(format_args!(
|
Self::SendRequest(plugin_name, req_type, e) => f.write_fmt(format_args!(
|
||||||
"Can't send request ({req_type}) to plugin {plugin_name}: {e}"
|
"Can't send request ({req_type}) to plugin {plugin_name}: {e}"
|
||||||
)),
|
)),
|
||||||
|
Self::Timeout(plugin_name, req_type) => f.write_fmt(format_args!(
|
||||||
|
"Timed out waiting for response to {req_type} request to plugin {plugin_name}"
|
||||||
|
)),
|
||||||
Self::ReadResponse(plugin_name, resp_type, e) => f.write_fmt(format_args!(
|
Self::ReadResponse(plugin_name, resp_type, e) => f.write_fmt(format_args!(
|
||||||
"Can't read response ({resp_type}) from plugin {plugin_name}: {e}"
|
"Can't read response ({resp_type}) from plugin {plugin_name}: {e}"
|
||||||
)),
|
)),
|
||||||
@@ -107,6 +105,7 @@ impl Error for PluginConnectionError {
|
|||||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
match self {
|
match self {
|
||||||
Self::SendRequest(_, _, e) => Some(e.as_ref()),
|
Self::SendRequest(_, _, e) => Some(e.as_ref()),
|
||||||
|
Self::Timeout(_, _) => None,
|
||||||
Self::ReadResponse(_, _, e) => Some(e.as_ref()),
|
Self::ReadResponse(_, _, e) => Some(e.as_ref()),
|
||||||
Self::DecodeResponse(_, _, e) => e.as_ref().map(|e| e as &dyn Error),
|
Self::DecodeResponse(_, _, e) => e.as_ref().map(|e| e as &dyn Error),
|
||||||
Self::InvalidMessageLength => None,
|
Self::InvalidMessageLength => None,
|
||||||
@@ -137,8 +136,7 @@ pub(crate) async fn try_load_plugin(
|
|||||||
cmd.env("DCRCBOT_PLUGIN_TRANSPORT", "stdio");
|
cmd.env("DCRCBOT_PLUGIN_TRANSPORT", "stdio");
|
||||||
|
|
||||||
let plugin_process = cmd.spawn().context("Failed to start the plugin")?;
|
let plugin_process = cmd.spawn().context("Failed to start the plugin")?;
|
||||||
let plugin =
|
let plugin = stdio::initialize_stdio_plugin(plugin_process, unique_name.clone(), config_json).await?;
|
||||||
stdio::initialize_stdio_plugin(plugin_process, unique_name.clone(), config_json).await?;
|
|
||||||
|
|
||||||
let mut ctx_lock = ctx.lock().await;
|
let mut ctx_lock = ctx.lock().await;
|
||||||
for cmd in plugin.lock().await.commands.iter().cloned() {
|
for cmd in plugin.lock().await.commands.iter().cloned() {
|
||||||
@@ -146,12 +144,8 @@ pub(crate) async fn try_load_plugin(
|
|||||||
if ctx_lock.plugin_commands.contains_key(&cmd.name) {
|
if ctx_lock.plugin_commands.contains_key(&cmd.name) {
|
||||||
bail!("duplicate command specification");
|
bail!("duplicate command specification");
|
||||||
}
|
}
|
||||||
ctx_lock
|
ctx_lock.plugin_commands.insert(cmd.name.clone(), Arc::clone(&cmd));
|
||||||
.plugin_commands
|
ctx_lock.plugin_cmd_aliases.insert(cmd.name.clone(), Arc::clone(&cmd));
|
||||||
.insert(cmd.name.clone(), Arc::clone(&cmd));
|
|
||||||
ctx_lock
|
|
||||||
.plugin_cmd_aliases
|
|
||||||
.insert(cmd.name.clone(), Arc::clone(&cmd));
|
|
||||||
for alias in cmd.aliases.iter() {
|
for alias in cmd.aliases.iter() {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"adding command alias /{} -> /{} of plugin {}",
|
"adding command alias /{} -> /{} of plugin {}",
|
||||||
@@ -159,13 +153,11 @@ pub(crate) async fn try_load_plugin(
|
|||||||
&cmd.name,
|
&cmd.name,
|
||||||
&unique_name
|
&unique_name
|
||||||
);
|
);
|
||||||
ctx_lock
|
ctx_lock.plugin_cmd_aliases.insert(alias.to_owned(), Arc::clone(&cmd));
|
||||||
.plugin_cmd_aliases
|
|
||||||
.insert(alias.to_owned(), Arc::clone(&cmd));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drop(ctx_lock);
|
|
||||||
|
|
||||||
ctx.lock().await.plugins.insert(unique_name, plugin);
|
let real_plugin_id = plugin.lock().await.plugin_id.clone();
|
||||||
|
ctx_lock.plugins.insert(real_plugin_id, plugin);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+118
-100
@@ -10,9 +10,7 @@ use tokio::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
plugin::{
|
plugin::{LoadedPlugin, PluginCommand, PluginConnection, PluginConnectionError, PluginRequestType},
|
||||||
LoadedPlugin, PluginCommand, PluginConnection, PluginConnectionError, PluginRequestType,
|
|
||||||
},
|
|
||||||
proto,
|
proto,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -23,7 +21,6 @@ pub(super) async fn initialize_stdio_plugin(
|
|||||||
) -> AnyhowResult<Arc<Mutex<LoadedPlugin>>> {
|
) -> AnyhowResult<Arc<Mutex<LoadedPlugin>>> {
|
||||||
let plugin = Arc::new(Mutex::new(LoadedPlugin::default()));
|
let plugin = Arc::new(Mutex::new(LoadedPlugin::default()));
|
||||||
log::info!("Connecting to plugin {} using standard I/O", &unique_name);
|
log::info!("Connecting to plugin {} using standard I/O", &unique_name);
|
||||||
plugin.lock().await.plugin_id = unique_name;
|
|
||||||
let connection = Arc::new(StdioPluginConnection::new(Arc::clone(&plugin), process));
|
let connection = Arc::new(StdioPluginConnection::new(Arc::clone(&plugin), process));
|
||||||
Arc::clone(&connection).run_stdio_loops();
|
Arc::clone(&connection).run_stdio_loops();
|
||||||
|
|
||||||
@@ -58,12 +55,19 @@ pub(super) async fn initialize_stdio_plugin(
|
|||||||
Ok(plugin)
|
Ok(plugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ResponseListener {
|
||||||
|
Oneshot(oneshot::Sender<proto::Response>),
|
||||||
|
Multishot(mpsc::Sender<proto::Response>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct StdioPluginConnection {
|
struct StdioPluginConnection {
|
||||||
plugin: Arc<Mutex<LoadedPlugin>>,
|
plugin: Arc<Mutex<LoadedPlugin>>,
|
||||||
process: Mutex<Child>,
|
process: Mutex<Child>,
|
||||||
buffered_stdout: Mutex<BufReader<ChildStdout>>,
|
buffered_stdout: Mutex<BufReader<ChildStdout>>,
|
||||||
next_request_id: Mutex<u32>,
|
next_request_id: Mutex<u32>,
|
||||||
pending_requests: Mutex<HashMap<u32, oneshot::Sender<proto::Response>>>,
|
pending_requests: Mutex<HashMap<u32, ResponseListener>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StdioPluginConnection {
|
impl StdioPluginConnection {
|
||||||
@@ -85,19 +89,16 @@ impl StdioPluginConnection {
|
|||||||
|
|
||||||
async fn stdout_reader_loop(self: Arc<Self>) {
|
async fn stdout_reader_loop(self: Arc<Self>) {
|
||||||
loop {
|
loop {
|
||||||
let frame =
|
let frame = match Self::read_length_delimited(self.buffered_stdout.lock().await.deref_mut()).await {
|
||||||
match Self::read_length_delimited(self.buffered_stdout.lock().await.deref_mut())
|
Ok(frame) => frame,
|
||||||
.await
|
Err(e) => {
|
||||||
{
|
log::error!(
|
||||||
Ok(frame) => frame,
|
"Error while reading STDOUT of stdio plugin {}: {e}",
|
||||||
Err(e) => {
|
&self.plugin.lock().await.plugin_id
|
||||||
log::error!(
|
);
|
||||||
"Error while reading STDOUT of stdio plugin {}: {e}",
|
break;
|
||||||
&self.plugin.lock().await.plugin_id
|
}
|
||||||
);
|
};
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let response = match proto::Response::decode(frame.as_slice()) {
|
let response = match proto::Response::decode(frame.as_slice()) {
|
||||||
Ok(response) => response,
|
Ok(response) => response,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -108,18 +109,29 @@ impl StdioPluginConnection {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match self
|
let mut pending_req_lock = self.pending_requests.lock().await;
|
||||||
.pending_requests
|
match pending_req_lock.get(&response.request_id) {
|
||||||
.lock()
|
Some(ResponseListener::Oneshot(_)) => {
|
||||||
.await
|
// We must remove to be able to send the response to the oneshot channel
|
||||||
.remove(&response.request_id)
|
if let Some(ResponseListener::Oneshot(sender)) = pending_req_lock.remove(&response.request_id) {
|
||||||
{
|
match sender.send(response) {
|
||||||
Some(sender) => match sender.send(response) {
|
Ok(()) => {}
|
||||||
|
Err(response) => {
|
||||||
|
log::warn!(
|
||||||
|
"Dropping response with request_id {} from plugin {}",
|
||||||
|
response.request_id,
|
||||||
|
self.plugin.lock().await.plugin_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(ResponseListener::Multishot(sender)) => match sender.send(response).await {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(response) => {
|
Err(e) => {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"Dropping response with request_id {} from plugin {}",
|
"Dropping response with request_id {} from plugin {}",
|
||||||
response.request_id,
|
e.0.request_id,
|
||||||
self.plugin.lock().await.plugin_id
|
self.plugin.lock().await.plugin_id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -133,9 +145,7 @@ impl StdioPluginConnection {
|
|||||||
self.pending_requests.lock().await.clear();
|
self.pending_requests.lock().await.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read_length_delimited<R>(
|
async fn read_length_delimited<R>(reader: &mut R) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>>
|
||||||
reader: &mut R,
|
|
||||||
) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>>
|
|
||||||
where
|
where
|
||||||
R: AsyncRead + Unpin,
|
R: AsyncRead + Unpin,
|
||||||
{
|
{
|
||||||
@@ -162,21 +172,6 @@ impl StdioPluginConnection {
|
|||||||
reader.read_exact(&mut message).await?;
|
reader.read_exact(&mut message).await?;
|
||||||
Ok(message)
|
Ok(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn await_response_to(
|
|
||||||
&self,
|
|
||||||
request_id: u32,
|
|
||||||
timeout: Duration,
|
|
||||||
) -> Result<proto::Response, Box<dyn Error + Send + Sync>> {
|
|
||||||
let (tx, rx) = oneshot::channel();
|
|
||||||
self.pending_requests.lock().await.insert(request_id, tx);
|
|
||||||
|
|
||||||
match tokio::time::timeout(timeout, rx).await {
|
|
||||||
Ok(Ok(response)) => Ok(response),
|
|
||||||
Ok(Err(e)) => Err(Box::new(e)),
|
|
||||||
Err(e) => Err(Box::new(e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -185,20 +180,31 @@ impl PluginConnection for StdioPluginConnection {
|
|||||||
&self,
|
&self,
|
||||||
config: String,
|
config: String,
|
||||||
) -> Result<proto::PluginInitializeResponse, PluginConnectionError> {
|
) -> Result<proto::PluginInitializeResponse, PluginConnectionError> {
|
||||||
|
// Generate request id
|
||||||
let request_id = {
|
let request_id = {
|
||||||
let mut r = self.next_request_id.lock().await;
|
let mut r = self.next_request_id.lock().await;
|
||||||
let id = *r;
|
let id = *r;
|
||||||
*r += 1;
|
*r += 1;
|
||||||
id
|
id
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Encode request
|
||||||
let request = proto::Request {
|
let request = proto::Request {
|
||||||
request_id,
|
request_id,
|
||||||
req: Some(proto::request::Req::InitializeReq(
|
req: Some(proto::request::Req::InitializeReq(proto::PluginInitializeRequest {
|
||||||
proto::PluginInitializeRequest { config },
|
config,
|
||||||
)),
|
})),
|
||||||
}
|
}
|
||||||
.encode_length_delimited_to_vec();
|
.encode_length_delimited_to_vec();
|
||||||
|
|
||||||
|
// Add response listener
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
self.pending_requests
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.insert(request_id, ResponseListener::Oneshot(tx));
|
||||||
|
|
||||||
|
// Send request
|
||||||
if let Err(e) = self
|
if let Err(e) = self
|
||||||
.process
|
.process
|
||||||
.lock()
|
.lock()
|
||||||
@@ -216,16 +222,20 @@ impl PluginConnection for StdioPluginConnection {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = match self
|
// Receive response
|
||||||
.await_response_to(request_id, Duration::from_secs(10))
|
let response = match tokio::time::timeout(Duration::from_secs(10), rx).await {
|
||||||
.await
|
Ok(Ok(response)) => response,
|
||||||
{
|
Ok(Err(e)) => {
|
||||||
Ok(response) => response,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(PluginConnectionError::ReadResponse(
|
return Err(PluginConnectionError::ReadResponse(
|
||||||
self.plugin.lock().await.plugin_id.clone(),
|
self.plugin.lock().await.plugin_id.clone(),
|
||||||
PluginRequestType::Initialize,
|
PluginRequestType::Initialize,
|
||||||
e,
|
Box::new(e),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
return Err(PluginConnectionError::Timeout(
|
||||||
|
self.plugin.lock().await.plugin_id.clone(),
|
||||||
|
PluginRequestType::Initialize,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -240,23 +250,30 @@ impl PluginConnection for StdioPluginConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn request_plugin_command_list(
|
async fn request_plugin_command_list(&self) -> Result<proto::PluginCommandListResponse, PluginConnectionError> {
|
||||||
&self,
|
// Generate request id
|
||||||
) -> Result<proto::PluginCommandListResponse, PluginConnectionError> {
|
|
||||||
let request_id = {
|
let request_id = {
|
||||||
let mut r = self.next_request_id.lock().await;
|
let mut r = self.next_request_id.lock().await;
|
||||||
let id = *r;
|
let id = *r;
|
||||||
*r += 1;
|
*r += 1;
|
||||||
id
|
id
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Encode request
|
||||||
let request = proto::Request {
|
let request = proto::Request {
|
||||||
request_id,
|
request_id,
|
||||||
req: Some(proto::request::Req::CommandListReq(
|
req: Some(proto::request::Req::CommandListReq(proto::PluginCommandListRequest {})),
|
||||||
proto::PluginCommandListRequest {},
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
.encode_length_delimited_to_vec();
|
.encode_length_delimited_to_vec();
|
||||||
|
|
||||||
|
// Add response listener
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
self.pending_requests
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.insert(request_id, ResponseListener::Oneshot(tx));
|
||||||
|
|
||||||
|
// Send request
|
||||||
if let Err(e) = self
|
if let Err(e) = self
|
||||||
.process
|
.process
|
||||||
.lock()
|
.lock()
|
||||||
@@ -274,16 +291,20 @@ impl PluginConnection for StdioPluginConnection {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = match self
|
// Receive response
|
||||||
.await_response_to(request_id, Duration::from_secs(10))
|
let response = match tokio::time::timeout(Duration::from_secs(10), rx).await {
|
||||||
.await
|
Ok(Ok(response)) => response,
|
||||||
{
|
Ok(Err(e)) => {
|
||||||
Ok(response) => response,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(PluginConnectionError::ReadResponse(
|
return Err(PluginConnectionError::ReadResponse(
|
||||||
self.plugin.lock().await.plugin_id.clone(),
|
self.plugin.lock().await.plugin_id.clone(),
|
||||||
PluginRequestType::CommandList,
|
PluginRequestType::CommandList,
|
||||||
e,
|
Box::new(e),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
return Err(PluginConnectionError::Timeout(
|
||||||
|
self.plugin.lock().await.plugin_id.clone(),
|
||||||
|
PluginRequestType::CommandList,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -303,28 +324,34 @@ impl PluginConnection for StdioPluginConnection {
|
|||||||
command_id: String,
|
command_id: String,
|
||||||
issuer_id: String,
|
issuer_id: String,
|
||||||
argv: Vec<String>,
|
argv: Vec<String>,
|
||||||
) -> Result<
|
) -> Result<mpsc::Receiver<Result<proto::CommandReply, PluginConnectionError>>, PluginConnectionError> {
|
||||||
mpsc::Receiver<Result<proto::CommandReply, PluginConnectionError>>,
|
// Generate request id
|
||||||
PluginConnectionError,
|
|
||||||
> {
|
|
||||||
let request_id = {
|
let request_id = {
|
||||||
let mut r = self.next_request_id.lock().await;
|
let mut r = self.next_request_id.lock().await;
|
||||||
let id = *r;
|
let id = *r;
|
||||||
*r += 1;
|
*r += 1;
|
||||||
id
|
id
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Encode request
|
||||||
let request = proto::Request {
|
let request = proto::Request {
|
||||||
request_id,
|
request_id,
|
||||||
req: Some(proto::request::Req::ExecuteReq(
|
req: Some(proto::request::Req::ExecuteReq(proto::PluginExecuteRequest {
|
||||||
proto::PluginExecuteRequest {
|
command_id,
|
||||||
command_id,
|
issuer_id,
|
||||||
issuer_id,
|
arg_vector: argv,
|
||||||
arg_vector: argv,
|
})),
|
||||||
},
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
.encode_length_delimited_to_vec();
|
.encode_length_delimited_to_vec();
|
||||||
|
|
||||||
|
// Add response listener
|
||||||
|
let (tx, mut rx) = mpsc::channel(4);
|
||||||
|
self.pending_requests
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.insert(request_id, ResponseListener::Multishot(tx));
|
||||||
|
|
||||||
|
// Send request
|
||||||
if let Err(e) = self
|
if let Err(e) = self
|
||||||
.process
|
.process
|
||||||
.lock()
|
.lock()
|
||||||
@@ -342,26 +369,14 @@ impl PluginConnection for StdioPluginConnection {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel(4);
|
// Receive responses
|
||||||
|
let (tx_out, rx_out) = mpsc::channel(4);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
let response = match self
|
let response = match rx.recv().await {
|
||||||
.await_response_to(request_id, Duration::from_secs(600))
|
Some(response) => response,
|
||||||
.await
|
None => {
|
||||||
{
|
log::error!("Response channel for request {request_id} closed unexpectedly");
|
||||||
Ok(response) => response,
|
|
||||||
Err(e) => {
|
|
||||||
if let Err(e) = tx
|
|
||||||
.send(Err(PluginConnectionError::ReadResponse(
|
|
||||||
self.plugin.lock().await.plugin_id.clone(),
|
|
||||||
PluginRequestType::Execute,
|
|
||||||
e,
|
|
||||||
)))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
log::error!("Cannot send error notification to another task: {e}");
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -372,7 +387,8 @@ impl PluginConnection for StdioPluginConnection {
|
|||||||
Some(proto::command_reply::Reply::End(_)) => true,
|
Some(proto::command_reply::Reply::End(_)) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
if let Err(e) = tx.send(Ok(reply)).await {
|
log::trace!("reply sent: Ok({:?})", &reply);
|
||||||
|
if let Err(e) = tx_out.send(Ok(reply)).await {
|
||||||
log::error!("Cannot send command reply to another task: {e}");
|
log::error!("Cannot send command reply to another task: {e}");
|
||||||
}
|
}
|
||||||
if end {
|
if end {
|
||||||
@@ -380,7 +396,7 @@ impl PluginConnection for StdioPluginConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if let Err(e) = tx
|
if let Err(e) = tx_out
|
||||||
.send(Err(PluginConnectionError::DecodeResponse(
|
.send(Err(PluginConnectionError::DecodeResponse(
|
||||||
self.plugin.lock().await.plugin_id.clone(),
|
self.plugin.lock().await.plugin_id.clone(),
|
||||||
PluginRequestType::Execute,
|
PluginRequestType::Execute,
|
||||||
@@ -394,8 +410,10 @@ impl PluginConnection for StdioPluginConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Remove response listener
|
||||||
|
self.pending_requests.lock().await.remove_entry(&request_id);
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(rx)
|
Ok(rx_out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-19
@@ -36,11 +36,7 @@ pub(crate) async fn connect_ssh<A: ToSocketAddrs + Debug, K: AsRef<[u8]>, P: Int
|
|||||||
"Connecting to SSH {:?} {} key & {} password",
|
"Connecting to SSH {:?} {} key & {} password",
|
||||||
&addr,
|
&addr,
|
||||||
if key.is_some() { "with" } else { "without" },
|
if key.is_some() { "with" } else { "without" },
|
||||||
if password.is_some() {
|
if password.is_some() { "with" } else { "without" }
|
||||||
"with"
|
|
||||||
} else {
|
|
||||||
"without"
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
let mut connection = tokio::time::timeout(
|
let mut connection = tokio::time::timeout(
|
||||||
connect_timeout,
|
connect_timeout,
|
||||||
@@ -65,12 +61,7 @@ pub(crate) async fn connect_ssh<A: ToSocketAddrs + Debug, K: AsRef<[u8]>, P: Int
|
|||||||
if !auth_success {
|
if !auth_success {
|
||||||
if let Some(key) = key {
|
if let Some(key) = key {
|
||||||
log::info!("Trying to authenticate using key");
|
log::info!("Trying to authenticate using key");
|
||||||
match tokio::time::timeout(
|
match tokio::time::timeout(auth_timeout, connection.authenticate_publickey(username, key)).await {
|
||||||
auth_timeout,
|
|
||||||
connection.authenticate_publickey(username, key),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(Ok(AuthResult::Success)) => {
|
Ok(Ok(AuthResult::Success)) => {
|
||||||
auth_success = true;
|
auth_success = true;
|
||||||
}
|
}
|
||||||
@@ -78,8 +69,7 @@ pub(crate) async fn connect_ssh<A: ToSocketAddrs + Debug, K: AsRef<[u8]>, P: Int
|
|||||||
remaining_methods,
|
remaining_methods,
|
||||||
partial_success,
|
partial_success,
|
||||||
})) => {
|
})) => {
|
||||||
if partial_success || !remaining_methods.contains(&russh::MethodKind::Password)
|
if partial_success || !remaining_methods.contains(&russh::MethodKind::Password) {
|
||||||
{
|
|
||||||
if partial_success {
|
if partial_success {
|
||||||
anyhow::bail!("SSH auth failed: multi-factor auth is not supported");
|
anyhow::bail!("SSH auth failed: multi-factor auth is not supported");
|
||||||
} else {
|
} else {
|
||||||
@@ -102,12 +92,7 @@ pub(crate) async fn connect_ssh<A: ToSocketAddrs + Debug, K: AsRef<[u8]>, P: Int
|
|||||||
if !auth_success {
|
if !auth_success {
|
||||||
if let Some(password) = password {
|
if let Some(password) = password {
|
||||||
log::info!("Trying to authenticate using password");
|
log::info!("Trying to authenticate using password");
|
||||||
match tokio::time::timeout(
|
match tokio::time::timeout(auth_timeout, connection.authenticate_password(username, password)).await {
|
||||||
auth_timeout,
|
|
||||||
connection.authenticate_password(username, password),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(Ok(AuthResult::Success)) => {
|
Ok(Ok(AuthResult::Success)) => {
|
||||||
auth_success = true;
|
auth_success = true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user