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
Some checks failed
moparisthebest/sendxmpp-rs/pipeline/head There was a failure building this commit
This commit is contained in:
parent
062571c3a4
commit
d2ee6168fb
1
.rustfmt.toml
Normal file
1
.rustfmt.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
max_width = 200
|
47
Cargo.lock
generated
47
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
180
src/main.rs
180
src/main.rs
@ -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,50 +87,103 @@ 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()
|
.or_else(|_| parse_cfg("/etc/sendxmpp/sendxmpp.toml"))
|
||||||
.die("cannot find home directory")
|
.die("valid config file not found"),
|
||||||
.join("sendxmpp.toml"),
|
|
||||||
)
|
|
||||||
.or_else(|_| parse_cfg("/etc/sendxmpp/sendxmpp.toml"))
|
|
||||||
.die("valid config file not found"),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut data = String::new();
|
if opts.raw {
|
||||||
stdin()
|
let mut client = Client::new(&cfg.jid, &cfg.password).await.die("could not connect to xmpp server");
|
||||||
.read_to_string(&mut data)
|
|
||||||
.die("error reading from stdin");
|
|
||||||
let data = data.trim();
|
|
||||||
|
|
||||||
// Client instance
|
if opts.presence {
|
||||||
let mut client = Client::new(&cfg.jid, &cfg.password).await.die("could not connect to xmpp server");
|
client.send_stanza(make_presence()).await.die("could not send presence");
|
||||||
|
}
|
||||||
|
|
||||||
for recipient in recipients {
|
// can paste this to test: <message xmlns="jabber:client" to="travis@burtrum.org" type="chat"><body>woot</body></message>
|
||||||
let reply = if opts.force_pgp || opts.attempt_pgp {
|
|
||||||
let encrypted = gpg_encrypt(recipient.clone(), &data);
|
pub struct OpenClient {
|
||||||
if encrypted.is_err() {
|
pub stream: xmpp_stream::XMPPStream<TlsStream<tokio::net::TcpStream>>,
|
||||||
if opts.force_pgp {
|
}
|
||||||
die!("pgp encryption to jid '{}' failed!", recipient);
|
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 {
|
} 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 {
|
} else {
|
||||||
let encrypted = encrypted.unwrap();
|
make_reply(recipient.clone(), &data)
|
||||||
let encrypted = encrypted.trim();
|
};
|
||||||
let mut reply = make_reply(recipient.clone(), "pgp");
|
client.send_stanza(reply).await.die("sending message failed");
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user