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]]
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",

View File

@ -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"

View File

@ -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<String>,
#[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 <presence/> 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<Jid> = opts
.recipients
.iter()
.map(|s| s.parse::<Jid>().die("invalid recipient jid"))
.collect();
let recipients: Vec<Jid> = opts.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!");
}
@ -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: <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();
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 <presence/>
fn make_presence() -> Element {
let mut presence = Presence::new(PresenceType::None);
presence.show = Some(PresenceShow::Chat);
presence.into()
}
// 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> {
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);
}
}