From 81ff8f1bd3d67d028e91a3e118a111762c25be12 Mon Sep 17 00:00:00 2001 From: timedout Date: Wed, 24 Dec 2025 01:34:08 +0000 Subject: [PATCH] feat: Allow using legacy, less secure validation --- Cargo.lock | 22 +- Cargo.toml | 2 +- conduwuit-example.toml | 15 +- src/api/client/membership/invite.rs | 13 +- src/api/server/invite.rs | 222 +++++++++++++++--- src/core/config/experiments/mod.rs | 25 ++ .../experiments/msc4284_policy_servers.rs | 58 +++++ src/core/config/mod.rs | 19 +- src/service/rooms/state_cache/update.rs | 8 +- src/service/rooms/timeline/create.rs | 2 +- 10 files changed, 316 insertions(+), 70 deletions(-) create mode 100644 src/core/config/experiments/mod.rs create mode 100644 src/core/config/experiments/msc4284_policy_servers.rs diff --git a/Cargo.lock b/Cargo.lock index 52cd586b..dbaa770d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4063,7 +4063,7 @@ checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" [[package]] name = "ruma" version = "0.10.1" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=3a6f29fda2c3ccf07282c746dc0e2021defc50bb#3a6f29fda2c3ccf07282c746dc0e2021defc50bb" dependencies = [ "assign", "js_int", @@ -4083,7 +4083,7 @@ dependencies = [ [[package]] name = "ruma-appservice-api" version = "0.10.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=3a6f29fda2c3ccf07282c746dc0e2021defc50bb#3a6f29fda2c3ccf07282c746dc0e2021defc50bb" dependencies = [ "js_int", "ruma-common", @@ -4095,7 +4095,7 @@ dependencies = [ [[package]] name = "ruma-client-api" version = "0.18.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=3a6f29fda2c3ccf07282c746dc0e2021defc50bb#3a6f29fda2c3ccf07282c746dc0e2021defc50bb" dependencies = [ "as_variant", "assign", @@ -4118,7 +4118,7 @@ dependencies = [ [[package]] name = "ruma-common" version = "0.13.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=3a6f29fda2c3ccf07282c746dc0e2021defc50bb#3a6f29fda2c3ccf07282c746dc0e2021defc50bb" dependencies = [ "as_variant", "base64 0.22.1", @@ -4150,7 +4150,7 @@ dependencies = [ [[package]] name = "ruma-events" version = "0.28.1" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=3a6f29fda2c3ccf07282c746dc0e2021defc50bb#3a6f29fda2c3ccf07282c746dc0e2021defc50bb" dependencies = [ "as_variant", "indexmap", @@ -4175,7 +4175,7 @@ dependencies = [ [[package]] name = "ruma-federation-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=3a6f29fda2c3ccf07282c746dc0e2021defc50bb#3a6f29fda2c3ccf07282c746dc0e2021defc50bb" dependencies = [ "bytes", "headers", @@ -4197,7 +4197,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" version = "0.9.5" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=3a6f29fda2c3ccf07282c746dc0e2021defc50bb#3a6f29fda2c3ccf07282c746dc0e2021defc50bb" dependencies = [ "js_int", "thiserror 2.0.17", @@ -4206,7 +4206,7 @@ dependencies = [ [[package]] name = "ruma-identity-service-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=3a6f29fda2c3ccf07282c746dc0e2021defc50bb#3a6f29fda2c3ccf07282c746dc0e2021defc50bb" dependencies = [ "js_int", "ruma-common", @@ -4216,7 +4216,7 @@ dependencies = [ [[package]] name = "ruma-macros" version = "0.13.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=3a6f29fda2c3ccf07282c746dc0e2021defc50bb#3a6f29fda2c3ccf07282c746dc0e2021defc50bb" dependencies = [ "cfg-if", "proc-macro-crate", @@ -4231,7 +4231,7 @@ dependencies = [ [[package]] name = "ruma-push-gateway-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=3a6f29fda2c3ccf07282c746dc0e2021defc50bb#3a6f29fda2c3ccf07282c746dc0e2021defc50bb" dependencies = [ "js_int", "ruma-common", @@ -4243,7 +4243,7 @@ dependencies = [ [[package]] name = "ruma-signatures" version = "0.15.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=3a6f29fda2c3ccf07282c746dc0e2021defc50bb#3a6f29fda2c3ccf07282c746dc0e2021defc50bb" dependencies = [ "base64 0.22.1", "ed25519-dalek", diff --git a/Cargo.toml b/Cargo.toml index 105f8795..91dc5c2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -351,7 +351,7 @@ version = "0.1.2" # Used for matrix spec type definitions and helpers [workspace.dependencies.ruma] git = "https://forgejo.ellis.link/continuwuation/ruwuma" -rev = "e60876b6ff2d207a46aa8910a02474268bac8592" +rev = "3a6f29fda2c3ccf07282c746dc0e2021defc50bb" features = [ "compat", "rand", diff --git a/conduwuit-example.toml b/conduwuit-example.toml index 1edb7e0e..065d53b5 100644 --- a/conduwuit-example.toml +++ b/conduwuit-example.toml @@ -1515,16 +1515,11 @@ # #block_non_admin_invites = false -# Enable or disable making requests to MSC4284 Policy Servers. -# It is recommended you keep this enabled unless you experience frequent -# connectivity issues, such as in a restricted networking environment. +# This item is undocumented. Please contribute documentation for it. # #enable_msc4284_policy_servers = true -# Enable running locally generated events through configured MSC4284 -# policy servers. You may wish to disable this if your server is -# single-user for a slight speed benefit in some rooms, but otherwise -# should leave it enabled. +# This item is undocumented. Please contribute documentation for it. # #policy_server_check_own_events = true @@ -1739,6 +1734,12 @@ # #ldap = false +# Configuration for protocol experiments that enable experimental +# features. Each one is associated with a matrix spec proposal, a list of +# which are published at https://spec.matrix.org/proposals/ +# +#experiments = false + [global.tls] # Path to a valid TLS certificate file. diff --git a/src/api/client/membership/invite.rs b/src/api/client/membership/invite.rs index 77ed7851..46a4108d 100644 --- a/src/api/client/membership/invite.rs +++ b/src/api/client/membership/invite.rs @@ -13,6 +13,7 @@ use ruma::{ room::member::{MembershipState, RoomMemberEventContent}, }, }; +use serde_json::value::RawValue; use service::Services; use super::banned_room_check; @@ -146,7 +147,17 @@ pub(crate) async fn invite_helper( ) .await?; - let invite_room_state = services.rooms.state.summary(&pdu, room_id).await; + let invite_room_state = services + .rooms + .state + .summary(&pdu, room_id) + .await + .into_iter() + .map(|evt| RawValue::from_string(evt.json().get().to_owned())) + .collect::, _>>() + .map_err(|e| { + err!(Request(BadJson(warn!("Could not clone invite state event: {e}")))) + })?; drop(state_lock); diff --git a/src/api/server/invite.rs b/src/api/server/invite.rs index cb14c79c..421f0c29 100644 --- a/src/api/server/invite.rs +++ b/src/api/server/invite.rs @@ -4,8 +4,9 @@ use axum::extract::State; use axum_client_ip::InsecureClientIp; use base64::{Engine as _, engine::general_purpose}; use conduwuit::{ - Err, Error, EventTypeExt, PduEvent, Result, RoomVersion, err, + Err, Error, EventTypeExt, PduEvent, Result, RoomVersion, debug, debug_warn, err, matrix::{Event, StateKey, event::gen_event_id}, + trace, utils::{self, hash::sha256}, warn, }; @@ -13,14 +14,14 @@ use ruma::{ CanonicalJsonValue, OwnedUserId, RoomId, RoomVersionId, ServerName, UserId, api::{client::error::ErrorKind, federation::membership::create_invite}, events::{ - AnyStateEvent, StateEventType, + StateEventType, room::{ create::RoomCreateEventContent, member::{MembershipState, RoomMemberEventContent}, }, }, - serde::Raw, }; +use serde_json::value::RawValue; use service::{Services, rooms::timeline::pdu_fits}; use crate::Ruma; @@ -29,7 +30,7 @@ use crate::Ruma; /// and complies with the room version's requirements. async fn check_invite_state( services: &Services, - stripped_state: &Vec>, + stripped_state: &Vec>, room_id: &RoomId, room_version_id: &RoomVersionId, ) -> Result<()> { @@ -43,11 +44,13 @@ async fn check_invite_state( // present and lines up with the other things we've been told, and then verify // any signatures present to ensure this isn't forged. for raw_event in stripped_state { - let event = raw_event - .deserialize_as::() - .map_err(|e| err!(Request(InvalidParam("Invalid state event: {e}"))))?; + trace!("Processing invite state event: {:?}", raw_event); + let canonical = utils::to_canonical_object(raw_event)?; + let event_id = gen_event_id(&canonical, room_version_id)?; + let event = PduEvent::from_id_val(&event_id, canonical.clone()) + .map_err(|e| err!(Request(InvalidParam("Invite state event is invalid: {e}"))))?; if event.state_key().is_none() { - return Err!(Request(InvalidParam("State event missing event type."))); + return Err!(Request(InvalidParam("Invite state event missing event type."))); } let key = event .event_type() @@ -57,15 +60,20 @@ async fn check_invite_state( } // verify the event - let canonical = utils::to_canonical_object(raw_event)?; - if !pdu_fits(&mut canonical.clone()) { - return Err!(Request(InvalidParam("An invite state event PDU is too large"))); + if !pdu_fits(&canonical) { + return Err!(Request(InvalidParam( + "An invite state event ({event_id}) is too large" + ))); } services .server_keys .verify_event(&canonical, Some(room_version_id)) .await - .map_err(|e| err!(Request(InvalidParam("Signature failed verification: {e}"))))?; + .map_err(|e| { + err!(Request(InvalidParam( + "Signature failed verification on event {event_id}: {e}" + ))) + })?; // Ensure all events are in the same room if event.room_id_or_hash() != room_id { @@ -156,9 +164,12 @@ async fn check_invite_event( } // Check: signature is valid + let as_obj = &mut utils::to_canonical_object(invite_event)?; + // remove the event_id before verification + as_obj.remove("event_id"); services .server_keys - .verify_event(&utils::to_canonical_object(invite_event)?, Some(room_version_id)) + .verify_event(as_obj, Some(room_version_id)) .await .map_err(|e| { err!(Request(InvalidParam("Invite event signature failed verification: {e}"))) @@ -167,15 +178,146 @@ async fn check_invite_event( Ok(state_key.to_owned()) } +/// Performs only legacy checks on the invite, for use when the requesting +/// server doesn't support matrix v1.16 invites. +/// This is significantly less secure than the full checks and should only be +/// used if the user has allowed it. +async fn legacy_check_invite( + services: &Services, + origin: &ServerName, + invite_event: &PduEvent, + stripped_state: &Vec>, + room_id: &RoomId, + room_version_id: &RoomVersionId, +) -> Result { + // Ensure the sender is from origin, the state key is a user ID that points at a + // local user, the event type is m.room.member with membership "invite", and + // the room ID matches. + if invite_event.sender().server_name() != origin { + return Err!(Request(InvalidParam( + "Invite event sender's server does not match the origin server." + ))); + } + let state_key: &UserId = invite_event + .state_key() + .ok_or_else(|| err!(Request(MissingParam("Invite event missing state_key."))))? + .try_into() + .map_err(|e| err!(Request(InvalidParam("Invalid state_key property: {e}"))))?; + if !services.globals.server_is_ours(state_key.server_name()) { + return Err!(Request(InvalidParam( + "Invite event state_key does not belong to this homeserver." + ))); + } + if let Some(evt_room_id) = invite_event.room_id() { + if evt_room_id != room_id { + return Err!(Request(InvalidParam( + "Invite event room ID does not match the expected room ID." + ))); + } + } else { + return Err!(Request(MissingParam("Invite event missing room ID."))); + } + let content = invite_event.get_content::()?; + if content.membership != MembershipState::Invite { + return Err!(Request(InvalidParam("Invite event is not a membership invite."))); + } + + // We can also opportunistically check that the m.room.create event is present + // and matches the room version, to avoid accepting invites to rooms that + // don't match. + let mut has_create = false; + for raw_event in stripped_state { + let canonical = utils::to_canonical_object(raw_event)?; + if let Some(event_type) = canonical.get("type") { + if event_type.as_str().unwrap_or_default() == "m.room.create" { + has_create = true; + let event_id = gen_event_id(&canonical, room_version_id)?; + let event = PduEvent::from_id_val(&event_id, canonical.clone()).map_err(|e| { + err!(Request(InvalidParam("Invite state event is invalid: {e}"))) + })?; + + // We can verify that the room ID is correct now + let version = RoomVersion::new(room_version_id)?; + if version.room_ids_as_hashes { + let given_room_id = event.event_id().as_str().replace('$', "!"); + if given_room_id != room_id.as_str() { + return Err!(Request(InvalidParam( + "m.room.create event ID does not match the room ID." + ))); + } + } else if event.room_id().unwrap() != room_id { + return Err!(Request(InvalidParam( + "m.room.create room ID does not match the room ID." + ))); + } + // Everything's as fine as we're getting with this event + break; + } + } + } + if !has_create { + warn!( + "federated invite is missing m.room.create event in stripped state, the remote \ + server is either outdated or trying something fishy." + ); + } + + Ok(state_key.to_owned()) +} + +/// Checks the incoming event is allowed and not forged. +/// If the MSC4311 enforcement experiment is enabled, performs full checks, +/// otherwise performs legacy checks only. +async fn check_invite( + services: &Services, + invite_event: &PduEvent, + stripped_state: &Vec>, + origin: &ServerName, + room_id: &RoomId, + room_version_id: &RoomVersionId, +) -> Result { + if services.config.experiments.enforce_msc4311 { + debug!("Checking invite event validity"); + let user = check_invite_event(services, invite_event, origin, room_id, room_version_id) + .await + .inspect_err(|e| { + debug_warn!("Invite event validity check failed: {e}"); + })?; + debug!("Checking invite state validity"); + check_invite_state(services, stripped_state, room_id, room_version_id) + .await + .inspect_err(|e| { + debug_warn!("Invite state validity check failed: {e}"); + })?; + Ok(user) + } else { + debug!("Performing legacy invite checks"); + legacy_check_invite( + services, + origin, + invite_event, + stripped_state, + room_id, + room_version_id, + ) + .await + .inspect_err(|e| { + debug_warn!("Legacy invite validity check failed: {e}"); + }) + } +} + /// # `PUT /_matrix/federation/v2/invite/{roomId}/{eventId}` /// /// Invites a remote user to a room. -#[tracing::instrument(skip_all, fields(%client), name = "invite")] +#[tracing::instrument(skip_all, fields(%client, room_id=?body.room_id), name = "invite")] pub(crate) async fn create_invite_route( State(services): State, InsecureClientIp(client): InsecureClientIp, body: Ruma, ) -> Result { + debug!("Received invite request from {}: {:?}", body.room_id, body.origin()); + // ACL check origin services .rooms @@ -184,6 +326,7 @@ pub(crate) async fn create_invite_route( .await?; if !services.server.supported_room_version(&body.room_version) { + debug_warn!("Unsupported room version: {}", body.room_version); return Err(Error::BadRequest( ErrorKind::IncompatibleRoomVersion { room_version: body.room_version.clone() }, "Server does not support this room version.", @@ -192,6 +335,7 @@ pub(crate) async fn create_invite_route( if let Some(server) = body.room_id.server_name() { if services.moderation.is_remote_server_forbidden(server) { + warn!("Received invite to room created by a banned server: {}. Rejecting.", server); return Err!(Request(Forbidden("Server is banned on this homeserver."))); } } @@ -201,7 +345,7 @@ pub(crate) async fn create_invite_route( .is_remote_server_forbidden(body.origin()) { warn!( - "Received federated/remote invite from banned server {} for room ID {}. Rejecting.", + "Received invite from banned server {} for room ID {}. Rejecting.", body.origin(), body.room_id ); @@ -215,26 +359,29 @@ pub(crate) async fn create_invite_route( // We need to hash and sign the event before we can generate the event ID. // It is important that this signed event does not get sent back to the caller // until we've verified this isn't incorrect. + trace!(event=?signed_event, "Hashing & signing invite event"); services .server_keys .hash_and_sign_event(&mut signed_event, &body.room_version) .map_err(|e| err!(Request(InvalidParam("Failed to sign event: {e}"))))?; let event_id = gen_event_id(&signed_event.clone(), &body.room_version)?; if event_id != body.event_id { - return Err!(Request(InvalidParam( - "Event ID does not match the generated event ID ({} vs {}).", - event_id, - body.event_id, - ))); + warn!("Event ID mismatch: expected {}, got {}", event_id, body.event_id); + return Err!(Request(InvalidParam("Event ID does not match the generated event ID."))); } let pdu = PduEvent::from_id_val(&event_id, signed_event.clone()) .map_err(|e| err!(Request(InvalidParam("Invite event is invalid: {e}"))))?; - // Check the invite event is valid. - let recipient_user = - check_invite_event(&services, &pdu, body.origin(), &body.room_id, &body.room_version) - .await?; + let recipient_user = check_invite( + &services, + &pdu, + &body.invite_room_state, + body.origin(), + &body.room_id, + &body.room_version, + ) + .await?; // Make sure the room isn't banned and we allow invites if services.config.block_non_admin_invites && !services.users.is_admin(&recipient_user).await @@ -254,22 +401,9 @@ pub(crate) async fn create_invite_route( .acl_check(recipient_user.server_name(), &body.room_id) .await?; - // And check the invite state is valid. - check_invite_state(&services, &body.invite_room_state, &body.room_id, &body.room_version) - .await?; - // Add event_id back signed_event.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.to_string())); - let sender_user: &UserId = signed_event - .get("sender") - .try_into() - .map_err(|e| err!(Request(InvalidParam("Invalid sender property: {e}"))))?; - - let mut invite_state = body.invite_room_state.clone(); - - invite_state.push(pdu.to_format()); - // If we are active in the room, the remote server will notify us about the // join/invite through /send. If we are not in the room, we need to manually // record the invited state for client /sync through update_membership(), and @@ -280,6 +414,19 @@ pub(crate) async fn create_invite_route( .server_in_room(services.globals.server_name(), &body.room_id) .await { + let mut invite_state: Vec = body + .invite_room_state + .clone() + .into_iter() + .map(|v| utils::to_canonical_object(&v).unwrap().into()) + .collect(); + + invite_state.push(pdu.to_canonical_object().into()); + let sender_user: &UserId = signed_event + .get("sender") + .try_into() + .map_err(|e| err!(Request(InvalidParam("Invalid sender property: {e}"))))?; + debug!("Marking user {} as invited to remote room {}", recipient_user, body.room_id); services .rooms .state_cache @@ -318,6 +465,7 @@ pub(crate) async fn create_invite_route( } } + debug!("Invite is valid, returning signed event"); Ok(create_invite::v2::Response { event: services .sending diff --git a/src/core/config/experiments/mod.rs b/src/core/config/experiments/mod.rs new file mode 100644 index 00000000..46f722ee --- /dev/null +++ b/src/core/config/experiments/mod.rs @@ -0,0 +1,25 @@ +mod msc4284_policy_servers; + +use conduwuit_macros::config_example_generator; +use serde::Deserialize; + +#[derive(Clone, Debug, Default, Deserialize)] +#[config_example_generator(filename = "conduwuit-example.toml", section = "global.experiments")] +pub struct Experiments { + /// Enforce MSC4311's updated requirements on all incoming invites. + /// + /// This drastically increases the security and filtering capabilities + /// when processing invites over federation, at the cost of compatibility. + /// Servers that do not implement MSC4311 will be unable to send invites + /// to your server when this is enabled, including continuwuity 0.5.0 and + /// below. + /// + /// default: false + /// Introduced in: (unreleased) + #[serde(default)] + pub enforce_msc4311: bool, + + /// MSC4284 Policy Server support configuration. + #[serde(default)] + pub msc4284: msc4284_policy_servers::MSC4248, +} diff --git a/src/core/config/experiments/msc4284_policy_servers.rs b/src/core/config/experiments/msc4284_policy_servers.rs new file mode 100644 index 00000000..883ea23a --- /dev/null +++ b/src/core/config/experiments/msc4284_policy_servers.rs @@ -0,0 +1,58 @@ +use conduwuit_macros::config_example_generator; +use serde::Deserialize; + +fn true_fn() -> bool { true } + +fn default_federation_timeout() -> u64 { 25 } + +#[derive(Clone, Debug, Default, Deserialize)] +#[config_example_generator( + filename = "conduwuit-example.toml", + section = "global.experiments.msc4284" +)] +pub struct MSC4248 { + /// Enable or disable making requests to MSC4284 Policy Servers. + /// It is recommended you keep this enabled unless you experience frequent + /// connectivity issues, such as in a restricted networking environment. + /// + /// default: true + /// Introduced in: 0.5.0 + #[serde(default = "true_fn")] + pub enabled: bool, + + /// Enable running locally generated events through configured MSC4284 + /// policy servers. You may wish to disable this if your server is + /// single-user for a slight speed benefit in some rooms, but otherwise + /// should leave it enabled. + /// + /// If the room's policy server configuration requires event signatures, + /// this option is effectively ignored, as otherwise local events would + /// be rejected for missing the policy server's signature. + /// + /// default: true + /// Introduced in: 0.5.0 + #[serde(default = "true_fn")] + pub check_own_events: bool, + + /// MSC4284 Policy server request timeout (seconds). Generally policy + /// servers should respond near instantly, however may slow down under + /// load. If a policy server doesn't respond in a short amount of time, the + /// room it is configured in may become unusable if this limit is set too + /// high. 25 seconds is a good default, however should be raised if you + /// experience too many connection issues. + /// + /// Please be aware that policy requests are *NOT* currently re-tried, so if + /// a spam check request fails, the event will be assumed to be not spam, + /// which in some cases may result in spam being sent to or received from + /// the room that would typically be prevented. + /// + /// If your request timeout is too low, and the policy server requires + /// signatures, you may find that you are unable to send events that are + /// accepted regardless. + /// + /// About policy servers: https://matrix.org/blog/2025/04/introducing-policy-servers/ + /// default: 25 + /// Introduced in: 0.5.0 + #[serde(default = "default_federation_timeout")] + pub request_timeout: u64, +} diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index 5dcaff5c..8bd744b4 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -1,5 +1,6 @@ #![allow(clippy::doc_link_with_quotes)] pub mod check; +pub mod experiments; pub mod manager; pub mod proxy; @@ -1734,16 +1735,9 @@ pub struct Config { #[serde(default)] pub block_non_admin_invites: bool, - /// Enable or disable making requests to MSC4284 Policy Servers. - /// It is recommended you keep this enabled unless you experience frequent - /// connectivity issues, such as in a restricted networking environment. #[serde(default = "true_fn")] pub enable_msc4284_policy_servers: bool, - /// Enable running locally generated events through configured MSC4284 - /// policy servers. You may wish to disable this if your server is - /// single-user for a slight speed benefit in some rooms, but otherwise - /// should leave it enabled. #[serde(default = "true_fn")] pub policy_server_check_own_events: bool, @@ -2003,6 +1997,13 @@ pub struct Config { // external structure; separate section #[serde(default)] pub blurhashing: BlurhashConfig, + + /// Configuration for protocol experiments that enable experimental + /// features. Each one is associated with a matrix spec proposal, a list of + /// which are published at https://spec.matrix.org/proposals/ + #[serde(default)] + pub experiments: experiments::Experiments, + #[serde(flatten)] #[allow(clippy::zero_sized_map_values)] // this is a catchall, the map shouldn't be zero at runtime @@ -2216,7 +2217,7 @@ struct ListeningAddr { addrs: Either>, } -const DEPRECATED_KEYS: &[&str; 9] = &[ +const DEPRECATED_KEYS: &[&str; 11] = &[ "cache_capacity", "conduit_cache_capacity_modifier", "max_concurrent_requests", @@ -2226,6 +2227,8 @@ const DEPRECATED_KEYS: &[&str; 9] = &[ "well_known_support_role", "well_known_support_email", "well_known_support_mxid", + "enable_msc4284_policy_servers", + "policy_server_check_own_events", ]; impl Config { diff --git a/src/service/rooms/state_cache/update.rs b/src/service/rooms/state_cache/update.rs index 378e3537..9162f637 100644 --- a/src/service/rooms/state_cache/update.rs +++ b/src/service/rooms/state_cache/update.rs @@ -4,10 +4,10 @@ use conduwuit::{Err, Event, Pdu, Result, implement, is_not_empty, utils::ReadyEx use database::{Json, serialize_key}; use futures::StreamExt; use ruma::{ - OwnedServerName, RoomId, UserId, + CanonicalJsonValue, OwnedServerName, RoomId, UserId, events::{ - AnyStateEvent, AnyStrippedStateEvent, GlobalAccountDataEventType, - RoomAccountDataEventType, StateEventType, + AnyStrippedStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, + StateEventType, direct::DirectEvent, invite_permission_config::FilterLevel, room::{ @@ -334,7 +334,7 @@ pub async fn mark_as_invited( user_id: &UserId, room_id: &RoomId, sender_user: &UserId, - last_state: Option>>, + last_state: Option>, invite_via: Option>, ) -> Result<()> { // return an error for blocked invites. ignored invites aren't handled here diff --git a/src/service/rooms/timeline/create.rs b/src/service/rooms/timeline/create.rs index 731ae13e..e74833ac 100644 --- a/src/service/rooms/timeline/create.rs +++ b/src/service/rooms/timeline/create.rs @@ -23,7 +23,7 @@ use serde_json::value::{RawValue, to_raw_value}; use super::RoomMutexGuard; -pub fn pdu_fits(owned_obj: &mut CanonicalJsonObject) -> bool { +pub fn pdu_fits(owned_obj: &CanonicalJsonObject) -> bool { // room IDs, event IDs, senders, types, and state keys must all be <= 255 bytes if let Some(CanonicalJsonValue::String(room_id)) = owned_obj.get("room_id") { if room_id.len() > 255 {