Add --raw option to support sending a stream of raw XML
Some checks failed
moparisthebest/sendxmpp-rs/pipeline/head There was a failure building this commit

This commit is contained in:
Travis Burtrum 2021-03-23 21:17:00 -04:00
parent 062571c3a4
commit d2ee6168fb
4 changed files with 146 additions and 86 deletions

1
.rustfmt.toml Normal file
View File

@ -0,0 +1 @@
max_width = 200

47
Cargo.lock generated
View File

@ -180,9 +180,9 @@ dependencies = [
[[package]] [[package]]
name = "data-encoding" name = "data-encoding"
version = "2.3.1" version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993a608597367c6377b258c25d7120740f00ed23a2252b729b1932dd7866f908" checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
[[package]] [[package]]
name = "die" name = "die"
@ -749,14 +749,14 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272" checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"instant", "instant",
"libc", "libc",
"redox_syscall 0.1.57", "redox_syscall 0.2.4",
"smallvec", "smallvec",
"winapi", "winapi",
] ]
@ -1048,9 +1048,9 @@ dependencies = [
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "397d411cfc34097bdb176984180f7d9980eb161aaf1368ceea96c9834d8f8b85" checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
dependencies = [ dependencies = [
"semver", "semver",
] ]
@ -1144,6 +1144,7 @@ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"tokio", "tokio",
"tokio-native-tls",
"tokio-xmpp", "tokio-xmpp",
"toml", "toml",
"xmpp-parsers", "xmpp-parsers",
@ -1346,9 +1347,9 @@ dependencies = [
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.1.0" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f" checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023"
dependencies = [ dependencies = [
"tinyvec_macros", "tinyvec_macros",
] ]
@ -1361,12 +1362,14 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.0.2" version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca04cec6ff2474c638057b65798f60ac183e5e79d3448bb7163d36a39cff6ec" checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"bytes",
"libc", "libc",
"memchr",
"mio", "mio",
"num_cpus", "num_cpus",
"pin-project-lite", "pin-project-lite",
@ -1375,9 +1378,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "1.0.0" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42517d2975ca3114b22a16192634e8241dc5cc1f130be194645970cc1c371494" checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1396,9 +1399,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-stream" name = "tokio-stream"
version = "0.1.2" version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76066865172052eb8796c686f0b441a93df8b08d40a950b062ffb9a426f00edd" checksum = "e177a5d8c3bf36de9ebe6d58537d8879e964332f93fb3339e43f618c81361af0"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"pin-project-lite", "pin-project-lite",
@ -1407,9 +1410,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.6.1" version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ae4751faa60b9f96dd8344d74592e5a17c0c9a220413dbc6942d14139bbfcc" checksum = "5143d049e85af7fbc36f5454d990e62c2df705b3589f123b71f441b6b59f443f"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@ -1417,7 +1420,6 @@ dependencies = [
"log", "log",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
"tokio-stream",
] ]
[[package]] [[package]]
@ -1454,9 +1456,9 @@ dependencies = [
[[package]] [[package]]
name = "trust-dns-proto" name = "trust-dns-proto"
version = "0.20.0" version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98a0381b2864c2978db7f8e17c7b23cca5a3a5f99241076e13002261a8ecbabd" checksum = "8d57e219ba600dd96c2f6d82eb79645068e14edbc5c7e27514af40436b88150c"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"cfg-if 1.0.0", "cfg-if 1.0.0",
@ -1472,15 +1474,16 @@ dependencies = [
"rand 0.8.2", "rand 0.8.2",
"smallvec", "smallvec",
"thiserror", "thiserror",
"tinyvec",
"tokio", "tokio",
"url", "url",
] ]
[[package]] [[package]]
name = "trust-dns-resolver" name = "trust-dns-resolver"
version = "0.20.0" version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3072d18c10bd621cb00507d59cfab5517862285c353160366e37fbf4c74856e4" checksum = "b0437eea3a6da51acc1e946545ff53d5b8fb2611ff1c3bed58522dde100536ae"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"futures-util", "futures-util",

View File

@ -27,7 +27,9 @@ gumdrop = "0.8.0"
gumdrop_derive = "0.8.0" gumdrop_derive = "0.8.0"
dirs = "3.0.1" dirs = "3.0.1"
tokio-xmpp = "3.0.0" 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" xmpp-parsers = "0.18"
die = "0.2.0" die = "0.2.0"
anyhow = "1.0" anyhow = "1.0"

View File

@ -8,12 +8,16 @@ use die::{die, Die};
use gumdrop::Options; use gumdrop::Options;
use serde_derive::Deserialize; 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 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)] #[derive(Deserialize)]
struct Config { struct Config {
@ -36,9 +40,7 @@ struct MyOptions {
#[options(help = "show this help message and exit")] #[options(help = "show this help message and exit")]
help: bool, help: bool,
#[options( #[options(help = "path to config file. default: ~/.config/sendxmpp.toml with fallback to /etc/sendxmpp/sendxmpp.toml")]
help = "path to config file. default: ~/.config/sendxmpp.toml with fallback to /etc/sendxmpp/sendxmpp.toml"
)]
config: Option<String>, config: Option<String>,
#[options(help = "Force OpenPGP encryption for all recipients", short = "e")] #[options(help = "Force OpenPGP encryption for all recipients", short = "e")]
@ -46,6 +48,12 @@ struct MyOptions {
#[options(help = "Attempt OpenPGP encryption for all recipients")] #[options(help = "Attempt OpenPGP encryption for all recipients")]
attempt_pgp: bool, attempt_pgp: bool,
#[options(help = "Send raw XML stream, cannot be used with recipients or PGP")]
raw: bool,
#[options(help = "Send a <presence/> after connecting before sending messages, required for receiving for --raw")]
presence: bool,
} }
#[tokio::main] #[tokio::main]
@ -55,30 +63,23 @@ async fn main() {
// Remember to skip the first argument. That's the program name. // Remember to skip the first argument. That's the program name.
let opts = match MyOptions::parse_args_default(&args[1..]) { let opts = match MyOptions::parse_args_default(&args[1..]) {
Ok(opts) => opts, Ok(opts) => opts,
Err(e) => die!( Err(e) => die!("{}: {}\nUsage: {} [OPTIONS] [ARGUMENTS]\n\n{}", args[0], e, args[0], MyOptions::usage()),
"{}: {}\nUsage: {} [OPTIONS] [ARGUMENTS]\n\n{}",
args[0],
e,
args[0],
MyOptions::usage()
),
}; };
if opts.help { if opts.help {
die!( die!("Usage: {} [OPTIONS] [ARGUMENTS]\n\n{}", args[0], MyOptions::usage());
"Usage: {} [OPTIONS] [ARGUMENTS]\n\n{}",
args[0],
MyOptions::usage()
);
} }
let recipients: Vec<Jid> = opts let recipients: Vec<Jid> = opts.recipients.iter().map(|s| s.parse::<Jid>().die("invalid recipient jid")).collect();
.recipients
.iter()
.map(|s| s.parse::<Jid>().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!"); die!("no recipients specified!");
} }
@ -86,24 +87,69 @@ async fn main() {
let cfg = match opts.config { let cfg = match opts.config {
Some(config) => parse_cfg(&config).die("provided config cannot be found/parsed"), Some(config) => parse_cfg(&config).die("provided config cannot be found/parsed"),
None => parse_cfg( None => parse_cfg(dirs::config_dir().die("cannot find home directory").join("sendxmpp.toml"))
dirs::config_dir()
.die("cannot find home directory")
.join("sendxmpp.toml"),
)
.or_else(|_| parse_cfg("/etc/sendxmpp/sendxmpp.toml")) .or_else(|_| parse_cfg("/etc/sendxmpp/sendxmpp.toml"))
.die("valid config file not found"), .die("valid config file not found"),
}; };
if opts.raw {
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");
}
// can paste this to test: <message xmlns="jabber:client" to="travis@burtrum.org" type="chat"><body>woot</body></message>
pub struct OpenClient {
pub stream: xmpp_stream::XMPPStream<TlsStream<tokio::net::TcpStream>>,
}
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("</stream:stream>".as_bytes()).await.ok();
open_client.flush().await.ok();
open_client.read(&mut rd_buf).await.ok();
} else {
let mut data = String::new(); let mut data = String::new();
stdin() stdin().lock().read_to_string(&mut data).die("error reading from stdin");
.read_to_string(&mut data)
.die("error reading from stdin");
let data = data.trim(); let data = data.trim();
// Client instance
let mut client = Client::new(&cfg.jid, &cfg.password).await.die("could not connect to xmpp server"); 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 { for recipient in recipients {
let reply = if opts.force_pgp || opts.attempt_pgp { let reply = if opts.force_pgp || opts.attempt_pgp {
let encrypted = gpg_encrypt(recipient.clone(), &data); let encrypted = gpg_encrypt(recipient.clone(), &data);
@ -130,6 +176,14 @@ async fn main() {
// Close client connection // Close client connection
client.end().await.ok(); // ignore errors here, I guess client.end().await.ok(); // ignore errors here, I guess
}
}
// Construct a <presence/>
fn make_presence() -> Element {
let mut presence = Presence::new(PresenceType::None);
presence.show = Some(PresenceShow::Chat);
presence.into()
} }
// Construct a chat <message/> // Construct a chat <message/>
@ -180,8 +234,8 @@ fn gpg_encrypt(to: Jid, body: &str) -> Result<String> {
} }
fn first_index_of(start_index: usize, haystack: &[u8], needle: &[u8]) -> Result<usize> { fn first_index_of(start_index: usize, haystack: &[u8], needle: &[u8]) -> Result<usize> {
for i in start_index..haystack.len()-needle.len()+1 { for i in start_index..haystack.len() - needle.len() + 1 {
if haystack[i..i+needle.len()] == needle[..] { if haystack[i..i + needle.len()] == needle[..] {
return Ok(i); return Ok(i);
} }
} }