From d2ee6168fbb75627be4aa2916a7d7fb891e34a6a Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Tue, 23 Mar 2021 21:17:00 -0400 Subject: [PATCH] Add --raw option to support sending a stream of raw XML --- .rustfmt.toml | 1 + Cargo.lock | 47 +++++++------ Cargo.toml | 4 +- src/main.rs | 180 ++++++++++++++++++++++++++++++++------------------ 4 files changed, 146 insertions(+), 86 deletions(-) create mode 100644 .rustfmt.toml diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..ffc467d --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1 @@ +max_width = 200 diff --git a/Cargo.lock b/Cargo.lock index f8da4f4..0045c53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,9 +180,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993a608597367c6377b258c25d7120740f00ed23a2252b729b1932dd7866f908" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "die" @@ -749,14 +749,14 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.1.57", + "redox_syscall 0.2.4", "smallvec", "winapi", ] @@ -1048,9 +1048,9 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397d411cfc34097bdb176984180f7d9980eb161aaf1368ceea96c9834d8f8b85" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" dependencies = [ "semver", ] @@ -1144,6 +1144,7 @@ dependencies = [ "serde", "serde_derive", "tokio", + "tokio-native-tls", "tokio-xmpp", "toml", "xmpp-parsers", @@ -1346,9 +1347,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f" +checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" dependencies = [ "tinyvec_macros", ] @@ -1361,12 +1362,14 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.0.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca04cec6ff2474c638057b65798f60ac183e5e79d3448bb7163d36a39cff6ec" +checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722" dependencies = [ "autocfg", + "bytes", "libc", + "memchr", "mio", "num_cpus", "pin-project-lite", @@ -1375,9 +1378,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42517d2975ca3114b22a16192634e8241dc5cc1f130be194645970cc1c371494" +checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" dependencies = [ "proc-macro2", "quote", @@ -1396,9 +1399,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76066865172052eb8796c686f0b441a93df8b08d40a950b062ffb9a426f00edd" +checksum = "e177a5d8c3bf36de9ebe6d58537d8879e964332f93fb3339e43f618c81361af0" dependencies = [ "futures-core", "pin-project-lite", @@ -1407,9 +1410,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.1" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ae4751faa60b9f96dd8344d74592e5a17c0c9a220413dbc6942d14139bbfcc" +checksum = "5143d049e85af7fbc36f5454d990e62c2df705b3589f123b71f441b6b59f443f" dependencies = [ "bytes", "futures-core", @@ -1417,7 +1420,6 @@ dependencies = [ "log", "pin-project-lite", "tokio", - "tokio-stream", ] [[package]] @@ -1454,9 +1456,9 @@ dependencies = [ [[package]] name = "trust-dns-proto" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98a0381b2864c2978db7f8e17c7b23cca5a3a5f99241076e13002261a8ecbabd" +checksum = "8d57e219ba600dd96c2f6d82eb79645068e14edbc5c7e27514af40436b88150c" dependencies = [ "async-trait", "cfg-if 1.0.0", @@ -1472,15 +1474,16 @@ dependencies = [ "rand 0.8.2", "smallvec", "thiserror", + "tinyvec", "tokio", "url", ] [[package]] name = "trust-dns-resolver" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3072d18c10bd621cb00507d59cfab5517862285c353160366e37fbf4c74856e4" +checksum = "b0437eea3a6da51acc1e946545ff53d5b8fb2611ff1c3bed58522dde100536ae" dependencies = [ "cfg-if 1.0.0", "futures-util", diff --git a/Cargo.toml b/Cargo.toml index a2d0cc7..e4c1833 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,9 @@ gumdrop = "0.8.0" gumdrop_derive = "0.8.0" dirs = "3.0.1" tokio-xmpp = "3.0.0" -tokio = { version = "1", features = ["net", "rt", "rt-multi-thread", "macros"] } +tokio = { version = "1", features = ["net", "rt", "rt-multi-thread", "macros", "io-util", "io-std"] } +tokio-tls = { package = "tokio-native-tls", version = "0.3" } xmpp-parsers = "0.18" die = "0.2.0" anyhow = "1.0" + diff --git a/src/main.rs b/src/main.rs index f085dc6..659e0c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,12 +8,16 @@ use die::{die, Die}; use gumdrop::Options; use serde_derive::Deserialize; -use tokio_xmpp::SimpleClient as Client; -use xmpp_parsers::message::{Body, Message}; -use xmpp_parsers::{Element, Jid}; use std::process::{Command, Stdio}; +use tokio_xmpp::{xmpp_stream, SimpleClient as Client}; +use xmpp_parsers::message::{Body, Message}; +use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType}; +use xmpp_parsers::{Element, Jid}; -use anyhow::{Result, bail, anyhow}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio_tls::TlsStream; + +use anyhow::{anyhow, bail, Result}; #[derive(Deserialize)] struct Config { @@ -36,9 +40,7 @@ struct MyOptions { #[options(help = "show this help message and exit")] help: bool, - #[options( - help = "path to config file. default: ~/.config/sendxmpp.toml with fallback to /etc/sendxmpp/sendxmpp.toml" - )] + #[options(help = "path to config file. default: ~/.config/sendxmpp.toml with fallback to /etc/sendxmpp/sendxmpp.toml")] config: Option, #[options(help = "Force OpenPGP encryption for all recipients", short = "e")] @@ -46,6 +48,12 @@ struct MyOptions { #[options(help = "Attempt OpenPGP encryption for all recipients")] attempt_pgp: bool, + + #[options(help = "Send raw XML stream, cannot be used with recipients or PGP")] + raw: bool, + + #[options(help = "Send a after connecting before sending messages, required for receiving for --raw")] + presence: bool, } #[tokio::main] @@ -55,30 +63,23 @@ async fn main() { // Remember to skip the first argument. That's the program name. let opts = match MyOptions::parse_args_default(&args[1..]) { Ok(opts) => opts, - Err(e) => die!( - "{}: {}\nUsage: {} [OPTIONS] [ARGUMENTS]\n\n{}", - args[0], - e, - args[0], - MyOptions::usage() - ), + Err(e) => die!("{}: {}\nUsage: {} [OPTIONS] [ARGUMENTS]\n\n{}", args[0], e, args[0], MyOptions::usage()), }; if opts.help { - die!( - "Usage: {} [OPTIONS] [ARGUMENTS]\n\n{}", - args[0], - MyOptions::usage() - ); + die!("Usage: {} [OPTIONS] [ARGUMENTS]\n\n{}", args[0], MyOptions::usage()); } - let recipients: Vec = opts - .recipients - .iter() - .map(|s| s.parse::().die("invalid recipient jid")) - .collect(); + let recipients: Vec = opts.recipients.iter().map(|s| s.parse::().die("invalid recipient jid")).collect(); - if recipients.is_empty() { + if opts.raw { + if opts.force_pgp || opts.attempt_pgp { + die!("--raw is incompatible with --force-pgp and --attempt-pgp"); + } + if !recipients.is_empty() { + die!("--raw is incompatible with recipients"); + } + } else if recipients.is_empty() { die!("no recipients specified!"); } @@ -86,50 +87,103 @@ async fn main() { let cfg = match opts.config { Some(config) => parse_cfg(&config).die("provided config cannot be found/parsed"), - None => parse_cfg( - dirs::config_dir() - .die("cannot find home directory") - .join("sendxmpp.toml"), - ) - .or_else(|_| parse_cfg("/etc/sendxmpp/sendxmpp.toml")) - .die("valid config file not found"), + None => parse_cfg(dirs::config_dir().die("cannot find home directory").join("sendxmpp.toml")) + .or_else(|_| parse_cfg("/etc/sendxmpp/sendxmpp.toml")) + .die("valid config file not found"), }; - let mut data = String::new(); - stdin() - .read_to_string(&mut data) - .die("error reading from stdin"); - let data = data.trim(); + if opts.raw { + let mut client = Client::new(&cfg.jid, &cfg.password).await.die("could not connect to xmpp server"); - // Client instance - let mut client = Client::new(&cfg.jid, &cfg.password).await.die("could not connect to xmpp server"); + if opts.presence { + client.send_stanza(make_presence()).await.die("could not send presence"); + } - for recipient in recipients { - let reply = if opts.force_pgp || opts.attempt_pgp { - let encrypted = gpg_encrypt(recipient.clone(), &data); - if encrypted.is_err() { - if opts.force_pgp { - die!("pgp encryption to jid '{}' failed!", recipient); + // can paste this to test: woot + + pub struct OpenClient { + pub stream: xmpp_stream::XMPPStream>, + } + let client: OpenClient = unsafe { std::mem::transmute(client) }; + let mut open_client = client.stream.stream.try_lock().die("could not lock client stream"); + let open_client = open_client.get_mut(); + + let mut rd_buf = [0u8; 256]; // todo: proper buffer size? + let mut stdin_buf = rd_buf.clone(); + + let mut stdin = tokio::io::stdin(); + let mut stdout = tokio::io::stdout(); + + loop { + tokio::select! { + n = open_client.read(&mut rd_buf) => { + let n = n.unwrap_or(0); + if n == 0 { + break; + } + stdout.write_all(&rd_buf[0..n]).await.die("could not send bytes"); + stdout.flush().await.die("could not flush"); + }, + n = stdin.read(&mut stdin_buf) => { + let n = n.unwrap_or(0); + if n == 0 { + break; + } + open_client.write_all(&stdin_buf[0..n]).await.die("could not send bytes"); + open_client.flush().await.die("could not flush"); + }, + } + } + + // Close client connection, ignoring errors + open_client.write_all("".as_bytes()).await.ok(); + open_client.flush().await.ok(); + open_client.read(&mut rd_buf).await.ok(); + } else { + let mut data = String::new(); + stdin().lock().read_to_string(&mut data).die("error reading from stdin"); + let data = data.trim(); + + let mut client = Client::new(&cfg.jid, &cfg.password).await.die("could not connect to xmpp server"); + + if opts.presence { + client.send_stanza(make_presence()).await.die("could not send presence"); + } + + for recipient in recipients { + let reply = if opts.force_pgp || opts.attempt_pgp { + let encrypted = gpg_encrypt(recipient.clone(), &data); + if encrypted.is_err() { + if opts.force_pgp { + die!("pgp encryption to jid '{}' failed!", recipient); + } else { + make_reply(recipient.clone(), &data) + } } else { - make_reply(recipient.clone(), &data) + let encrypted = encrypted.unwrap(); + let encrypted = encrypted.trim(); + let mut reply = make_reply(recipient.clone(), "pgp"); + let mut x = Element::bare("x", "jabber:x:encrypted"); + x.append_text_node(encrypted); + reply.append_child(x); + reply } } else { - let encrypted = encrypted.unwrap(); - let encrypted = encrypted.trim(); - let mut reply = make_reply(recipient.clone(), "pgp"); - let mut x = Element::bare("x", "jabber:x:encrypted"); - x.append_text_node(encrypted); - reply.append_child(x); - reply - } - } else { - make_reply(recipient.clone(), &data) - }; - client.send_stanza(reply).await.die("sending message failed"); - } + make_reply(recipient.clone(), &data) + }; + client.send_stanza(reply).await.die("sending message failed"); + } - // Close client connection - client.end().await.ok(); // ignore errors here, I guess + // Close client connection + client.end().await.ok(); // ignore errors here, I guess + } +} + +// Construct a +fn make_presence() -> Element { + let mut presence = Presence::new(PresenceType::None); + presence.show = Some(PresenceShow::Chat); + presence.into() } // Construct a chat @@ -180,8 +234,8 @@ fn gpg_encrypt(to: Jid, body: &str) -> Result { } fn first_index_of(start_index: usize, haystack: &[u8], needle: &[u8]) -> Result { - for i in start_index..haystack.len()-needle.len()+1 { - if haystack[i..i+needle.len()] == needle[..] { + for i in start_index..haystack.len() - needle.len() + 1 { + if haystack[i..i + needle.len()] == needle[..] { return Ok(i); } }