Compare commits
8 Commits
raw_newlib
...
master
Author | SHA1 | Date | |
---|---|---|---|
5b36300054 | |||
7aff2080df | |||
294da89d22 | |||
f1aae8e3c2 | |||
c8ce4bb629 | |||
|
3b36852749 | ||
|
06ecb97f4c | ||
|
b643665354 |
@ -9,9 +9,6 @@ SUFFIX=""
|
|||||||
|
|
||||||
echo "$TARGET" | grep -E '^x86_64-pc-windows-gnu$' >/dev/null && SUFFIX=".exe"
|
echo "$TARGET" | grep -E '^x86_64-pc-windows-gnu$' >/dev/null && SUFFIX=".exe"
|
||||||
|
|
||||||
# ring fails to compile here
|
|
||||||
echo "$TARGET" | grep -E '^(s390x|powerpc|mips|riscv64gc|.*solaris$)' >/dev/null && echo "$TARGET not supported in rustls" && exit 0
|
|
||||||
|
|
||||||
# build binary
|
# build binary
|
||||||
cross build --target $TARGET --release
|
cross build --target $TARGET --release
|
||||||
|
|
||||||
|
1306
Cargo.lock
generated
1306
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sendxmpp"
|
name = "sendxmpp"
|
||||||
version = "2.0.0"
|
version = "3.0.1"
|
||||||
authors = ["moparisthebest <admin@moparisthebest.com>"]
|
authors = ["moparisthebest <admin@moparisthebest.com>"]
|
||||||
|
|
||||||
description = "Send XMPP messages from the command line."
|
description = "Send XMPP messages from the command line."
|
||||||
@ -25,14 +25,10 @@ serde_derive = "1.0"
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
gumdrop = "0.8.0"
|
gumdrop = "0.8.0"
|
||||||
gumdrop_derive = "0.8.0"
|
gumdrop_derive = "0.8.0"
|
||||||
dirs = "3.0.1"
|
dirs = "4.0.0"
|
||||||
tokio-xmpp = { git = "https://gitlab.com/moparisthebest/xmpp-rs.git", branch = "main", default-features = false }
|
tokio-xmpp = { version = "3.2.0", default-features = false, features = ["tls-rust"] }
|
||||||
tokio = { version = "1", features = ["net", "rt", "rt-multi-thread", "macros", "io-util", "io-std"] }
|
tokio = { version = "1", features = ["net", "rt", "rt-multi-thread", "macros", "io-util", "io-std"] }
|
||||||
xmpp-parsers = "0.18"
|
xmpp-parsers = "0.19"
|
||||||
die = "0.2.0"
|
die = "0.2.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
env_logger = "0.9"
|
||||||
[features]
|
|
||||||
default = ["tls-rust"]
|
|
||||||
tls-rust = ["tokio-xmpp/tls-rust"]
|
|
||||||
tls-native = ["tokio-xmpp/tls-native"]
|
|
||||||
|
@ -18,6 +18,10 @@ Optional arguments:
|
|||||||
-c, --config CONFIG path to config file. default: ~/.config/sendxmpp.toml with fallback to /etc/sendxmpp/sendxmpp.toml
|
-c, --config CONFIG path to config file. default: ~/.config/sendxmpp.toml with fallback to /etc/sendxmpp/sendxmpp.toml
|
||||||
-e, --force-pgp Force OpenPGP encryption for all recipients
|
-e, --force-pgp Force OpenPGP encryption for all recipients
|
||||||
-a, --attempt-pgp Attempt OpenPGP encryption for all recipients
|
-a, --attempt-pgp Attempt OpenPGP encryption for all recipients
|
||||||
|
-r, --raw Send raw XML stream, cannot be used with recipients or PGP
|
||||||
|
-p, --presence Send a <presence/> after connecting before sending messages, required for receiving for --raw
|
||||||
|
-m, --muc Recipients are Multi-User Chats
|
||||||
|
-n, --nick NICK Nickname to use in Multi-User Chats
|
||||||
```
|
```
|
||||||
|
|
||||||
Usage examples:
|
Usage examples:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
# jid and password exactly like this, nothing else
|
# jid and password exactly like this
|
||||||
|
|
||||||
jid = "jid@example.org"
|
jid = "jid@example.org"
|
||||||
password = "sOmePa55W0rD"
|
password = "sOmePa55W0rD"
|
||||||
|
# nick = "foobar" # optional nick for Multi-User Chat
|
||||||
|
68
src/main.rs
68
src/main.rs
@ -3,16 +3,18 @@ use std::fs::File;
|
|||||||
use std::io::{stdin, Read, Write};
|
use std::io::{stdin, Read, Write};
|
||||||
use std::iter::Iterator;
|
use std::iter::Iterator;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use die::{die, Die};
|
use die::{die, Die};
|
||||||
use gumdrop::Options;
|
use gumdrop::Options;
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
|
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use tokio_xmpp::SimpleClient as Client;
|
use tokio_xmpp::{SimpleClient as Client};
|
||||||
use xmpp_parsers::message::{Body, Message};
|
use xmpp_parsers::message::{Body, Message, MessageType};
|
||||||
|
use xmpp_parsers::muc::Muc;
|
||||||
use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
|
use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
|
||||||
use xmpp_parsers::{Element, Jid};
|
use xmpp_parsers::{BareJid, Element, FullJid, Jid};
|
||||||
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
@ -22,6 +24,7 @@ use anyhow::{anyhow, bail, Result};
|
|||||||
struct Config {
|
struct Config {
|
||||||
jid: String,
|
jid: String,
|
||||||
password: String,
|
password: String,
|
||||||
|
nick: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_cfg<P: AsRef<Path>>(path: P) -> Result<Config> {
|
fn parse_cfg<P: AsRef<Path>>(path: P) -> Result<Config> {
|
||||||
@ -53,10 +56,18 @@ struct MyOptions {
|
|||||||
|
|
||||||
#[options(help = "Send a <presence/> after connecting before sending messages, required for receiving for --raw")]
|
#[options(help = "Send a <presence/> after connecting before sending messages, required for receiving for --raw")]
|
||||||
presence: bool,
|
presence: bool,
|
||||||
|
|
||||||
|
#[options(help = "Recipients are Multi-User Chats")]
|
||||||
|
muc: bool,
|
||||||
|
|
||||||
|
#[options(help = "Nickname to use in Multi-User Chats")]
|
||||||
|
nick: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
let args: Vec<String> = args().collect();
|
let args: Vec<String> = args().collect();
|
||||||
|
|
||||||
// Remember to skip the first argument. That's the program name.
|
// Remember to skip the first argument. That's the program name.
|
||||||
@ -78,10 +89,19 @@ async fn main() {
|
|||||||
if !recipients.is_empty() {
|
if !recipients.is_empty() {
|
||||||
die!("--raw is incompatible with recipients");
|
die!("--raw is incompatible with recipients");
|
||||||
}
|
}
|
||||||
|
if opts.muc {
|
||||||
|
die!("--raw is incompatible with --muc");
|
||||||
|
}
|
||||||
} else if recipients.is_empty() {
|
} else if recipients.is_empty() {
|
||||||
die!("no recipients specified!");
|
die!("no recipients specified!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.muc {
|
||||||
|
if opts.force_pgp || opts.attempt_pgp {
|
||||||
|
die!("--force-pgp and --attempt-pgp isn't implemented with --muc");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let recipients = &recipients;
|
let recipients = &recipients;
|
||||||
|
|
||||||
let cfg = match opts.config {
|
let cfg = match opts.config {
|
||||||
@ -100,16 +120,13 @@ async fn main() {
|
|||||||
|
|
||||||
// can paste this to test: <message xmlns="jabber:client" to="travis@burtrum.org" type="chat"><body>woot</body></message>
|
// can paste this to test: <message xmlns="jabber:client" to="travis@burtrum.org" type="chat"><body>woot</body></message>
|
||||||
|
|
||||||
let client = client.into_inner();
|
let mut open_client = client.into_inner().into_inner();
|
||||||
let mut open_client = client.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 rd_buf = [0u8; 256]; // todo: proper buffer size?
|
||||||
let mut stdin_buf = rd_buf.clone();
|
let mut stdin_buf = rd_buf.clone();
|
||||||
|
|
||||||
let mut stdin = tokio::io::stdin();
|
let mut stdin = tokio::io::stdin();
|
||||||
let mut stdout = tokio::io::stdout();
|
let mut stdout = tokio::io::stdout();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
n = open_client.read(&mut rd_buf) => {
|
n = open_client.read(&mut rd_buf) => {
|
||||||
@ -139,6 +156,10 @@ async fn main() {
|
|||||||
let mut data = String::new();
|
let mut data = String::new();
|
||||||
stdin().lock().read_to_string(&mut data).die("error reading from stdin");
|
stdin().lock().read_to_string(&mut data).die("error reading from stdin");
|
||||||
let data = data.trim();
|
let data = data.trim();
|
||||||
|
if data.is_empty() {
|
||||||
|
// don't send empty stanzas
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
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");
|
||||||
|
|
||||||
@ -147,28 +168,46 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for recipient in recipients {
|
for recipient in recipients {
|
||||||
|
if opts.muc {
|
||||||
|
let nick = opts
|
||||||
|
.nick
|
||||||
|
.clone()
|
||||||
|
.or(cfg.nick.clone())
|
||||||
|
.or_else(|| BareJid::from_str(cfg.jid.as_str()).unwrap().node)
|
||||||
|
.die("couldn't find a nick to use");
|
||||||
|
let participant = match recipient.clone() {
|
||||||
|
Jid::Full(_) => die!("Invalid room address"),
|
||||||
|
Jid::Bare(bare) => bare.with_resource(nick.clone()),
|
||||||
|
};
|
||||||
|
let join = make_join(participant.clone());
|
||||||
|
client.send_stanza(join).await.die("failed to join MUC");
|
||||||
|
|
||||||
|
let reply = make_reply(recipient.clone(), &data, opts.muc);
|
||||||
|
client.send_stanza(reply).await.die("sending message failed");
|
||||||
|
} else {
|
||||||
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);
|
||||||
if encrypted.is_err() {
|
if encrypted.is_err() {
|
||||||
if opts.force_pgp {
|
if opts.force_pgp {
|
||||||
die!("pgp encryption to jid '{}' failed!", recipient);
|
die!("pgp encryption to jid '{}' failed!", recipient);
|
||||||
} else {
|
} else {
|
||||||
make_reply(recipient.clone(), &data)
|
make_reply(recipient.clone(), &data, opts.muc)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let encrypted = encrypted.unwrap();
|
let encrypted = encrypted.unwrap();
|
||||||
let encrypted = encrypted.trim();
|
let encrypted = encrypted.trim();
|
||||||
let mut reply = make_reply(recipient.clone(), "pgp");
|
let mut reply = make_reply(recipient.clone(), "pgp", opts.muc);
|
||||||
let mut x = Element::bare("x", "jabber:x:encrypted");
|
let mut x = Element::bare("x", "jabber:x:encrypted");
|
||||||
x.append_text_node(encrypted);
|
x.append_text_node(encrypted);
|
||||||
reply.append_child(x);
|
reply.append_child(x);
|
||||||
reply
|
reply
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
make_reply(recipient.clone(), &data)
|
make_reply(recipient.clone(), &data, opts.muc)
|
||||||
};
|
};
|
||||||
client.send_stanza(reply).await.die("sending message failed");
|
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
|
||||||
@ -182,9 +221,16 @@ fn make_presence() -> Element {
|
|||||||
presence.into()
|
presence.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_join(to: FullJid) -> Element {
|
||||||
|
Presence::new(PresenceType::None).with_to(Jid::Full(to)).with_payloads(vec![Muc::new().into()]).into()
|
||||||
|
}
|
||||||
|
|
||||||
// Construct a chat <message/>
|
// Construct a chat <message/>
|
||||||
fn make_reply(to: Jid, body: &str) -> Element {
|
fn make_reply(to: Jid, body: &str, groupchat: bool) -> Element {
|
||||||
let mut message = Message::new(Some(to));
|
let mut message = Message::new(Some(to));
|
||||||
|
if groupchat {
|
||||||
|
message.type_ = MessageType::Groupchat;
|
||||||
|
}
|
||||||
message.bodies.insert(String::new(), Body(body.to_owned()));
|
message.bodies.insert(String::new(), Body(body.to_owned()));
|
||||||
message.into()
|
message.into()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user