diff --git a/src/api/client_server/account.rs b/src/api/client_server/account.rs index 1187ec7b..298c2655 100644 --- a/src/api/client_server/account.rs +++ b/src/api/client_server/account.rs @@ -294,7 +294,8 @@ pub(crate) async fn register_route(body: Ruma) -> Result< .admin .send_message(RoomMessageEventContent::notice_plain(format!( "New user \"{user_id}\" registered on this server." - ))); + ))) + .await; } // log in conduit admin channel if a guest registered @@ -310,27 +311,30 @@ pub(crate) async fn register_route(body: Ruma) -> Result< .send_message(RoomMessageEventContent::notice_plain(format!( "Guest user \"{user_id}\" with device display name `{device_display_name}` registered on this \ server." - ))); + ))) + .await; } else { services() .admin .send_message(RoomMessageEventContent::notice_plain(format!( "Guest user \"{user_id}\" with no device display name registered on this server.", - ))); + ))) + .await; } } else { services() .admin .send_message(RoomMessageEventContent::notice_plain(format!( "Guest user \"{user_id}\" with no device display name registered on this server.", - ))); + ))) + .await; } } // If this is the first real user, grant them admin privileges except for guest // users Note: the server user, @conduit:servername, is generated first if !is_guest { - if let Some(admin_room) = service::admin::Service::get_admin_room()? { + if let Some(admin_room) = service::admin::Service::get_admin_room().await? { if services() .rooms .state_cache @@ -461,7 +465,8 @@ pub(crate) async fn change_password_route( .admin .send_message(RoomMessageEventContent::notice_plain(format!( "User {sender_user} changed their password." - ))); + ))) + .await; Ok(change_password::v3::Response {}) } @@ -536,7 +541,8 @@ pub(crate) async fn deactivate_route(body: Ruma) -> Res .admin .send_message(RoomMessageEventContent::notice_plain(format!( "User {sender_user} deactivated their account." - ))); + ))) + .await; Ok(deactivate::v3::Response { id_server_unbind_result: ThirdPartyIdRemovalStatus::NoSupport, diff --git a/src/api/client_server/report.rs b/src/api/client_server/report.rs index ee8f2e75..4cefcd08 100644 --- a/src/api/client_server/report.rs +++ b/src/api/client_server/report.rs @@ -97,7 +97,8 @@ pub(crate) async fn report_event_route( body.score.unwrap_or_else(|| ruma::Int::from(0)), HtmlEscape(body.reason.as_deref().unwrap_or("")) ), - )); + )) + .await; // even though this is kinda security by obscurity, let's still make a small // random delay sending a successful response per spec suggestion regarding diff --git a/src/api/client_server/state.rs b/src/api/client_server/state.rs index b895b949..b6a9f0ae 100644 --- a/src/api/client_server/state.rs +++ b/src/api/client_server/state.rs @@ -239,7 +239,7 @@ async fn send_state_event_for_key_helper( }, // admin room is a sensitive room, it should not ever be made public StateEventType::RoomJoinRules => { - if let Some(admin_room_id) = service::admin::Service::get_admin_room()? { + if let Some(admin_room_id) = service::admin::Service::get_admin_room().await? { if admin_room_id == room_id { if let Ok(join_rule) = serde_json::from_str::(json.json().get()) { if join_rule.join_rule == JoinRule::Public { @@ -254,7 +254,7 @@ async fn send_state_event_for_key_helper( }, // admin room is a sensitive room, it should not ever be made world readable StateEventType::RoomHistoryVisibility => { - if let Some(admin_room_id) = service::admin::Service::get_admin_room()? { + if let Some(admin_room_id) = service::admin::Service::get_admin_room().await? { if admin_room_id == room_id { if let Ok(visibility_content) = serde_json::from_str::(json.json().get()) diff --git a/src/database/mod.rs b/src/database/mod.rs index e31e0a78..ffcfc2b7 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -372,7 +372,8 @@ impl KeyValueDatabase { .send_message(RoomMessageEventContent::text_plain( "The Conduit account emergency password is set! Please unset it as soon as you finish \ admin account recovery!", - )); + )) + .await; } }, Err(e) => { @@ -473,7 +474,8 @@ impl KeyValueDatabase { .send_message(RoomMessageEventContent::text_plain(format!( "@room: the following is a message from the conduwuit puppy. it was sent on '{}':\n\n{}", update.date, update.message - ))); + ))) + .await; } } services() diff --git a/src/service/admin/debug/debug_commands.rs b/src/service/admin/debug/debug_commands.rs index c91c8f4f..db4a89b2 100644 --- a/src/service/admin/debug/debug_commands.rs +++ b/src/service/admin/debug/debug_commands.rs @@ -8,7 +8,10 @@ use tokio::sync::RwLock; use tracing::{debug, info, warn}; use tracing_subscriber::EnvFilter; -use crate::{api::server_server::parse_incoming_pdu, services, utils::HtmlEscape, Error, PduEvent, Result}; +use crate::{ + api::server_server::parse_incoming_pdu, service::sending::send::resolve_actual_dest, services, utils::HtmlEscape, + Error, PduEvent, Result, +}; pub(crate) async fn get_auth_chain(_body: Vec<&str>, event_id: Box) -> Result { let event_id = Arc::::from(event_id); @@ -428,3 +431,25 @@ pub(crate) async fn verify_json(body: Vec<&str>) -> Result, server_name: Box, no_cache: bool, +) -> Result { + if !services().globals.config.allow_federation { + return Ok(RoomMessageEventContent::text_plain( + "Federation is disabled on this homeserver.", + )); + } + + if server_name == services().globals.config.server_name { + return Ok(RoomMessageEventContent::text_plain( + "Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.", + )); + } + + let (actual_dest, hostname_uri) = resolve_actual_dest(&server_name, no_cache, true).await?; + + Ok(RoomMessageEventContent::text_plain(format!( + "Actual destination: {actual_dest:?} | Hostname URI: {hostname_uri}" + ))) +} diff --git a/src/service/admin/debug/mod.rs b/src/service/admin/debug/mod.rs index 8b617218..17521ced 100644 --- a/src/service/admin/debug/mod.rs +++ b/src/service/admin/debug/mod.rs @@ -3,7 +3,7 @@ use ruma::{events::room::message::RoomMessageEventContent, EventId, RoomId, Serv use self::debug_commands::{ change_log_level, force_device_list_updates, get_auth_chain, get_pdu, get_remote_pdu, get_remote_pdu_list, - get_room_state, parse_pdu, ping, sign_json, verify_json, + get_room_state, parse_pdu, ping, resolve_true_destination, sign_json, verify_json, }; use crate::Result; @@ -106,6 +106,17 @@ pub(crate) enum DebugCommand { /// This command needs a JSON blob provided in a Markdown code block below /// the command. VerifyJson, + + /// - Runs a server name through conduwuit's true destination resolution + /// process + /// + /// Useful for debugging well-known issues + ResolveTrueDestination { + server_name: Box, + + #[arg(short, long)] + no_cache: bool, + }, } pub(crate) async fn process(command: DebugCommand, body: Vec<&str>) -> Result { @@ -138,5 +149,9 @@ pub(crate) async fn process(command: DebugCommand, body: Vec<&str>) -> Result get_remote_pdu_list(body, server, force).await?, + DebugCommand::ResolveTrueDestination { + server_name, + no_cache, + } => resolve_true_destination(body, server_name, no_cache).await?, }) } diff --git a/src/service/admin/mod.rs b/src/service/admin/mod.rs index 50fb58c6..3797af65 100644 --- a/src/service/admin/mod.rs +++ b/src/service/admin/mod.rs @@ -127,7 +127,7 @@ impl Service { let conduit_user = UserId::parse(format!("@conduit:{}", services().globals.server_name())) .expect("@conduit:server_name is valid"); - if let Ok(Some(conduit_room)) = Self::get_admin_room() { + if let Ok(Some(conduit_room)) = Self::get_admin_room().await { loop { tokio::select! { event = receiver.recv_async() => { @@ -201,15 +201,17 @@ impl Service { Ok(()) } - pub(crate) fn process_message(&self, room_message: String, event_id: Arc) { + pub(crate) async fn process_message(&self, room_message: String, event_id: Arc) { self.sender - .send(AdminRoomEvent::ProcessMessage(room_message, event_id)) + .send_async(AdminRoomEvent::ProcessMessage(room_message, event_id)) + .await .unwrap(); } - pub(crate) fn send_message(&self, message_content: RoomMessageEventContent) { + pub(crate) async fn send_message(&self, message_content: RoomMessageEventContent) { self.sender - .send(AdminRoomEvent::SendMessage(message_content)) + .send_async(AdminRoomEvent::SendMessage(message_content)) + .await .unwrap(); } @@ -621,7 +623,7 @@ impl Service { /// /// Errors are propagated from the database, and will have None if there is /// no admin room - pub(crate) fn get_admin_room() -> Result> { + pub(crate) async fn get_admin_room() -> Result> { let admin_room_alias: Box = format!("#admins:{}", services().globals.server_name()) .try_into() .expect("#admins:server_name is a valid alias name"); @@ -636,7 +638,7 @@ impl Service { /// /// In conduit, this is equivalent to granting admin privileges. pub(crate) async fn make_user_admin(&self, user_id: &UserId, displayname: String) -> Result<()> { - if let Some(room_id) = Self::get_admin_room()? { + if let Some(room_id) = Self::get_admin_room().await? { let mutex_state = Arc::clone( services() .globals diff --git a/src/service/admin/room/room_moderation_commands.rs b/src/service/admin/room/room_moderation_commands.rs index fe870d87..8fe6a583 100644 --- a/src/service/admin/room/room_moderation_commands.rs +++ b/src/service/admin/room/room_moderation_commands.rs @@ -25,7 +25,7 @@ pub(crate) async fn process(command: RoomModerationCommand, body: Vec<&str>) -> .try_into() .expect("#admins:server_name is a valid alias name"); - if let Some(admin_room_id) = Service::get_admin_room()? { + if let Some(admin_room_id) = Service::get_admin_room().await? { if room.to_string().eq(&admin_room_id) || room.to_string().eq(&admin_room_alias) { return Ok(RoomMessageEventContent::text_plain("Not allowed to ban the admin room.")); } @@ -190,7 +190,7 @@ pub(crate) async fn process(command: RoomModerationCommand, body: Vec<&str>) -> for &room in &rooms_s { match <&RoomOrAliasId>::try_from(room) { Ok(room_alias_or_id) => { - if let Some(admin_room_id) = Service::get_admin_room()? { + if let Some(admin_room_id) = Service::get_admin_room().await? { if room.to_owned().eq(&admin_room_id) || room.to_owned().eq(&admin_room_alias) { info!("User specified admin room in bulk ban list, ignoring"); continue; diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index ad7539e0..e9fa67ed 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -512,9 +512,12 @@ impl Service { // the administrator can execute commands as conduit let from_conduit = pdu.sender == server_user && services().globals.emergency_password().is_none(); - if let Some(admin_room) = service::admin::Service::get_admin_room()? { + if let Some(admin_room) = service::admin::Service::get_admin_room().await? { if to_conduit && !from_conduit && admin_room == pdu.room_id { - services().admin.process_message(body, pdu.event_id.clone()); + services() + .admin + .process_message(body, pdu.event_id.clone()) + .await; } } } @@ -815,7 +818,7 @@ impl Service { ) -> Result> { let (pdu, pdu_json) = self.create_hash_and_sign_event(pdu_builder, sender, room_id, state_lock)?; - if let Some(admin_room) = service::admin::Service::get_admin_room()? { + if let Some(admin_room) = service::admin::Service::get_admin_room().await? { if admin_room == room_id { match pdu.event_type() { TimelineEventType::RoomEncryption => { diff --git a/src/service/sending/mod.rs b/src/service/sending/mod.rs index 969c3204..36e8f780 100644 --- a/src/service/sending/mod.rs +++ b/src/service/sending/mod.rs @@ -12,8 +12,8 @@ use crate::{services, Config, Error, Result}; mod appservice; mod data; -mod send; -mod sender; +pub(crate) mod send; +pub(crate) mod sender; pub(crate) use send::FedDest; pub(crate) struct Service { diff --git a/src/service/sending/send.rs b/src/service/sending/send.rs index fb96c8e6..90702bd3 100644 --- a/src/service/sending/send.rs +++ b/src/service/sending/send.rs @@ -13,6 +13,7 @@ use ruma::{ client::error::Error as RumaError, EndpointError, IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken, }, + events::room::message::RoomMessageEventContent, OwnedServerName, ServerName, }; use tracing::{debug, error, trace}; @@ -194,7 +195,7 @@ async fn get_actual_dest(server_name: &ServerName) -> Result { } else { cached = false; validate_dest(server_name)?; - resolve_actual_dest(server_name).await? + resolve_actual_dest(server_name, false, false).await? }; let string = dest.clone().into_https_string(); @@ -210,59 +211,220 @@ async fn get_actual_dest(server_name: &ServerName) -> Result { /// Implemented according to the specification at /// Numbers in comments below refer to bullet points in linked section of /// specification -async fn resolve_actual_dest(dest: &'_ ServerName) -> Result<(FedDest, String)> { +pub(crate) async fn resolve_actual_dest( + dest: &'_ ServerName, no_cache_dest: bool, admin_room_caller: bool, +) -> Result<(FedDest, String)> { trace!("Finding actual destination for {dest}"); let dest_str = dest.as_str().to_owned(); let mut hostname = dest_str.clone(); + + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain(format!( + "Checking for 1: IP literal with provided or default port" + ))) + .await; + } + + #[allow(clippy::single_match_else)] let actual_dest = match get_ip_with_port(&dest_str) { Some(host_port) => { debug!("1: IP literal with provided or default port"); + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain(format!( + "1: IP literal with provided or default port\n\nHost and Port: {host_port:?}" + ))) + .await; + } + host_port }, None => { + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain( + "Checking for 2: Hostname with included port", + )) + .await; + } + if let Some(pos) = dest_str.find(':') { debug!("2: Hostname with included port"); + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain("2: Hostname with included port")) + .await; + } let (host, port) = dest_str.split_at(pos); - query_and_cache_override(host, host, port.parse::().unwrap_or(8448)).await?; + if !no_cache_dest { + query_and_cache_override(host, host, port.parse::().unwrap_or(8448)).await?; + } + + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain(format!("Host: {host} | Port: {port}"))) + .await; + } FedDest::Named(host.to_owned(), port.to_owned()) } else { trace!("Requesting well known for {dest}"); + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain(format!( + "Checking for 3: A .well-known file is available. Requesting well-known for {dest}" + ))) + .await; + } + if let Some(delegated_hostname) = request_well_known(dest.as_str()).await? { debug!("3: A .well-known file is available"); + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain("3: A .well-known file is available")) + .await; + } + hostname = add_port_to_hostname(&delegated_hostname).into_uri_string(); match get_ip_with_port(&delegated_hostname) { - Some(host_and_port) => host_and_port, // 3.1: IP literal in .well-known file + Some(host_and_port) => { + debug!("3.1: IP literal in .well-known file"); + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain(format!( + "3.1: IP literal in .well-known file\n\nHost and Port: {host_and_port:?}" + ))) + .await; + } + + host_and_port + }, None => { + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain(format!( + "Checking for 3.2: Hostname with port in .well-known file" + ))) + .await; + } + if let Some(pos) = delegated_hostname.find(':') { debug!("3.2: Hostname with port in .well-known file"); + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain( + "3.2: Hostname with port in .well-known file", + )) + .await; + } let (host, port) = delegated_hostname.split_at(pos); - query_and_cache_override(host, host, port.parse::().unwrap_or(8448)).await?; + if !no_cache_dest { + query_and_cache_override(host, host, port.parse::().unwrap_or(8448)).await?; + } + + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain(format!( + "Host: {host} | Port: {port}" + ))) + .await; + } FedDest::Named(host.to_owned(), port.to_owned()) } else { trace!("Delegated hostname has no port in this branch"); + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain( + "Delegated hostname has no port specified", + )) + .await; + } + if let Some(hostname_override) = query_srv_record(&delegated_hostname).await? { debug!("3.3: SRV lookup successful"); + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain( + "3.3: SRV lookup successful", + )) + .await; + } let force_port = hostname_override.port(); - query_and_cache_override( - &delegated_hostname, - &hostname_override.hostname(), - force_port.unwrap_or(8448), - ) - .await?; + if !no_cache_dest { + query_and_cache_override( + &delegated_hostname, + &hostname_override.hostname(), + force_port.unwrap_or(8448), + ) + .await?; + } if let Some(port) = force_port { + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain(format!( + "Host: {delegated_hostname} | Port: {port}" + ))) + .await; + } + FedDest::Named(delegated_hostname, format!(":{port}")) } else { + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain(format!( + "Host: {delegated_hostname} | Port: 8448" + ))) + .await; + } + add_port_to_hostname(&delegated_hostname) } } else { debug!("3.4: No SRV records, just use the hostname from .well-known"); - query_and_cache_override(&delegated_hostname, &delegated_hostname, 8448).await?; + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain( + "3.4: No SRV records, just use the hostname from .well-known", + )) + .await; + } + + if !no_cache_dest { + query_and_cache_override(&delegated_hostname, &delegated_hostname, 8448) + .await?; + } + + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain(format!( + "Host: {delegated_hostname} | Port: 8448" + ))) + .await; + } + add_port_to_hostname(&delegated_hostname) } } @@ -270,21 +432,84 @@ async fn resolve_actual_dest(dest: &'_ ServerName) -> Result<(FedDest, String)> } } else { trace!("4: No .well-known or an error occured"); + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain( + "4: No .well-known or an error occured", + )) + .await; + } + if let Some(hostname_override) = query_srv_record(&dest_str).await? { debug!("4: No .well-known; SRV record found"); + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain( + "4: No .well-known; SRV record found", + )) + .await; + } let force_port = hostname_override.port(); - query_and_cache_override(&hostname, &hostname_override.hostname(), force_port.unwrap_or(8448)) + + if !no_cache_dest { + query_and_cache_override( + &hostname, + &hostname_override.hostname(), + force_port.unwrap_or(8448), + ) .await?; + } if let Some(port) = force_port { + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain(format!( + "Host: {hostname} | Port: {port}" + ))) + .await; + } + FedDest::Named(hostname.clone(), format!(":{port}")) } else { + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain(format!( + "Host: {hostname} | Port: 8448" + ))) + .await; + } + add_port_to_hostname(&hostname) } } else { debug!("4: No .well-known; 5: No SRV record found"); - query_and_cache_override(&dest_str, &dest_str, 8448).await?; + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain( + "4: No .well-known; 5: No SRV record found", + )) + .await; + } + + if !no_cache_dest { + query_and_cache_override(&dest_str, &dest_str, 8448).await?; + } + + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain(format!( + "Host: {dest_str} | Port: 8448" + ))) + .await; + } + add_port_to_hostname(&dest_str) } } @@ -306,6 +531,14 @@ async fn resolve_actual_dest(dest: &'_ ServerName) -> Result<(FedDest, String)> }; debug!("Actual destination: {actual_dest:?} hostname: {hostname:?}"); + if admin_room_caller { + services() + .admin + .send_message(RoomMessageEventContent::notice_plain(format!( + "Actual destination: {actual_dest:?} | Hostname: {hostname:?}" + ))) + .await; + } Ok((actual_dest, hostname.into_uri_string())) }