diff --git a/Cargo.lock b/Cargo.lock index b2a47390..cc48430a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,6 +399,7 @@ dependencies = [ "http", "hyper", "image", + "itertools 0.12.1", "jsonwebtoken", "lru-cache", "nix", @@ -419,6 +420,7 @@ dependencies = [ "serde", "serde_html_form", "serde_json", + "serde_regex", "serde_yaml", "sha-1", "thiserror", @@ -2455,6 +2457,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.5" diff --git a/Cargo.toml b/Cargo.toml index 88383391..22f4e1bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,9 @@ ring = "0.17.7" hickory-resolver = "0.24" # Used to find matching events for appservices regex = "1.8.1" +# Used to load forbidden room/user regex from config +serde_regex = "1.1.0" +itertools = "0.12.0" # jwt jsonwebtokens jsonwebtoken = "9.2.0" # Performance measurements diff --git a/src/api/client_server/account.rs b/src/api/client_server/account.rs index 0226abc7..ef926d84 100644 --- a/src/api/client_server/account.rs +++ b/src/api/client_server/account.rs @@ -55,6 +55,17 @@ pub async fn get_register_available_route( )); } + if services() + .globals + .forbidden_usernames() + .is_match(user_id.localpart()) + { + return Err(Error::BadRequest( + ErrorKind::Unknown, + "Username is forbidden.", + )); + } + // TODO add check for appservice namespaces // If no if check is true we have an username that's available to be used. @@ -105,6 +116,16 @@ pub async fn register_route(body: Ruma) -> Result loop { diff --git a/src/api/client_server/alias.rs b/src/api/client_server/alias.rs index 7cbe9fa1..e2d361c0 100644 --- a/src/api/client_server/alias.rs +++ b/src/api/client_server/alias.rs @@ -43,6 +43,17 @@ pub async fn create_alias_route( )); } + if services() + .globals + .forbidden_alias_names() + .is_match(body.room_alias.alias()) + { + return Err(Error::BadRequest( + ErrorKind::Unknown, + "Room alias is forbidden.", + )); + } + if services() .rooms .alias diff --git a/src/api/client_server/room.rs b/src/api/client_server/room.rs index e3e8a746..5e9f718a 100644 --- a/src/api/client_server/room.rs +++ b/src/api/client_server/room.rs @@ -81,6 +81,16 @@ pub async fn create_room_route( body.room_alias_name .as_ref() .map_or(Ok(None), |localpart| { + if services() + .globals + .forbidden_alias_names() + .is_match(localpart) + { + return Err(Error::BadRequest( + ErrorKind::Unknown, + "Room alias is forbidden.", + )); + } // TODO: Check for invalid characters and maximum length let alias = RoomAliasId::parse(format!( "#{}:{}", diff --git a/src/config/mod.rs b/src/config/mod.rs index fb1e2f31..4e140baf 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,3 +1,6 @@ +use itertools::Itertools; +use regex::RegexSet; + use std::{ collections::BTreeMap, fmt, @@ -83,6 +86,14 @@ pub struct Config { #[serde(flatten)] pub catchall: BTreeMap, + + #[serde(default = "RegexSet::empty")] + #[serde(with = "serde_regex")] + pub forbidden_alias_names: RegexSet, + + #[serde(default = "RegexSet::empty")] + #[serde(with = "serde_regex")] + pub forbidden_usernames: RegexSet, } #[derive(Clone, Debug, Deserialize)] @@ -194,6 +205,12 @@ impl fmt::Display for Config { } &lst.join(", ") }), + ("Forbidden usernames", { + &self.forbidden_usernames.patterns().iter().join(", ") + }), + ("Forbidden room names", { + &self.forbidden_alias_names.patterns().iter().join(", ") + }), ]; let mut msg: String = "Active config values:\n\n".to_owned(); diff --git a/src/database/mod.rs b/src/database/mod.rs index 41da857c..84c89fcb 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -7,6 +7,7 @@ use crate::{ }; use abstraction::{KeyValueDatabaseEngine, KvTree}; use directories::ProjectDirs; +use itertools::Itertools; use lru_cache::LruCache; use ruma::{ @@ -942,6 +943,56 @@ impl KeyValueDatabase { latest_database_version ); + { + let patterns = &services().globals.config.forbidden_usernames; + if !patterns.is_empty() { + for user_id in services() + .users + .iter() + .filter_map(std::result::Result::ok) + .filter(|user| !services().users.is_deactivated(user).unwrap_or(true)) + .filter(|user| user.server_name() == services().globals.server_name()) + { + let matches = patterns.matches(user_id.localpart()); + if matches.matched_any() { + warn!( + "User {} matches the following forbidden username patterns: {}", + user_id.to_string(), + matches + .into_iter() + .map(|x| &patterns.patterns()[x]) + .join(", ") + ) + } + } + } + } + + { + let patterns = &services().globals.config.forbidden_alias_names; + if !patterns.is_empty() { + for address in services().rooms.metadata.iter_ids() { + let room_id = address?; + let room_aliases = services().rooms.alias.local_aliases_for_room(&room_id); + for room_alias_result in room_aliases { + let room_alias = room_alias_result?; + let matches = patterns.matches(room_alias.alias()); + if matches.matched_any() { + warn!( + "Room with alias {} ({}) matches the following forbidden room name patterns: {}", + room_alias, + &room_id, + matches + .into_iter() + .map(|x| &patterns.patterns()[x]) + .join(", ") + ) + } + } + } + } + } + info!( "Loaded {} database with version {}", services().globals.config.database_backend, diff --git a/src/service/globals/mod.rs b/src/service/globals/mod.rs index ab66ed45..09ba38c3 100644 --- a/src/service/globals/mod.rs +++ b/src/service/globals/mod.rs @@ -1,5 +1,6 @@ mod data; pub use data::Data; +use regex::RegexSet; use ruma::{ serde::Base64, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedServerSigningKeyId, OwnedUserId, @@ -353,6 +354,14 @@ impl Service { &self.config.emergency_password } + pub fn forbidden_alias_names(&self) -> &RegexSet { + &self.config.forbidden_alias_names + } + + pub fn forbidden_usernames(&self) -> &RegexSet { + &self.config.forbidden_usernames + } + pub fn supported_room_versions(&self) -> Vec { let mut room_versions: Vec = vec![]; room_versions.extend(self.stable_room_versions.clone());