2019-01-25 21:56:50 -05:00
|
|
|
use std::env::args;
|
2017-08-11 00:37:50 -04:00
|
|
|
use std::fs::File;
|
2019-01-25 21:56:50 -05:00
|
|
|
use std::io::{stdin, Read};
|
|
|
|
use std::iter::Iterator;
|
2017-08-12 23:59:43 -04:00
|
|
|
use std::path::Path;
|
2017-08-11 00:37:50 -04:00
|
|
|
|
2019-03-02 13:19:48 -05:00
|
|
|
use die::{die, Die};
|
2017-08-11 00:37:50 -04:00
|
|
|
use gumdrop::Options;
|
2019-01-25 21:56:50 -05:00
|
|
|
use serde_derive::Deserialize;
|
|
|
|
|
|
|
|
use futures::{future, Sink, Stream};
|
|
|
|
use tokio::runtime::current_thread::Runtime;
|
2019-01-31 22:14:35 -05:00
|
|
|
use tokio_xmpp::xmpp_codec::Packet;
|
2019-01-25 21:56:50 -05:00
|
|
|
use tokio_xmpp::Client;
|
|
|
|
use xmpp_parsers::message::{Body, Message};
|
|
|
|
use xmpp_parsers::{Element, Jid};
|
2017-08-11 00:37:50 -04:00
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
struct Config {
|
|
|
|
jid: String,
|
|
|
|
password: String,
|
|
|
|
}
|
|
|
|
|
2017-08-12 23:59:43 -04:00
|
|
|
fn parse_cfg<P: AsRef<Path>>(path: P) -> Option<Config> {
|
|
|
|
match File::open(path) {
|
|
|
|
Ok(mut f) => {
|
|
|
|
let mut input = String::new();
|
|
|
|
match f.read_to_string(&mut input) {
|
|
|
|
Ok(_) => match toml::from_str(&input) {
|
|
|
|
Ok(toml) => Some(toml),
|
2019-01-25 21:56:50 -05:00
|
|
|
Err(_) => None,
|
2017-08-12 23:59:43 -04:00
|
|
|
},
|
2019-01-25 21:56:50 -05:00
|
|
|
Err(_) => None,
|
2017-08-12 23:59:43 -04:00
|
|
|
}
|
2017-08-11 00:37:50 -04:00
|
|
|
}
|
2019-01-25 21:56:50 -05:00
|
|
|
Err(_) => None,
|
2017-08-12 23:59:43 -04:00
|
|
|
}
|
2017-08-11 00:37:50 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default, Options)]
|
|
|
|
struct MyOptions {
|
|
|
|
#[options(free)]
|
|
|
|
recipients: Vec<String>,
|
|
|
|
|
|
|
|
#[options(help = "show this help message and exit")]
|
|
|
|
help: bool,
|
|
|
|
|
2019-01-25 21:56:50 -05:00
|
|
|
#[options(
|
|
|
|
help = "path to config file. default: ~/.config/sendxmpp.toml with fallback to /etc/sendxmpp/sendxmpp.toml"
|
|
|
|
)]
|
2017-08-11 00:37:50 -04:00
|
|
|
config: Option<String>,
|
|
|
|
|
|
|
|
#[options(help = "Force OpenPGP encryption for all recipients", short = "e")]
|
|
|
|
force_pgp: bool,
|
|
|
|
|
|
|
|
#[options(help = "Attempt OpenPGP encryption for all recipients")]
|
|
|
|
attempt_pgp: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let args: Vec<String> = args().collect();
|
|
|
|
|
|
|
|
// Remember to skip the first argument. That's the program name.
|
|
|
|
let opts = match MyOptions::parse_args_default(&args[1..]) {
|
|
|
|
Ok(opts) => opts,
|
2019-03-02 13:19:48 -05:00
|
|
|
Err(e) => die!(
|
|
|
|
"{}: {}\nUsage: {} [OPTIONS] [ARGUMENTS]\n\n{}",
|
|
|
|
args[0],
|
|
|
|
e,
|
|
|
|
args[0],
|
|
|
|
MyOptions::usage()
|
|
|
|
),
|
2017-08-11 00:37:50 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
if opts.help {
|
2019-03-02 13:19:48 -05:00
|
|
|
die!(
|
|
|
|
"Usage: {} [OPTIONS] [ARGUMENTS]\n\n{}",
|
|
|
|
args[0],
|
|
|
|
MyOptions::usage()
|
|
|
|
);
|
2017-08-11 00:37:50 -04:00
|
|
|
}
|
|
|
|
|
2019-01-25 21:56:50 -05:00
|
|
|
let recipients: Vec<Jid> = opts
|
|
|
|
.recipients
|
|
|
|
.iter()
|
2019-03-02 13:19:48 -05:00
|
|
|
.map(|s| s.parse::<Jid>().die("invalid recipient jid"))
|
2019-01-25 21:56:50 -05:00
|
|
|
.collect();
|
2019-03-02 13:19:48 -05:00
|
|
|
|
|
|
|
if recipients.is_empty() {
|
|
|
|
die!("no recipients specified!");
|
|
|
|
}
|
|
|
|
|
2019-01-25 21:56:50 -05:00
|
|
|
let recipients = &recipients;
|
2017-08-11 00:37:50 -04:00
|
|
|
|
|
|
|
let cfg = match opts.config {
|
2019-03-02 13:19:48 -05:00
|
|
|
Some(config) => parse_cfg(&config).die("provided config cannot be found/parsed"),
|
2019-01-25 21:56:50 -05:00
|
|
|
None => parse_cfg(
|
|
|
|
dirs::config_dir()
|
2019-03-02 13:19:48 -05:00
|
|
|
.die("cannot find home directory")
|
2019-01-25 21:56:50 -05:00
|
|
|
.join("sendxmpp.toml"),
|
|
|
|
)
|
|
|
|
.or_else(|| parse_cfg("/etc/sendxmpp/sendxmpp.toml"))
|
2019-03-02 13:19:48 -05:00
|
|
|
.die("valid config file not found"),
|
2017-08-11 00:37:50 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
let mut data = String::new();
|
2019-01-25 21:56:50 -05:00
|
|
|
stdin()
|
|
|
|
.read_to_string(&mut data)
|
2019-03-02 13:19:48 -05:00
|
|
|
.die("error reading from stdin");
|
2019-01-25 21:56:50 -05:00
|
|
|
let data = data.trim();
|
|
|
|
|
|
|
|
// tokio_core context
|
2019-03-02 13:19:48 -05:00
|
|
|
let mut rt = Runtime::new().die("unknown error");
|
2019-01-25 21:56:50 -05:00
|
|
|
// Client instance
|
2019-03-02 13:19:48 -05:00
|
|
|
let client = Client::new(&cfg.jid, &cfg.password).die("could not connect to xmpp server");
|
2019-01-25 21:56:50 -05:00
|
|
|
|
|
|
|
// Make the two interfaces for sending and receiving independent
|
|
|
|
// of each other so we can move one into a closure.
|
2019-01-31 22:14:35 -05:00
|
|
|
let (sink, stream) = client.split();
|
|
|
|
let mut sink_state = Some(sink);
|
|
|
|
|
2019-01-25 21:56:50 -05:00
|
|
|
// Main loop, processes events
|
2019-01-31 22:14:35 -05:00
|
|
|
let done = stream.for_each(move |event| {
|
2019-01-25 21:56:50 -05:00
|
|
|
if event.is_online() {
|
2019-03-02 13:19:48 -05:00
|
|
|
let mut sink = sink_state.take().die("unknown error");
|
2019-01-25 21:56:50 -05:00
|
|
|
for recipient in recipients {
|
|
|
|
let reply = make_reply(recipient.clone(), &data);
|
2019-03-02 13:19:48 -05:00
|
|
|
sink.start_send(Packet::Stanza(reply)).die("send failed");
|
2019-01-25 21:56:50 -05:00
|
|
|
}
|
2019-01-31 22:14:35 -05:00
|
|
|
sink.start_send(Packet::StreamEnd)
|
2019-03-02 13:19:48 -05:00
|
|
|
.die("send stream end failed");
|
2019-01-25 21:56:50 -05:00
|
|
|
}
|
2017-08-11 00:37:50 -04:00
|
|
|
|
2019-01-25 21:56:50 -05:00
|
|
|
Box::new(future::ok(()))
|
|
|
|
});
|
2017-08-11 00:37:50 -04:00
|
|
|
|
2019-01-25 21:56:50 -05:00
|
|
|
// Start polling `done`
|
|
|
|
match rt.block_on(done) {
|
2019-01-31 22:14:35 -05:00
|
|
|
Ok(_) => (),
|
2019-03-02 13:19:48 -05:00
|
|
|
Err(e) => die!("Fatal: {}", e),
|
2019-01-25 21:56:50 -05:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Construct a chat <message/>
|
|
|
|
fn make_reply(to: Jid, body: &str) -> Element {
|
|
|
|
let mut message = Message::new(Some(to));
|
|
|
|
message.bodies.insert(String::new(), Body(body.to_owned()));
|
|
|
|
message.into()
|
2017-08-11 00:37:50 -04:00
|
|
|
}
|