This provides a clear and consistent interface for selecting TLS-related
features on both (xmpp and tokio-xmpp) crates. All feature checks have
been revamped. All working combinations have been tested (including a
connectivity test + --all-features docs build) using:
```
set -xeuo pipefail
features=(aws_lc_rs ring ktls,aws_lc_rs ktls,ring aws_lc_rs,ring aws_lc_rs,ring,ktls native-tls rustls-any-backend)
export RUSTFLAGS=" -D warnings"
cargo test --no-default-features
cargo test
for feature in ${features[@]}; do
echo ">>> BUILDING with $feature" 2>&1
# Running code or building examples cannot succeed with rustls-any-backend.
features="starttls,$feature"
if [ "$feature" != 'rustls-any-backend' ]; then
if ! cargo test --no-default-features --features="$features"; then
echo ">>> BUILD FAILED for tls feature set: $features" >&2
exit 1
fi
set +e
timeout -sINT -p -k 2 3 cargo run --no-default-features --features="$features" --example keep_connection -- test@hub.sotecware.net "$(pass xmpp/test@hub.sotecware.net)"
status="$?"
set -e
if [ $status -ne 0 ]; then
echo ">>> keep_connection did not shut down cleanly! (status: $status)" >&2
exit 1
fi
else
if ! cargo build --no-default-features --features="$feature"; then
echo ">>> BUILD FAILED for tls feature set: $features" >&2
exit 1
fi
fi
done
RUSTDOCFLAGS="--cfg docsrs" RUSTFLAGS="--cfg xmpprs_doc_build" cargo +nightly doc -Zrustdoc-map --all-features
```
130 lines
4.1 KiB
Rust
130 lines
4.1 KiB
Rust
use futures::stream::StreamExt;
|
|
use std::env::args;
|
|
use std::process::exit;
|
|
use std::str::FromStr;
|
|
#[cfg(feature = "rustls-any-backend")]
|
|
use tokio_xmpp::rustls;
|
|
use tokio_xmpp::{Client, IqRequest, IqResponse};
|
|
use xmpp_parsers::{
|
|
disco::{DiscoInfoQuery, DiscoInfoResult},
|
|
jid::{BareJid, Jid},
|
|
ns,
|
|
server_info::ServerInfo,
|
|
};
|
|
|
|
#[cfg(all(
|
|
feature = "rustls-any-backend",
|
|
not(any(feature = "aws_lc_rs", feature = "ring"))
|
|
))]
|
|
compile_error!("using rustls (e.g. via the ktls feature) needs an enabled rustls backend feature (either aws_lc_rs or ring).");
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
env_logger::init();
|
|
|
|
#[cfg(all(feature = "aws_lc_rs", not(feature = "ring")))]
|
|
rustls::crypto::aws_lc_rs::default_provider()
|
|
.install_default()
|
|
.expect("failed to install rustls crypto provider");
|
|
|
|
#[cfg(all(feature = "ring"))]
|
|
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));
|
|
}
|
|
}
|