2025-04-27 03:33:29 +00:00
|
|
|
mod ban;
|
|
|
|
|
mod forget;
|
|
|
|
|
mod invite;
|
|
|
|
|
mod join;
|
|
|
|
|
mod kick;
|
|
|
|
|
mod knock;
|
|
|
|
|
mod leave;
|
|
|
|
|
mod members;
|
|
|
|
|
mod unban;
|
|
|
|
|
|
2025-08-31 12:30:27 +05:00
|
|
|
use std::{cmp::Ordering, net::IpAddr};
|
2025-04-27 03:33:29 +00:00
|
|
|
|
|
|
|
|
use axum::extract::State;
|
|
|
|
|
use futures::{FutureExt, StreamExt};
|
2025-08-25 01:29:45 +05:00
|
|
|
use ruma::{
|
|
|
|
|
OwnedRoomId, OwnedServerName, RoomId, RoomOrAliasId, ServerName, UserId,
|
|
|
|
|
api::client::membership::joined_rooms,
|
|
|
|
|
};
|
|
|
|
|
use tuwunel_core::{Err, Result, result::LogErr, utils::shuffle, warn};
|
2025-04-27 03:33:29 +00:00
|
|
|
use tuwunel_service::Services;
|
|
|
|
|
|
|
|
|
|
pub(crate) use self::{
|
|
|
|
|
ban::ban_user_route,
|
|
|
|
|
forget::forget_room_route,
|
2025-08-25 19:12:27 +05:00
|
|
|
invite::invite_user_route,
|
2025-04-27 03:33:29 +00:00
|
|
|
join::{join_room_by_id_or_alias_route, join_room_by_id_route},
|
|
|
|
|
kick::kick_user_route,
|
|
|
|
|
knock::knock_room_route,
|
|
|
|
|
leave::leave_room_route,
|
|
|
|
|
members::{get_member_events_route, joined_members_route},
|
|
|
|
|
unban::unban_user_route,
|
|
|
|
|
};
|
2025-08-25 19:12:27 +05:00
|
|
|
use crate::Ruma;
|
2025-04-27 03:33:29 +00:00
|
|
|
|
|
|
|
|
/// # `POST /_matrix/client/r0/joined_rooms`
|
|
|
|
|
///
|
|
|
|
|
/// Lists all rooms the user has joined.
|
|
|
|
|
pub(crate) async fn joined_rooms_route(
|
|
|
|
|
State(services): State<crate::State>,
|
|
|
|
|
body: Ruma<joined_rooms::v3::Request>,
|
|
|
|
|
) -> Result<joined_rooms::v3::Response> {
|
|
|
|
|
Ok(joined_rooms::v3::Response {
|
|
|
|
|
joined_rooms: services
|
|
|
|
|
.state_cache
|
|
|
|
|
.rooms_joined(body.sender_user())
|
|
|
|
|
.map(ToOwned::to_owned)
|
|
|
|
|
.collect()
|
|
|
|
|
.await,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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(skip(services))]
|
|
|
|
|
pub(crate) async fn banned_room_check(
|
|
|
|
|
services: &Services,
|
|
|
|
|
user_id: &UserId,
|
|
|
|
|
room_id: Option<&RoomId>,
|
|
|
|
|
server_name: Option<&ServerName>,
|
|
|
|
|
client_ip: IpAddr,
|
|
|
|
|
) -> Result {
|
|
|
|
|
if services.users.is_admin(user_id).await {
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-25 01:29:45 +05:00
|
|
|
// TODO: weird condition
|
2025-04-27 03:33:29 +00:00
|
|
|
if let Some(room_id) = room_id {
|
2025-08-22 20:15:54 +05:00
|
|
|
if services.metadata.is_banned(room_id).await
|
2025-06-29 03:33:29 +00:00
|
|
|
|| (room_id.server_name().is_some()
|
|
|
|
|
&& services
|
|
|
|
|
.config
|
|
|
|
|
.forbidden_remote_server_names
|
|
|
|
|
.is_match(
|
|
|
|
|
room_id
|
|
|
|
|
.server_name()
|
|
|
|
|
.expect("legacy room mxid")
|
|
|
|
|
.host(),
|
|
|
|
|
)) {
|
2025-04-27 03:33:29 +00:00
|
|
|
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}"
|
|
|
|
|
);
|
|
|
|
|
|
2025-08-25 01:29:45 +05:00
|
|
|
maybe_deactivate(services, user_id, client_ip)
|
|
|
|
|
.await
|
|
|
|
|
.log_err()
|
|
|
|
|
.ok();
|
2025-04-27 03:33:29 +00:00
|
|
|
|
|
|
|
|
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
|
|
|
|
|
}
|
|
|
|
|
} else if let Some(server_name) = server_name {
|
|
|
|
|
if services
|
|
|
|
|
.config
|
|
|
|
|
.forbidden_remote_server_names
|
|
|
|
|
.is_match(server_name.host())
|
|
|
|
|
{
|
|
|
|
|
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.",
|
|
|
|
|
);
|
|
|
|
|
|
2025-08-25 01:29:45 +05:00
|
|
|
maybe_deactivate(services, user_id, client_ip)
|
|
|
|
|
.await
|
|
|
|
|
.log_err()
|
|
|
|
|
.ok();
|
2025-04-27 03:33:29 +00:00
|
|
|
|
|
|
|
|
return Err!(Request(Forbidden("This remote server is banned on this homeserver.")));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2025-08-25 01:29:45 +05:00
|
|
|
|
|
|
|
|
async fn maybe_deactivate(services: &Services, user_id: &UserId, client_ip: IpAddr) -> Result {
|
|
|
|
|
if services
|
|
|
|
|
.server
|
|
|
|
|
.config
|
|
|
|
|
.auto_deactivate_banned_room_attempts
|
|
|
|
|
{
|
|
|
|
|
warn!("Automatically deactivating user {user_id} due to attempted banned room join");
|
|
|
|
|
|
|
|
|
|
if services.server.config.admin_room_notices {
|
|
|
|
|
services
|
|
|
|
|
.admin
|
|
|
|
|
.send_text(&format!(
|
|
|
|
|
"Automatically deactivating user {user_id} due to attempted banned room \
|
|
|
|
|
join from IP {client_ip}"
|
|
|
|
|
))
|
|
|
|
|
.await;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-25 19:12:27 +05:00
|
|
|
services
|
|
|
|
|
.deactivate
|
|
|
|
|
.full_deactivate(user_id)
|
2025-08-25 01:29:45 +05:00
|
|
|
.boxed()
|
|
|
|
|
.await?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2025-08-25 19:12:27 +05:00
|
|
|
|
|
|
|
|
// TODO: should this be in services? banned check would have to resolve again if
|
|
|
|
|
// room_id is not available at callsite
|
|
|
|
|
async fn get_join_params(
|
|
|
|
|
services: &Services,
|
|
|
|
|
user_id: &UserId,
|
|
|
|
|
room_id_or_alias: &RoomOrAliasId,
|
|
|
|
|
via: &[OwnedServerName],
|
|
|
|
|
) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> {
|
|
|
|
|
// servers tried first, additional_servers shuffled then tried after
|
2025-12-02 06:28:50 +00:00
|
|
|
let (room_id, mut primary_servers, mut additional_servers) =
|
2025-08-25 19:12:27 +05:00
|
|
|
match OwnedRoomId::try_from(room_id_or_alias.to_owned()) {
|
|
|
|
|
// if room id, shuffle via + room_id server_name ...
|
|
|
|
|
| Ok(room_id) => {
|
|
|
|
|
let mut additional_servers = via.to_vec();
|
|
|
|
|
|
|
|
|
|
if let Some(server) = room_id.server_name() {
|
|
|
|
|
additional_servers.push(server.to_owned());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(room_id, Vec::new(), additional_servers)
|
|
|
|
|
},
|
|
|
|
|
// ... if room alias, resolve and don't shuffle ...
|
|
|
|
|
| Err(room_alias) => {
|
2025-09-20 06:56:21 +05:00
|
|
|
let (room_id, servers) = services.alias.resolve_alias(&room_alias).await?;
|
2025-08-25 19:12:27 +05:00
|
|
|
|
|
|
|
|
(room_id, servers, Vec::new())
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// either way, add invited vias
|
|
|
|
|
additional_servers.extend(
|
|
|
|
|
services
|
|
|
|
|
.state_cache
|
|
|
|
|
.servers_invite_via(&room_id)
|
|
|
|
|
.map(ToOwned::to_owned)
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.await,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// either way, add invite senders' servers
|
|
|
|
|
additional_servers.extend(
|
|
|
|
|
services
|
|
|
|
|
.state_cache
|
|
|
|
|
.invite_state(user_id, &room_id)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|event| event.get_field("sender").ok().flatten())
|
|
|
|
|
.filter_map(|sender: &str| UserId::parse(sender).ok())
|
|
|
|
|
.map(|user| user.server_name().to_owned()),
|
|
|
|
|
);
|
|
|
|
|
|
2025-12-02 06:28:50 +00:00
|
|
|
primary_servers.sort_unstable();
|
|
|
|
|
primary_servers.dedup();
|
|
|
|
|
shuffle(&mut primary_servers);
|
|
|
|
|
|
2025-08-25 19:12:27 +05:00
|
|
|
// shuffle additionals, append to base servers
|
2025-12-02 06:28:50 +00:00
|
|
|
additional_servers.sort_unstable();
|
|
|
|
|
additional_servers.dedup();
|
|
|
|
|
shuffle(&mut additional_servers);
|
|
|
|
|
|
|
|
|
|
let mut servers: Vec<_> = room_id_or_alias
|
|
|
|
|
.server_name()
|
|
|
|
|
.filter(|_| room_id_or_alias.is_room_alias_id())
|
|
|
|
|
.map(ToOwned::to_owned)
|
|
|
|
|
.into_iter()
|
|
|
|
|
.chain(primary_servers.into_iter())
|
|
|
|
|
.chain(additional_servers.into_iter())
|
|
|
|
|
.collect();
|
2025-08-25 19:12:27 +05:00
|
|
|
|
2025-08-31 12:30:27 +05:00
|
|
|
// sort deprioritized servers last
|
|
|
|
|
servers.sort_by(|a, b| {
|
|
|
|
|
let a_matches = services
|
|
|
|
|
.server
|
|
|
|
|
.config
|
|
|
|
|
.deprioritize_joins_through_servers
|
|
|
|
|
.is_match(a.host());
|
2025-12-02 06:28:50 +00:00
|
|
|
|
2025-08-31 12:30:27 +05:00
|
|
|
let b_matches = services
|
|
|
|
|
.server
|
|
|
|
|
.config
|
|
|
|
|
.deprioritize_joins_through_servers
|
|
|
|
|
.is_match(b.host());
|
|
|
|
|
|
|
|
|
|
if a_matches && !b_matches {
|
|
|
|
|
Ordering::Greater
|
|
|
|
|
} else if !a_matches && b_matches {
|
|
|
|
|
Ordering::Less
|
|
|
|
|
} else {
|
|
|
|
|
Ordering::Equal
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-25 19:12:27 +05:00
|
|
|
Ok((room_id, servers))
|
|
|
|
|
}
|