diff --git a/src/client_server/account.rs b/src/client_server/account.rs index 4b3ad0d..fb223d5 100644 --- a/src/client_server/account.rs +++ b/src/client_server/account.rs @@ -34,7 +34,7 @@ use ruma::{ EventType, }, identifiers::RoomName, - push, RoomAliasId, RoomId, RoomVersionId, UserId, + push, RoomAliasId, RoomId, UserId, }; use serde_json::value::to_raw_value; use tracing::info; @@ -282,7 +282,7 @@ pub async fn register_route( let mut content = RoomCreateEventContent::new(conduit_user.clone()); content.federate = true; content.predecessor = None; - content.room_version = RoomVersionId::Version6; + content.room_version = db.globals.default_room_version(); // 1. The room create event db.rooms.build_and_append_pdu( diff --git a/src/client_server/capabilities.rs b/src/client_server/capabilities.rs index f86b23b..36296b5 100644 --- a/src/client_server/capabilities.rs +++ b/src/client_server/capabilities.rs @@ -1,9 +1,6 @@ -use crate::{ConduitResult, Ruma}; -use ruma::{ - api::client::r0::capabilities::{ - get_capabilities, Capabilities, RoomVersionStability, RoomVersionsCapability, - }, - RoomVersionId, +use crate::{database::DatabaseGuard, ConduitResult, Ruma}; +use ruma::api::client::r0::capabilities::{ + get_capabilities, Capabilities, RoomVersionStability, RoomVersionsCapability, }; use std::collections::BTreeMap; @@ -17,17 +14,28 @@ use rocket::get; feature = "conduit_bin", get("/_matrix/client/r0/capabilities", data = "<_body>") )] -#[tracing::instrument(skip(_body))] +#[tracing::instrument(skip(_body, db))] pub async fn get_capabilities_route( _body: Ruma, + db: DatabaseGuard, ) -> ConduitResult { let mut available = BTreeMap::new(); - available.insert(RoomVersionId::Version5, RoomVersionStability::Stable); - available.insert(RoomVersionId::Version6, RoomVersionStability::Stable); + if db.globals.allow_unstable_room_versions() { + for room_version in &db.globals.unstable_room_versions { + available.insert(room_version.clone(), RoomVersionStability::Stable); + } + } else { + for room_version in &db.globals.unstable_room_versions { + available.insert(room_version.clone(), RoomVersionStability::Unstable); + } + } + for room_version in &db.globals.stable_room_versions { + available.insert(room_version.clone(), RoomVersionStability::Stable); + } let mut capabilities = Capabilities::new(); capabilities.room_versions = RoomVersionsCapability { - default: RoomVersionId::Version6, + default: db.globals.default_room_version(), available, }; diff --git a/src/client_server/membership.rs b/src/client_server/membership.rs index ec685ec..9147242 100644 --- a/src/client_server/membership.rs +++ b/src/client_server/membership.rs @@ -551,7 +551,7 @@ async fn join_room_by_id_helper( federation::membership::create_join_event_template::v1::Request { room_id, user_id: sender_user, - ver: &[RoomVersionId::Version5, RoomVersionId::Version6], + ver: &db.globals.supported_room_versions(), }, ) .await; @@ -566,12 +566,7 @@ async fn join_room_by_id_helper( let (make_join_response, remote_server) = make_join_response_and_server?; let room_version = match make_join_response.room_version { - Some(room_version) - if room_version == RoomVersionId::Version5 - || room_version == RoomVersionId::Version6 => - { - room_version - } + Some(room_version) if db.rooms.is_supported_version(&db, &room_version) => room_version, _ => return Err(Error::BadServerResponse("Room version is not supported")), }; @@ -890,9 +885,10 @@ pub(crate) async fn invite_helper<'a>( None }; - // If there was no create event yet, assume we are creating a version 6 room right now + // If there was no create event yet, assume we are creating a room with the default + // version right now let room_version_id = create_event_content - .map_or(RoomVersionId::Version6, |create_event| { + .map_or(db.globals.default_room_version(), |create_event| { create_event.room_version }); let room_version = @@ -1039,7 +1035,8 @@ pub(crate) async fn invite_helper<'a>( let pub_key_map = RwLock::new(BTreeMap::new()); // We do not add the event_id field to the pdu here because of signature and hashes checks - let (event_id, value) = match crate::pdu::gen_event_id_canonical_json(&response.event) { + let (event_id, value) = match crate::pdu::gen_event_id_canonical_json(&response.event, &db) + { Ok(t) => t, Err(_) => { // Event could not be converted to canonical json diff --git a/src/client_server/room.rs b/src/client_server/room.rs index 47c7ee6..a63b341 100644 --- a/src/client_server/room.rs +++ b/src/client_server/room.rs @@ -23,7 +23,7 @@ use ruma::{ EventType, }, serde::{CanonicalJsonObject, JsonObject}, - RoomAliasId, RoomId, RoomVersionId, + RoomAliasId, RoomId, }; use serde_json::{json, value::to_raw_value}; use std::{ @@ -109,7 +109,7 @@ pub async fn create_room_route( let room_version = match body.room_version.clone() { Some(room_version) => { - if room_version == RoomVersionId::Version5 || room_version == RoomVersionId::Version6 { + if db.rooms.is_supported_version(&db, &room_version) { room_version } else { return Err(Error::BadRequest( @@ -118,7 +118,7 @@ pub async fn create_room_route( )); } } - None => RoomVersionId::Version6, + None => db.globals.default_room_version(), }; let content = match &body.creation_content { @@ -505,10 +505,7 @@ pub async fn upgrade_room_route( ) -> ConduitResult { let sender_user = body.sender_user.as_ref().expect("user is authenticated"); - if !matches!( - body.new_version, - RoomVersionId::Version5 | RoomVersionId::Version6 - ) { + if !db.rooms.is_supported_version(&db, &body.new_version) { return Err(Error::BadRequest( ErrorKind::UnsupportedRoomVersion, "This server does not support that room version.", diff --git a/src/database.rs b/src/database.rs index 080e24b..c533a45 100644 --- a/src/database.rs +++ b/src/database.rs @@ -24,7 +24,7 @@ use rocket::{ request::{FromRequest, Request}, Shutdown, State, }; -use ruma::{DeviceId, EventId, RoomId, ServerName, UserId}; +use ruma::{DeviceId, EventId, RoomId, RoomVersionId, ServerName, UserId}; use serde::{de::IgnoredAny, Deserialize}; use std::{ collections::{BTreeMap, HashMap, HashSet}, @@ -63,8 +63,12 @@ pub struct Config { allow_federation: bool, #[serde(default = "true_fn")] allow_room_creation: bool, + #[serde(default = "true_fn")] + allow_unstable_room_versions: bool, #[serde(default = "false_fn")] pub allow_jaeger: bool, + #[serde(default = "default_room_version")] + default_room_version: RoomVersionId, #[serde(default = "false_fn")] pub tracing_flame: bool, #[serde(default)] @@ -137,6 +141,10 @@ fn default_max_concurrent_requests() -> u16 { 100 } +fn default_room_version() -> RoomVersionId { + RoomVersionId::Version6 +} + fn default_log() -> String { "info,state_res=warn,rocket=off,_=off,sled=off".to_owned() } diff --git a/src/database/globals.rs b/src/database/globals.rs index 05ecb56..de5b250 100644 --- a/src/database/globals.rs +++ b/src/database/globals.rs @@ -4,7 +4,8 @@ use ruma::{ client::r0::sync::sync_events, federation::discovery::{ServerSigningKeys, VerifyKey}, }, - DeviceId, EventId, MilliSecondsSinceUnixEpoch, RoomId, ServerName, ServerSigningKeyId, UserId, + DeviceId, EventId, MilliSecondsSinceUnixEpoch, RoomId, RoomVersionId, ServerName, + ServerSigningKeyId, UserId, }; use std::{ collections::{BTreeMap, HashMap}, @@ -39,6 +40,8 @@ pub struct Globals { keypair: Arc, dns_resolver: TokioAsyncResolver, jwt_decoding_key: Option>, + pub stable_room_versions: Vec, + pub unstable_room_versions: Vec, pub(super) server_signingkeys: Arc, pub bad_event_ratelimiter: Arc>>, pub bad_signature_ratelimiter: Arc, RateLimitState>>>, @@ -132,7 +135,16 @@ impl Globals { .as_ref() .map(|secret| jsonwebtoken::DecodingKey::from_secret(secret.as_bytes()).into_static()); - let s = Self { + // Supported and stable room versions + let stable_room_versions = vec![RoomVersionId::Version6]; + // Experimental, partially supported room versions + let unstable_room_versions = vec![ + RoomVersionId::Version3, + RoomVersionId::Version4, + RoomVersionId::Version5, + ]; + + let mut s = Self { globals, config, keypair: Arc::new(keypair), @@ -143,6 +155,8 @@ impl Globals { tls_name_override, server_signingkeys, jwt_decoding_key, + stable_room_versions, + unstable_room_versions, bad_event_ratelimiter: Arc::new(RwLock::new(HashMap::new())), bad_signature_ratelimiter: Arc::new(RwLock::new(HashMap::new())), servername_ratelimiter: Arc::new(RwLock::new(HashMap::new())), @@ -155,6 +169,14 @@ impl Globals { fs::create_dir_all(s.get_media_folder())?; + if !s + .supported_room_versions() + .contains(&s.config.default_room_version) + { + error!("Room version in config isn't supported, falling back to Version 6"); + s.config.default_room_version = RoomVersionId::Version6; + }; + Ok(s) } @@ -214,6 +236,14 @@ impl Globals { self.config.allow_room_creation } + pub fn allow_unstable_room_versions(&self) -> bool { + self.config.allow_unstable_room_versions + } + + pub fn default_room_version(&self) -> RoomVersionId { + self.config.default_room_version.clone() + } + pub fn trusted_servers(&self) -> &[Box] { &self.config.trusted_servers } @@ -246,6 +276,15 @@ impl Globals { &self.config.turn_secret } + pub fn supported_room_versions(&self) -> Vec { + let mut room_versions: Vec = vec![]; + room_versions.extend(self.stable_room_versions.clone()); + if self.allow_unstable_room_versions() { + room_versions.extend(self.unstable_room_versions.clone()); + }; + room_versions + } + /// TODO: the key valid until timestamp is only honored in room version > 4 /// Remove the outdated keys and insert the new ones. /// diff --git a/src/database/rooms.rs b/src/database/rooms.rs index c5b795b..a0455db 100644 --- a/src/database/rooms.rs +++ b/src/database/rooms.rs @@ -129,6 +129,12 @@ pub struct Rooms { } impl Rooms { + /// Returns true if a given room version is supported + #[tracing::instrument(skip(self, db))] + pub fn is_supported_version(&self, db: &Database, room_version: &RoomVersionId) -> bool { + db.globals.supported_room_versions().contains(room_version) + } + /// Builds a StateMap by iterating over all keys that start /// with state_hash, this gives the full state for the given state_hash. #[tracing::instrument(skip(self))] @@ -1983,9 +1989,10 @@ impl Rooms { None }; - // If there was no create event yet, assume we are creating a version 6 room right now + // If there was no create event yet, assume we are creating a room with the default + // version right now let room_version_id = create_event_content - .map_or(RoomVersionId::Version6, |create_event| { + .map_or(db.globals.default_room_version(), |create_event| { create_event.room_version }); let room_version = RoomVersion::new(&room_version_id).expect("room version is supported"); @@ -2778,11 +2785,7 @@ impl Rooms { let (make_leave_response, remote_server) = make_leave_response_and_server?; let room_version_id = match make_leave_response.room_version { - Some(version) - if version == RoomVersionId::Version5 || version == RoomVersionId::Version6 => - { - version - } + Some(version) if self.is_supported_version(&db, &version) => version, _ => return Err(Error::BadServerResponse("Room version is not supported")), }; @@ -3448,4 +3451,24 @@ impl Rooms { Ok(()) } + + /// Returns the room's version. + #[tracing::instrument(skip(self))] + pub fn get_room_version(&self, room_id: &RoomId) -> Result { + let create_event = self.room_state_get(room_id, &EventType::RoomCreate, "")?; + + let create_event_content: Option = create_event + .as_ref() + .map(|create_event| { + serde_json::from_str(create_event.content.get()).map_err(|e| { + warn!("Invalid create event: {}", e); + Error::bad_database("Invalid create event in db.") + }) + }) + .transpose()?; + let room_version = create_event_content + .map(|create_event| create_event.room_version) + .ok_or_else(|| Error::BadDatabase("Invalid room version"))?; + Ok(room_version) + } } diff --git a/src/pdu.rs b/src/pdu.rs index 0f99f43..6fe322a 100644 --- a/src/pdu.rs +++ b/src/pdu.rs @@ -1,4 +1,4 @@ -use crate::Error; +use crate::{Database, Error}; use ruma::{ events::{ room::member::RoomMemberEventContent, AnyEphemeralRoomEvent, AnyInitialStateEvent, @@ -6,7 +6,7 @@ use ruma::{ EventType, StateEvent, }, serde::{CanonicalJsonObject, CanonicalJsonValue, Raw}, - state_res, EventId, MilliSecondsSinceUnixEpoch, RoomId, RoomVersionId, UInt, UserId, + state_res, EventId, MilliSecondsSinceUnixEpoch, RoomId, UInt, UserId, }; use serde::{Deserialize, Serialize}; use serde_json::{ @@ -331,16 +331,24 @@ impl Ord for PduEvent { /// Returns a tuple of the new `EventId` and the PDU as a `BTreeMap`. pub(crate) fn gen_event_id_canonical_json( pdu: &RawJsonValue, + db: &Database, ) -> crate::Result<(EventId, CanonicalJsonObject)> { - let value = serde_json::from_str(pdu.get()).map_err(|e| { + let value: CanonicalJsonObject = serde_json::from_str(pdu.get()).map_err(|e| { warn!("Error parsing incoming event {:?}: {:?}", pdu, e); Error::BadServerResponse("Invalid PDU in server response") })?; + let room_id = value + .get("room_id") + .and_then(|id| RoomId::try_from(id.as_str()?).ok()) + .expect("Invalid room id in event"); + + let room_version_id = db.rooms.get_room_version(&room_id); + let event_id = EventId::try_from(&*format!( "${}", // Anything higher than version3 behaves the same - ruma::signatures::reference_hash(&value, &RoomVersionId::Version6) + ruma::signatures::reference_hash(&value, &room_version_id?) .expect("ruma can calculate reference hashes") )) .expect("ruma's reference hashes are valid event ids"); diff --git a/src/server_server.rs b/src/server_server.rs index 482edf0..5357a90 100644 --- a/src/server_server.rs +++ b/src/server_server.rs @@ -725,7 +725,7 @@ pub async fn send_transaction_message_route( for pdu in &body.pdus { // We do not add the event_id field to the pdu here because of signature and hashes checks - let (event_id, value) = match crate::pdu::gen_event_id_canonical_json(pdu) { + let (event_id, value) = match crate::pdu::gen_event_id_canonical_json(pdu, &db) { Ok(t) => t, Err(_) => { // Event could not be converted to canonical json @@ -1911,7 +1911,7 @@ pub(crate) fn fetch_and_handle_outliers<'a>( Ok(res) => { warn!("Got {} over federation", id); let (calculated_event_id, value) = - match crate::pdu::gen_event_id_canonical_json(&res.pdu) { + match crate::pdu::gen_event_id_canonical_json(&res.pdu, &db) { Ok(t) => t, Err(_) => { back_off((**id).clone()); @@ -2670,10 +2670,12 @@ pub fn create_join_event_template_route( None }; - // If there was no create event yet, assume we are creating a version 6 room right now - let room_version_id = create_event_content.map_or(RoomVersionId::Version6, |create_event| { - create_event.room_version - }); + // If there was no create event yet, assume we are creating a room with the default version + // right now + let room_version_id = create_event_content + .map_or(db.globals.default_room_version(), |create_event| { + create_event.room_version + }); let room_version = RoomVersion::new(&room_version_id).expect("room version is supported"); if !body.ver.contains(&room_version_id) { @@ -2813,7 +2815,7 @@ async fn create_join_event( // let mut auth_cache = EventMap::new(); // We do not add the event_id field to the pdu here because of signature and hashes checks - let (event_id, value) = match crate::pdu::gen_event_id_canonical_json(pdu) { + let (event_id, value) = match crate::pdu::gen_event_id_canonical_json(pdu, &db) { Ok(t) => t, Err(_) => { // Event could not be converted to canonical json @@ -2937,8 +2939,7 @@ pub async fn create_invite_route( return Err(Error::bad_config("Federation is disabled.")); } - if body.room_version != RoomVersionId::Version5 && body.room_version != RoomVersionId::Version6 - { + if !db.rooms.is_supported_version(&db, &body.room_version) { return Err(Error::BadRequest( ErrorKind::IncompatibleRoomVersion { room_version: body.room_version.clone(),