From 72d983d2ec237236e0ba7926514c06eb678f6133 Mon Sep 17 00:00:00 2001 From: strawberry Date: Sat, 6 Apr 2024 20:32:13 -0400 Subject: [PATCH] add admin command to fetch `/.well-known/matrix/support` from server Signed-off-by: strawberry --- src/service/admin/federation.rs | 62 +++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/src/service/admin/federation.rs b/src/service/admin/federation.rs index b965d78a..c7a61103 100644 --- a/src/service/admin/federation.rs +++ b/src/service/admin/federation.rs @@ -1,10 +1,10 @@ use std::{collections::BTreeMap, fmt::Write as _}; use clap::Subcommand; -use ruma::{events::room::message::RoomMessageEventContent, RoomId}; +use ruma::{events::room::message::RoomMessageEventContent, RoomId, ServerName}; use tokio::sync::RwLock; -use crate::{services, Result}; +use crate::{services, utils::HtmlEscape, Result}; #[cfg_attr(test, derive(Debug))] #[derive(Subcommand)] @@ -33,6 +33,19 @@ pub(crate) enum FederationCommand { /// This command needs a JSON blob provided in a Markdown code block below /// the command. VerifyJson, + + /// - Fetch `/.well-known/matrix/support` from the specified server + /// + /// Despite the name, this is not a federation endpoint and does not go + /// through the federation / server resolution process as per-spec this is + /// supposed to be served at the server_name. + /// + /// Respecting homeservers put this file here for listing administration, + /// moderation, and security inquiries. This command provides a way to + /// easily fetch that information. + FetchSupportWellKnown { + server_name: Box, + }, } pub(crate) async fn process(command: FederationCommand, body: Vec<&str>) -> Result { @@ -110,5 +123,50 @@ pub(crate) async fn process(command: FederationCommand, body: Vec<&str>) -> Resu )) } }, + FederationCommand::FetchSupportWellKnown { + server_name, + } => { + let response = services() + .globals + .client + .default + .get(format!("https://{server_name}/.well-known/matrix/support")) + .send() + .await?; + + let text = response.text().await?; + + if text.is_empty() { + return Ok(RoomMessageEventContent::text_plain("Response text/body is empty.")); + } + + if text.len() > 1500 { + return Ok(RoomMessageEventContent::text_plain( + "Response text/body is over 1500 characters, assuming no support well-known.", + )); + } + + let json: serde_json::Value = match serde_json::from_str(&text) { + Ok(json) => json, + Err(_) => { + return Ok(RoomMessageEventContent::text_plain("Response text/body is not valid JSON.")); + }, + }; + + let pretty_json: String = match serde_json::to_string_pretty(&json) { + Ok(json) => json, + Err(_) => { + return Ok(RoomMessageEventContent::text_plain("Response text/body is not valid JSON.")); + }, + }; + + Ok(RoomMessageEventContent::text_html( + format!("Got JSON response:\n\n```json\n{pretty_json}\n```"), + format!( + "

Got JSON response:

\n
{}\n
\n", + HtmlEscape(&pretty_json) + ), + )) + }, } }