diff --git a/conduwuit-example.toml b/conduwuit-example.toml index b726fd28..90a84ac1 100644 --- a/conduwuit-example.toml +++ b/conduwuit-example.toml @@ -269,6 +269,19 @@ url_preview_check_root_domain = false # Defaults to true allow_profile_lookup_federation_requests = true +# Config option to automatically deactivate the account of any user who attempts to join a: +# - banned room +# - forbidden room alias +# - room alias or ID with a forbidden server name +# +# This may be useful if all your banned lists consist of toxic rooms or servers that no good faith user would ever attempt to join, and +# to automatically remediate the problem without any admin user intervention. +# +# This will also make the user leave all rooms. Federation (e.g. remote room invites) are ignored here. +# +# Defaults to false as rooms can be banned for non-moderation-related reasons +#auto_deactivate_banned_room_attempts = false + ### Misc diff --git a/src/api/client_server/account.rs b/src/api/client_server/account.rs index b5d92d15..41343183 100644 --- a/src/api/client_server/account.rs +++ b/src/api/client_server/account.rs @@ -533,7 +533,7 @@ pub(crate) async fn deactivate_route(body: Ruma) -> Res } // Make the user leave all rooms before deactivation - client_server::leave_all_rooms(sender_user).await?; + client_server::leave_all_rooms(sender_user).await; // Remove devices and mark account as deactivated services().users.deactivate_account(sender_user)?; diff --git a/src/api/client_server/membership.rs b/src/api/client_server/membership.rs index 54af1d41..edda0c64 100644 --- a/src/api/client_server/membership.rs +++ b/src/api/client_server/membership.rs @@ -21,12 +21,13 @@ use ruma::{ room::{ join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent}, member::{MembershipState, RoomMemberEventContent}, + message::RoomMessageEventContent, }, StateEventType, TimelineEventType, }, serde::Base64, state_res, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, OwnedServerName, - OwnedUserId, RoomId, RoomVersionId, UserId, + OwnedUserId, RoomId, RoomVersionId, ServerName, UserId, }; use serde_json::value::{to_raw_value, RawValue as RawJsonValue}; use tokio::sync::RwLock; @@ -40,6 +41,91 @@ use crate::{ Error, PduEvent, Result, Ruma, }; +/// Checks if the room is banned in any way possible and the sender user is not +/// an admin. +/// +/// Performs automatic deactivation if `auto_deactivate_banned_room_attempts` is +/// enabled +#[tracing::instrument] +async fn banned_room_check(user_id: &UserId, room_id: Option<&RoomId>, server_name: Option<&ServerName>) -> Result<()> { + if !services().users.is_admin(user_id)? { + if let Some(room_id) = room_id { + if services().rooms.metadata.is_banned(room_id)? + || services() + .globals + .config + .forbidden_remote_server_names + .contains(&room_id.server_name().unwrap().to_owned()) + { + warn!( + "User {user_id} who is not an admin attempted to send an invite for or attempted to join a banned \ + room or banned room server name: {room_id}." + ); + + if services() + .globals + .config + .auto_deactivate_banned_room_attempts + { + warn!("Automatically deactivating user {user_id} due to attempted banned room join"); + services() + .admin + .send_message(RoomMessageEventContent::text_plain(format!( + "Automatically deactivating user {user_id} due to attempted banned room join" + ))) + .await; + + // ignore errors + leave_all_rooms(user_id).await; + _ = services().users.deactivate_account(user_id); + } + + return Err(Error::BadRequest( + ErrorKind::forbidden(), + "This room is banned on this homeserver.", + )); + } + } else if let Some(server_name) = server_name { + if services() + .globals + .config + .forbidden_remote_server_names + .contains(&server_name.to_owned()) + { + warn!( + "User {user_id} who is not an admin tried joining a room which has the server name {server_name} \ + that is globally forbidden. Rejecting.", + ); + + if services() + .globals + .config + .auto_deactivate_banned_room_attempts + { + warn!("Automatically deactivating user {user_id} due to attempted banned room join"); + services() + .admin + .send_message(RoomMessageEventContent::text_plain(format!( + "Automatically deactivating user {user_id} due to attempted banned room join" + ))) + .await; + + // ignore errors + leave_all_rooms(user_id).await; + _ = services().users.deactivate_account(user_id); + } + + return Err(Error::BadRequest( + ErrorKind::forbidden(), + "This remote server is banned on this homeserver.", + )); + } + } + } + + Ok(()) +} + /// # `POST /_matrix/client/r0/rooms/{roomId}/join` /// /// Tries to join the sender user into a room. @@ -53,32 +139,7 @@ pub(crate) async fn join_room_by_id_route( ) -> Result { let sender_user = body.sender_user.as_ref().expect("user is authenticated"); - if services().rooms.metadata.is_banned(&body.room_id)? && !services().users.is_admin(sender_user)? { - return Err(Error::BadRequest( - ErrorKind::forbidden(), - "This room is banned on this homeserver.", - )); - } - - if let Some(server) = body.room_id.server_name() { - if services() - .globals - .config - .forbidden_remote_server_names - .contains(&server.to_owned()) - && !services().users.is_admin(sender_user)? - { - warn!( - "User {sender_user} tried joining room ID {} which has a server name that is globally forbidden. \ - Rejecting.", - body.room_id - ); - return Err(Error::BadRequest( - ErrorKind::forbidden(), - "This remote server is banned on this homeserver.", - )); - } - } + banned_room_check(sender_user, Some(&body.room_id), body.room_id.server_name()).await?; // There is no body.server_name for /roomId/join let mut servers = services() @@ -131,31 +192,7 @@ pub(crate) async fn join_room_by_id_or_alias_route( let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias) { Ok(room_id) => { - if services().rooms.metadata.is_banned(&room_id)? && !services().users.is_admin(sender_user)? { - return Err(Error::BadRequest( - ErrorKind::forbidden(), - "This room is banned on this homeserver.", - )); - } - - if let Some(server) = room_id.server_name() { - if services() - .globals - .config - .forbidden_remote_server_names - .contains(&server.to_owned()) - && !services().users.is_admin(sender_user)? - { - warn!( - "User {sender_user} tried joining room ID {room_id} which has a server name that is globally \ - forbidden. Rejecting.", - ); - return Err(Error::BadRequest( - ErrorKind::forbidden(), - "This remote server is banned on this homeserver.", - )); - } - } + banned_room_check(sender_user, Some(&room_id), room_id.server_name()).await?; let mut servers = body.server_name.clone(); servers.extend( @@ -186,69 +223,9 @@ pub(crate) async fn join_room_by_id_or_alias_route( (servers, room_id) }, Err(room_alias) => { - if services() - .globals - .config - .forbidden_remote_server_names - .contains(&room_alias.server_name().to_owned()) - && !services().users.is_admin(sender_user)? - { - warn!( - "User {sender_user} tried joining room alias {room_alias} which has a server name that is \ - globally forbidden. Rejecting.", - ); - return Err(Error::BadRequest( - ErrorKind::forbidden(), - "This remote server is banned on this homeserver.", - )); - } - let response = get_alias_helper(room_alias.clone(), Some(body.server_name.clone())).await?; - if services().rooms.metadata.is_banned(&response.room_id)? && !services().users.is_admin(sender_user)? { - return Err(Error::BadRequest( - ErrorKind::forbidden(), - "This room is banned on this homeserver.", - )); - } - - if services() - .globals - .config - .forbidden_remote_server_names - .contains(&room_alias.server_name().to_owned()) - && !services().users.is_admin(sender_user)? - { - warn!( - "User {sender_user} tried joining room alias {room_alias} with room ID {}, which the alias has a \ - server name that is globally forbidden. Rejecting.", - &response.room_id - ); - return Err(Error::BadRequest( - ErrorKind::forbidden(), - "This remote server is banned on this homeserver.", - )); - } - - if let Some(server) = response.room_id.server_name() { - if services() - .globals - .config - .forbidden_remote_server_names - .contains(&server.to_owned()) - && !services().users.is_admin(sender_user)? - { - warn!( - "User {sender_user} tried joining room alias {room_alias} with room ID {}, which has a server \ - name that is globally forbidden. Rejecting.", - &response.room_id - ); - return Err(Error::BadRequest( - ErrorKind::forbidden(), - "This remote server is banned on this homeserver.", - )); - } - } + banned_room_check(sender_user, Some(&response.room_id), Some(room_alias.server_name())).await?; let mut servers = body.server_name; servers.extend(response.servers); @@ -321,30 +298,7 @@ pub(crate) async fn invite_user_route(body: Ruma) -> R )); } - if services().rooms.metadata.is_banned(&body.room_id)? && !services().users.is_admin(sender_user)? { - info!( - "Local user {} who is not an admin attempted to send an invite for banned room {}.", - &sender_user, &body.room_id - ); - return Err(Error::BadRequest( - ErrorKind::forbidden(), - "This room is banned on this homeserver.", - )); - } - - if let Some(server) = body.room_id.server_name() { - if services() - .globals - .config - .forbidden_remote_server_names - .contains(&server.to_owned()) - { - return Err(Error::BadRequest( - ErrorKind::forbidden(), - "Server is banned on this homeserver.", - )); - } - } + banned_room_check(sender_user, Some(&body.room_id), body.room_id.server_name()).await?; if let invite_user::v3::InvitationRecipient::UserId { user_id, @@ -1606,8 +1560,9 @@ pub(crate) async fn invite_helper( Ok(()) } -// Make a user leave all their joined rooms -pub(crate) async fn leave_all_rooms(user_id: &UserId) -> Result<()> { +// Make a user leave all their joined rooms, forgets all rooms, and ignores +// errors +pub(crate) async fn leave_all_rooms(user_id: &UserId) { let all_rooms = services() .rooms .state_cache @@ -1627,10 +1582,9 @@ pub(crate) async fn leave_all_rooms(user_id: &UserId) -> Result<()> { }; // ignore errors + _ = services().rooms.state_cache.forget(&room_id, user_id); _ = leave_room(user_id, &room_id, None).await; } - - Ok(()) } pub(crate) async fn leave_room(user_id: &UserId, room_id: &RoomId, reason: Option) -> Result<()> { diff --git a/src/config/mod.rs b/src/config/mod.rs index 3a7471f8..8b84a9b1 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -207,6 +207,8 @@ pub(crate) struct Config { #[serde(default = "Vec::new")] pub(crate) auto_join_rooms: Vec, + #[serde(default)] + pub(crate) auto_deactivate_banned_room_attempts: bool, #[serde(default = "default_rocksdb_log_level")] pub(crate) rocksdb_log_level: String, @@ -612,6 +614,10 @@ impl fmt::Display for Config { "Allow incoming profile lookup federation requests", &self.allow_profile_lookup_federation_requests.to_string(), ), + ( + "Auto deactivate banned room join attempts", + &self.auto_deactivate_banned_room_attempts.to_string(), + ), ("Notification push path", &self.notification_push_path), ("Allow room creation", &self.allow_room_creation.to_string()), ( diff --git a/src/service/admin/user/user_commands.rs b/src/service/admin/user/user_commands.rs index a8375064..1aa8d4ea 100644 --- a/src/service/admin/user/user_commands.rs +++ b/src/service/admin/user/user_commands.rs @@ -167,7 +167,7 @@ pub(crate) async fn deactivate( services().users.deactivate_account(&user_id)?; if leave_rooms { - leave_all_rooms(&user_id).await?; + leave_all_rooms(&user_id).await; } Ok(RoomMessageEventContent::text_plain(format!( @@ -282,7 +282,7 @@ pub(crate) async fn deactivate_all(body: Vec<&str>, leave_rooms: bool, force: bo if leave_rooms { for &user_id in &user_ids { - _ = leave_all_rooms(user_id).await; + leave_all_rooms(user_id).await; } }