For this, we had to: - Make the choice(s) of backend explicit in tokio-xmpp features - Avoid picking a backend through transitive dependencies in xmpp (reqwest->hyper->rustls). In addition, we choose the default rustls backend by default and adapt the examples so that they can cope with either situation.
131 lines
4.1 KiB
Rust
131 lines
4.1 KiB
Rust
use futures::stream::StreamExt;
|
|
use std::env::args;
|
|
use std::process::exit;
|
|
use std::str::FromStr;
|
|
#[cfg(all(
|
|
feature = "tls-rust",
|
|
any(feature = "tls-rust-aws_lc_rs", feature = "tls-rust-ring")
|
|
))]
|
|
use tokio_xmpp::rustls;
|
|
use tokio_xmpp::{Client, IqRequest, IqResponse};
|
|
use xmpp_parsers::{
|
|
disco::{DiscoInfoQuery, DiscoInfoResult},
|
|
jid::{BareJid, Jid},
|
|
ns,
|
|
server_info::ServerInfo,
|
|
};
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
env_logger::init();
|
|
|
|
#[cfg(all(feature = "tls-rust", feature = "tls-rust-aws_lc_rs"))]
|
|
rustls::crypto::aws_lc_rs::default_provider()
|
|
.install_default()
|
|
.expect("failed to install rustls crypto provider");
|
|
|
|
#[cfg(all(
|
|
feature = "tls-rust",
|
|
feature = "tls-rust-ring",
|
|
not(feature = "tls-rust-aws_lc_rs")
|
|
))]
|
|
rustls::crypto::ring::default_provider()
|
|
.install_default()
|
|
.expect("failed to install rustls crypto provider");
|
|
|
|
let args: Vec<String> = args().collect();
|
|
if args.len() != 4 {
|
|
println!("Usage: {} <jid> <password> <target>", args[0]);
|
|
exit(1);
|
|
}
|
|
let jid = BareJid::from_str(&args[1]).expect(&format!("Invalid JID: {}", &args[1]));
|
|
let password = args[2].clone();
|
|
let target = Jid::from_str(&args[3]).expect(&format!("Invalid JID: {}", &args[3]));
|
|
|
|
// Client instance
|
|
let mut client = Client::new(jid, password);
|
|
|
|
let token = client
|
|
.send_iq(
|
|
Some(target),
|
|
IqRequest::Get(DiscoInfoQuery { node: None }.into()),
|
|
)
|
|
.await;
|
|
tokio::pin!(token);
|
|
|
|
// Main loop, processes events
|
|
loop {
|
|
tokio::select! {
|
|
response = &mut token => match response {
|
|
Ok(IqResponse::Result(Some(payload))) => {
|
|
if payload.is("query", ns::DISCO_INFO) {
|
|
if let Ok(disco_info) = DiscoInfoResult::try_from(payload) {
|
|
for ext in disco_info.extensions {
|
|
if let Ok(server_info) = ServerInfo::try_from(ext) {
|
|
print_server_info(server_info);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
Ok(IqResponse::Result(None)) => {
|
|
panic!("disco#info response misses payload!");
|
|
}
|
|
Ok(IqResponse::Error(err)) => {
|
|
panic!("disco#info response is an error: {:?}", err);
|
|
}
|
|
Err(err) => {
|
|
panic!("disco#info request failed to send: {}", err);
|
|
}
|
|
},
|
|
event = client.next() => {
|
|
let Some(event) = event else {
|
|
println!("Client terminated");
|
|
break;
|
|
};
|
|
if event.is_online() {
|
|
println!("Online!");
|
|
}
|
|
},
|
|
}
|
|
}
|
|
client.send_end().await.expect("Stream shutdown unclean");
|
|
}
|
|
|
|
fn convert_field(field: Vec<String>) -> String {
|
|
field
|
|
.iter()
|
|
.fold((field.len(), String::new()), |(l, mut acc), s| {
|
|
acc.push('<');
|
|
acc.push_str(&s);
|
|
acc.push('>');
|
|
if l > 1 {
|
|
acc.push(',');
|
|
acc.push(' ');
|
|
}
|
|
(0, acc)
|
|
})
|
|
.1
|
|
}
|
|
|
|
fn print_server_info(server_info: ServerInfo) {
|
|
if server_info.abuse.len() != 0 {
|
|
println!("abuse: {}", convert_field(server_info.abuse));
|
|
}
|
|
if server_info.admin.len() != 0 {
|
|
println!("admin: {}", convert_field(server_info.admin));
|
|
}
|
|
if server_info.feedback.len() != 0 {
|
|
println!("feedback: {}", convert_field(server_info.feedback));
|
|
}
|
|
if server_info.sales.len() != 0 {
|
|
println!("sales: {}", convert_field(server_info.sales));
|
|
}
|
|
if server_info.security.len() != 0 {
|
|
println!("security: {}", convert_field(server_info.security));
|
|
}
|
|
if server_info.support.len() != 0 {
|
|
println!("support: {}", convert_field(server_info.support));
|
|
}
|
|
}
|