1use std::{
2 borrow::Borrow,
3 collections::{BTreeSet, HashMap, HashSet},
4 iter::once,
5 mem::take,
6 sync::Arc,
7};
8
9use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, future::join};
10use ruma::{
11 CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedServerName, OwnedUserId, RoomId,
12 RoomOrAliasId, RoomVersionId, UserId,
13 api::{error::ErrorKind, federation},
14 canonical_json::to_canonical_value,
15 events::{
16 StateEventType,
17 room::{
18 create::RoomCreateEventContent,
19 join_rules::RoomJoinRulesEventContent,
20 member::{MembershipState, RoomMemberEventContent},
21 },
22 },
23 room::{AllowRule, JoinRule},
24 room_version_rules::RoomVersionRules,
25};
26use serde_json::value::{RawValue as RawJsonValue, to_raw_value};
27use tuwunel_core::{
28 Err, Result, async_noinline, at, debug, debug_error, debug_info, debug_warn, err, error,
29 implement, info,
30 matrix::{event::gen_event_id_canonical_json, room_version},
31 pdu::{Pdu, PduBuilder, check_rules},
32 trace,
33 utils::{self, BoolExt, IterStream, ReadyExt, math::Expected, shuffle},
34 warn,
35};
36
37use super::Service;
38use crate::{
39 Services,
40 federation::{Candidates, WhenAllBackedOff},
41 rooms::{
42 state::{IdMapState, RoomMutexGuard},
43 state_compressor::{CompressedState, HashSetCompressStateEvent},
44 state_res,
45 },
46};
47
48#[derive(Debug)]
49pub struct Join<'a> {
50 pub sender_user: &'a UserId,
51 pub room_id: &'a RoomId,
52 pub orig_room_id: Option<&'a RoomOrAliasId>,
53 pub reason: Option<String>,
54 pub servers: &'a [OwnedServerName],
55 pub is_appservice: bool,
56 pub extra_content: Option<CanonicalJsonObject>,
57}
58
59#[implement(Service)]
60#[async_noinline]
62#[tracing::instrument(
63 name = "join",
64 level = "debug",
65 skip_all,
66 fields(%sender_user, %room_id)
67)]
68pub async fn join<'a>(
69 &'a self,
70 Join {
71 sender_user,
72 room_id,
73 orig_room_id,
74 reason,
75 servers,
76 is_appservice,
77 extra_content,
78 }: Join<'a>,
79) -> Result {
80 let servers =
81 get_servers_for_room(&self.services, sender_user, room_id, orig_room_id, servers).await?;
82
83 let (federation_lock, state_lock) = self.lock_join(room_id, &servers).await;
84
85 let user_is_guest = !is_appservice
86 && self
87 .services
88 .users
89 .is_deactivated(sender_user)
90 .await
91 .unwrap_or(false);
92
93 if user_is_guest
94 && !self
95 .services
96 .state_accessor
97 .guest_can_join(room_id)
98 .await
99 {
100 return Err!(Request(Forbidden("Guests are not allowed to join this room")));
101 }
102
103 if self
104 .services
105 .state_cache
106 .is_joined(sender_user, room_id)
107 .await
108 {
109 debug_warn!(%sender_user, %room_id, "User is already joined.");
110 return Ok(());
111 }
112
113 if self
115 .services
116 .state_accessor
117 .get_member(room_id, sender_user)
118 .await
119 .is_ok_and(|content| content.membership == MembershipState::Ban)
120 && self
121 .services
122 .state_cache
123 .is_invited(sender_user, room_id)
124 .await
125 .is_false()
126 {
127 debug_warn!(%sender_user, %room_id, "Banned user attempted to join.");
128 return Err!(Request(Forbidden("You are banned from the room.")));
129 }
130
131 match federation_lock {
132 | Some(federation_lock) if !self.is_local_join(room_id, &servers).await =>
133 self.join_remote(
134 sender_user,
135 room_id,
136 reason,
137 &servers,
138 federation_lock,
139 state_lock,
140 extra_content,
141 )
142 .boxed() .await?,
144 | federation_lock => {
145 drop(federation_lock);
146 self.join_local(sender_user, room_id, reason, &servers, state_lock, extra_content)
147 .boxed() .await?;
149 },
150 }
151
152 self.copy_predecessor_push_rules(sender_user, room_id)
153 .await;
154
155 Ok(())
156}
157
158#[implement(Service)]
164async fn lock_join(
165 &self,
166 room_id: &RoomId,
167 servers: &[OwnedServerName],
168) -> (Option<RoomMutexGuard>, RoomMutexGuard) {
169 if !self.is_local_join(room_id, servers).await {
170 let (federation_lock, state_lock) = self.lock_join_remote(room_id).await;
171
172 return (Some(federation_lock), state_lock);
173 }
174
175 let state_lock = self.services.state.mutex.lock(room_id).await;
176
177 if self.is_local_join(room_id, servers).await {
178 return (None, state_lock);
179 }
180
181 drop(state_lock);
182 let (federation_lock, state_lock) = self.lock_join_remote(room_id).await;
183
184 (Some(federation_lock), state_lock)
185}
186
187#[implement(Service)]
188async fn is_local_join(&self, room_id: &RoomId, servers: &[OwnedServerName]) -> bool {
189 servers.is_empty()
190 || (servers.len() == 1 && self.services.globals.server_is_ours(&servers[0]))
191 || self
192 .services
193 .state_cache
194 .server_in_room(self.services.globals.server_name(), room_id)
195 .await
196}
197
198#[implement(Service)]
199async fn lock_join_remote(&self, room_id: &RoomId) -> (RoomMutexGuard, RoomMutexGuard) {
200 let federation_lock = self
203 .services
204 .event_handler
205 .mutex_federation
206 .lock(room_id)
207 .await;
208
209 let state_lock = self.services.state.mutex.lock(room_id).await;
210
211 (federation_lock, state_lock)
212}
213
214#[implement(Service)]
215async fn copy_predecessor_push_rules(&self, user_id: &UserId, room_id: &RoomId) {
216 let Ok(create): Result<RoomCreateEventContent> = self
217 .services
218 .state_accessor
219 .room_state_get_content(room_id, &StateEventType::RoomCreate, "")
220 .await
221 else {
222 return;
223 };
224
225 let Some(predecessor) = create.predecessor else {
226 return;
227 };
228
229 self.services
230 .account_data
231 .copy_room_push_rule(user_id, &predecessor.room_id, room_id)
232 .await
233 .ok();
234}
235
236#[implement(Service)]
237#[expect(clippy::too_many_arguments)]
238#[tracing::instrument(
239 name = "remote",
240 level = "debug",
241 skip_all,
242 fields(?servers)
243)]
244async fn join_remote(
245 &self,
246 sender_user: &UserId,
247 room_id: &RoomId,
248 reason: Option<String>,
249 servers: &[OwnedServerName],
250 _federation_lock: RoomMutexGuard,
251 state_lock: RoomMutexGuard,
252 extra_content: Option<CanonicalJsonObject>,
253) -> Result {
254 info!("Joining {room_id} over federation.");
255
256 let (make_join_response, remote_server) = self
257 .make_join_request(sender_user, room_id, servers)
258 .await?;
259
260 info!("make_join finished");
261
262 let room_version_id = self.require_supported_remote_room_version(&make_join_response)?;
263 let room_version_rules = room_version::rules(&room_version_id)?;
264 let (mut join_event, event_id, join_authorized_via_users_server) = self
265 .create_join_event(
266 room_id,
267 sender_user,
268 &make_join_response.event,
269 &room_version_id,
270 &room_version_rules,
271 reason,
272 extra_content,
273 )
274 .await?;
275
276 let mut response = self
277 .execute_send_join(
278 &remote_server,
279 room_id,
280 &event_id,
281 join_event.clone(),
282 &room_version_id,
283 )
284 .await?;
285
286 if response.members_omitted {
287 self.fetch_omitted_state(&remote_server, room_id, &event_id, servers, &mut response)
288 .await?;
289 }
290
291 if join_authorized_via_users_server.is_some() {
292 merge_restricted_signature(
293 &remote_server,
294 &event_id,
295 &room_version_id,
296 &response,
297 &mut join_event,
298 )?;
299 }
300
301 let shortroomid = self
302 .services
303 .short
304 .get_or_create_shortroomid(room_id)
305 .await;
306
307 info!(
308 %room_id,
309 %shortroomid,
310 "Initialized room. Parsing join event..."
311 );
312 let (parsed_join_pdu, join_event) =
313 Pdu::from_object_federation(room_id, &event_id, join_event, &room_version_rules)?;
314
315 info!(
316 events = response
317 .state
318 .len()
319 .expected_add(response.auth_chain.len()),
320 "Acquiring server signing keys for response events..."
321 );
322 self.services
323 .server_keys
324 .acquire_events_pubkeys(
325 response
326 .auth_chain
327 .iter()
328 .chain(response.state.iter()),
329 )
330 .await;
331
332 let state = self
333 .ingest_send_join_state(room_id, &room_version_id, &room_version_rules, &response.state)
334 .await;
335
336 self.ingest_send_join_auth_chain(
337 room_id,
338 &room_version_id,
339 &room_version_rules,
340 &response.auth_chain,
341 )
342 .await;
343
344 debug!("Running send_join auth check...");
345 state_res::auth_check(
346 &room_version_rules,
347 &parsed_join_pdu,
348 &*self.services.timeline,
349 IdMapState { services: &self.services, ids: &state },
350 )
351 .and_then(async |outcome| outcome.into_result())
352 .inspect_err(|e| error!(?e, "send_join auth check failed"))
353 .boxed() .await?;
355
356 self.apply_send_join_state(room_id, &state, &state_lock)
357 .await?;
358
359 let statehash_after_join = self
363 .services
364 .state
365 .append_to_state(&parsed_join_pdu)
366 .await?;
367
368 info!(
369 event_id = %parsed_join_pdu.event_id,
370 "Appending new room join event..."
371 );
372
373 self.services
374 .timeline
375 .append_pdu(
376 &parsed_join_pdu,
377 join_event,
378 once(parsed_join_pdu.event_id.borrow()),
379 &state_lock,
380 )
381 .await?;
382
383 self.services
386 .state
387 .set_room_state(room_id, statehash_after_join, &state_lock);
388
389 info!(
390 statehash = %statehash_after_join,
391 "Set final room state for new room."
392 );
393
394 Ok(())
395}
396
397#[implement(Service)]
398fn require_supported_remote_room_version(
399 &self,
400 make_join_response: &federation::membership::prepare_join_event::v1::Response,
401) -> Result<RoomVersionId> {
402 let Some(room_version_id) = make_join_response.room_version.clone() else {
403 return Err!(BadServerResponse("Remote room version is not supported by tuwunel"));
404 };
405
406 if !self
407 .services
408 .config
409 .supported_room_version(&room_version_id)
410 {
411 return Err!(BadServerResponse(
412 "Remote room version {room_version_id} is not supported by tuwunel"
413 ));
414 }
415
416 Ok(room_version_id)
417}
418
419#[implement(Service)]
420async fn execute_send_join(
421 &self,
422 remote_server: &OwnedServerName,
423 room_id: &RoomId,
424 event_id: &OwnedEventId,
425 join_event: CanonicalJsonObject,
426 room_version_id: &RoomVersionId,
427) -> Result<federation::membership::create_join_event::v2::RoomState> {
428 let send_join_request = federation::membership::create_join_event::v2::Request {
429 room_id: room_id.to_owned(),
430 event_id: event_id.clone(),
431 omit_members: true,
432 pdu: self
433 .services
434 .federation
435 .format_pdu_into(join_event, Some(room_version_id))
436 .await,
437 };
438
439 info!("Asking {remote_server} for fast_join in room {room_id}");
440 let response = self
441 .services
442 .federation
443 .execute(remote_server, send_join_request)
444 .await
445 .inspect_err(|e| error!("send_join failed: {e}"))?
446 .room_state;
447
448 info!(
449 fast_join = response.members_omitted,
450 auth_chain = response.auth_chain.len(),
451 state = response.state.len(),
452 servers = response
453 .servers_in_room
454 .as_ref()
455 .map(Vec::len)
456 .unwrap_or(0),
457 "send_join finished"
458 );
459
460 Ok(response)
461}
462
463#[implement(Service)]
464async fn fetch_omitted_state(
465 &self,
466 remote_server: &OwnedServerName,
467 room_id: &RoomId,
468 event_id: &OwnedEventId,
469 servers: &[OwnedServerName],
470 response: &mut federation::membership::create_join_event::v2::RoomState,
471) -> Result {
472 use federation::event::get_room_state::v1::{Request, Response};
473
474 let eligible =
475 self.omitted_state_servers(remote_server, servers, response.servers_in_room.as_deref());
476
477 let candidates = self
478 .services
479 .federation
480 .rank_candidates(eligible, WhenAllBackedOff::Attempt)
481 .await;
482
483 let mut last_error = Err!(BadServerResponse("No server provided omitted send_join state."));
484 for server in candidates {
485 info!("Asking {server} for state in room {room_id}");
486 let result = self
487 .services
488 .federation
489 .execute(&server, Request {
490 room_id: room_id.to_owned(),
491 event_id: event_id.clone(),
492 })
493 .await;
494
495 match result {
496 | Err(e) => {
497 debug_warn!(?server, "state fetch failed: {e}");
498 last_error = Err(e);
499 },
500 | Ok(Response { mut auth_chain, mut pdus }) => {
501 response.auth_chain = take(&mut auth_chain);
502 response.state = take(&mut pdus);
503
504 info!(
505 auth_chain = response.auth_chain.len(),
506 state = response.state.len(),
507 "state finished"
508 );
509
510 return Ok(());
511 },
512 }
513 }
514
515 last_error
516}
517
518#[implement(Service)]
519fn omitted_state_servers(
520 &self,
521 remote_server: &OwnedServerName,
522 servers: &[OwnedServerName],
523 servers_in_room: Option<&[String]>,
524) -> Candidates {
525 let extracted = servers_in_room
526 .into_iter()
527 .flatten()
528 .filter_map(|server| OwnedServerName::parse(server.as_str()).ok());
529
530 let mut seen = BTreeSet::new();
531 once(remote_server.clone())
532 .chain(extracted)
533 .chain(servers.iter().cloned())
534 .filter(|server| !self.services.globals.server_is_ours(server))
535 .filter(move |server| seen.insert(server.clone()))
536 .take(
537 self.services
538 .config
539 .max_make_join_attempts_per_join_attempt,
540 )
541 .collect()
542}
543
544fn merge_restricted_signature(
545 remote_server: &OwnedServerName,
546 event_id: &OwnedEventId,
547 room_version_id: &RoomVersionId,
548 response: &federation::membership::create_join_event::v2::RoomState,
549 join_event: &mut CanonicalJsonObject,
550) -> Result {
551 let Some(signed_raw) = &response.event else {
552 return Ok(());
553 };
554
555 debug_info!(
556 "There is a signed event with join_authorized_via_users_server. This room is probably \
557 using restricted joins. Adding signature to our event"
558 );
559
560 let (signed_event_id, signed_value) =
561 gen_event_id_canonical_json(signed_raw, room_version_id).map_err(|e| {
562 err!(Request(BadJson(warn!("Could not convert event to canonical JSON: {e}"))))
563 })?;
564
565 if signed_event_id != *event_id {
566 return Err!(Request(BadJson(warn!(
567 %signed_event_id, %event_id,
568 "Server {remote_server} sent event with wrong event ID"
569 ))));
570 }
571
572 let signature = signed_value["signatures"]
573 .as_object()
574 .ok_or_else(|| {
575 err!(BadServerResponse(warn!("Server {remote_server} sent invalid signatures type")))
576 })
577 .and_then(|e| {
578 e.get(remote_server.as_str()).ok_or_else(|| {
579 err!(BadServerResponse(warn!(
580 "Server {remote_server} did not send its signature for a restricted room"
581 )))
582 })
583 });
584
585 match signature {
586 | Ok(signature) => {
587 join_event
588 .get_mut("signatures")
589 .expect("we created a valid pdu")
590 .as_object_mut()
591 .expect("we created a valid pdu")
592 .insert(remote_server.as_str().into(), signature.clone());
593 },
594 | Err(e) => {
595 warn!(
596 "Server {remote_server} sent invalid signature in send_join signatures for \
597 event {signed_value:?}: {e:?}",
598 );
599 },
600 }
601
602 Ok(())
603}
604
605#[implement(Service)]
606async fn ingest_send_join_state(
607 &self,
608 room_id: &RoomId,
609 room_version_id: &RoomVersionId,
610 room_version_rules: &RoomVersionRules,
611 state_pdus: &[Box<RawJsonValue>],
612) -> HashMap<u64, OwnedEventId> {
613 info!(events = state_pdus.len(), "Going through send_join response room_state...");
614 let cork = self.services.db.cork_and_flush();
615 let state = state_pdus
616 .iter()
617 .stream()
618 .then(|pdu| {
619 self.services
620 .server_keys
621 .validate_and_add_event_id_no_fetch(pdu, room_version_id)
622 })
623 .inspect_err(|e| debug_error!("Invalid send_join state event: {e:?}"))
624 .ready_filter_map(Result::ok)
625 .ready_filter_map(|(event_id, value)| {
626 Pdu::from_object_federation(room_id, &event_id, value, room_version_rules)
627 .inspect_err(|error| {
628 debug_warn!(?event_id, %error, "Invalid PDU in the join response.");
629 })
630 .map(move |(pdu, value)| (event_id, pdu, value))
631 .ok()
632 })
633 .fold(HashMap::new(), async |mut state, (event_id, pdu, value)| {
634 self.services
635 .timeline
636 .add_pdu_outlier(&event_id, &value);
637
638 if let Some(state_key) = &pdu.state_key {
639 let shortstatekey = self
640 .services
641 .short
642 .get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)
643 .await;
644
645 state.insert(shortstatekey, pdu.event_id.clone());
646 }
647
648 state
649 })
650 .await;
651
652 drop(cork);
653 state
654}
655
656#[implement(Service)]
657async fn ingest_send_join_auth_chain(
658 &self,
659 room_id: &RoomId,
660 room_version_id: &RoomVersionId,
661 room_version_rules: &RoomVersionRules,
662 auth_chain: &[Box<RawJsonValue>],
663) {
664 info!(events = auth_chain.len(), "Going through send_join response auth_chain...");
665 let cork = self.services.db.cork_and_flush();
666 auth_chain
667 .iter()
668 .stream()
669 .then(|pdu| {
670 self.services
671 .server_keys
672 .validate_and_add_event_id_no_fetch(pdu, room_version_id)
673 })
674 .inspect_err(|e| debug_error!("Invalid send_join auth_chain event: {e:?}"))
675 .ready_filter_map(Result::ok)
676 .ready_for_each(|(event_id, mut value)| {
677 if !room_version_rules
678 .event_format
679 .require_room_create_room_id
680 && value["type"] == "m.room.create"
681 {
682 let room_id = CanonicalJsonValue::String(room_id.as_str().into());
683 value.insert("room_id".into(), room_id);
684 }
685
686 self.services
687 .timeline
688 .add_pdu_outlier(&event_id, &value);
689 })
690 .await;
691
692 drop(cork);
693}
694
695#[implement(Service)]
696async fn apply_send_join_state(
697 &self,
698 room_id: &RoomId,
699 state: &HashMap<u64, OwnedEventId>,
700 state_lock: &RoomMutexGuard,
701) -> Result {
702 info!(events = state.len(), "Compressing state from send_join...");
703 let compressed: CompressedState = self
704 .services
705 .state_compressor
706 .compress_state_events(state.iter().map(|(ssk, eid)| (ssk, eid.borrow())))
707 .collect()
708 .await;
709
710 debug!("Saving compressed state...");
711 let HashSetCompressStateEvent {
712 shortstatehash: statehash_before_join,
713 added,
714 removed,
715 } = self
716 .services
717 .state_compressor
718 .save_state(room_id, Arc::new(compressed))
719 .await?;
720
721 debug!(
722 state_hash = ?statehash_before_join,
723 "Forcing state for new room..."
724 );
725 self.services
726 .state
727 .force_state(room_id, statehash_before_join, added, removed, state_lock)
728 .await?;
729
730 self.services
731 .state_cache
732 .update_joined_count(room_id)
733 .await;
734
735 Ok(())
736}
737
738#[implement(Service)]
739#[tracing::instrument(name = "local", level = "debug", skip_all)]
740async fn join_local(
741 &self,
742 sender_user: &UserId,
743 room_id: &RoomId,
744 reason: Option<String>,
745 servers: &[OwnedServerName],
746 state_lock: RoomMutexGuard,
747 extra_content: Option<CanonicalJsonObject>,
748) -> Result {
749 debug_info!("We can join locally");
750
751 let join_rules_event_content = self
752 .services
753 .state_accessor
754 .room_state_get_content::<RoomJoinRulesEventContent>(
755 room_id,
756 &StateEventType::RoomJoinRules,
757 "",
758 )
759 .await;
760
761 let restriction_rooms = match join_rules_event_content {
762 | Ok(RoomJoinRulesEventContent {
763 join_rule: JoinRule::Restricted(restricted) | JoinRule::KnockRestricted(restricted),
764 }) => restricted
765 .allow
766 .into_iter()
767 .filter_map(|a| match a {
768 | AllowRule::RoomMembership(r) => Some(r.room_id),
769 | _ => None,
770 })
771 .collect(),
772 | _ => Vec::new(),
773 };
774
775 let is_joined_restricted_rooms = self
776 .services
777 .state_cache
778 .is_joined_any(sender_user, restriction_rooms.iter().map(AsRef::as_ref))
779 .await;
780
781 let join_authorized_via_users_server = is_joined_restricted_rooms
782 .then_async(async || {
783 self.services
784 .state_cache
785 .local_users_in_room(room_id)
786 .filter(|user| {
787 self.services.state_accessor.user_can_invite(
788 room_id,
789 user,
790 sender_user,
791 &state_lock,
792 )
793 })
794 .map(ToOwned::to_owned)
795 .boxed() .next()
797 .await
798 })
799 .map(Option::flatten)
800 .await;
801
802 let content = self
803 .services
804 .profile
805 .fill_content(sender_user, RoomMemberEventContent {
806 reason: reason.clone(),
807 join_authorized_via_users_server,
808 ..RoomMemberEventContent::new(MembershipState::Join)
809 })
810 .await;
811
812 let content = merge_member_content(content, extra_content.as_ref())?;
813
814 let pdu_builder = PduBuilder {
815 event_type: StateEventType::RoomMember.into(),
816 content: to_raw_value(&content).map(Into::into)?,
817 state_key: Some(sender_user.to_string().into()),
818 ..Default::default()
819 };
820
821 let Err(error) = self
823 .services
824 .timeline
825 .build_and_append_pdu(pdu_builder, sender_user, room_id, &state_lock)
826 .await
827 else {
828 return Ok(());
829 };
830
831 if restriction_rooms.is_empty()
832 && (servers.is_empty()
833 || servers.len() == 1 && self.services.globals.server_is_ours(&servers[0]))
834 {
835 return Err(error);
836 }
837
838 warn!(
839 "We couldn't do the join locally, maybe federation can help to satisfy the restricted \
840 join requirements"
841 );
842
843 drop(state_lock);
846
847 let Ok((make_join_response, remote_server)) = self
848 .make_join_request(sender_user, room_id, servers)
849 .await
850 else {
851 return Err(error);
852 };
853
854 let room_version_id = self.require_supported_remote_room_version(&make_join_response)?;
855 let room_version_rules = room_version::rules(&room_version_id)?;
856 let (join_event, event_id, _) = self
857 .create_join_event(
858 room_id,
859 sender_user,
860 &make_join_response.event,
861 &room_version_id,
862 &room_version_rules,
863 reason,
864 extra_content,
865 )
866 .await?;
867
868 let send_join_response = self
869 .execute_send_join(&remote_server, room_id, &event_id, join_event, &room_version_id)
870 .await?;
871
872 let Some(signed_raw) = send_join_response.event else {
873 return Err(error);
874 };
875
876 let (signed_event_id, signed_value) =
877 gen_event_id_canonical_json(&signed_raw, &room_version_id).map_err(|e| {
878 err!(Request(BadJson(warn!("Could not convert event to canonical JSON: {e}"))))
879 })?;
880
881 if signed_event_id != event_id {
882 return Err!(Request(BadJson(warn!(
883 %signed_event_id, %event_id, "Server {remote_server} sent event with wrong event ID"
884 ))));
885 }
886
887 self.services
888 .event_handler
889 .handle_incoming_pdu(&remote_server, room_id, &signed_event_id, signed_value, true)
890 .await?
891 .ok_or_else(|| {
892 err!(Request(InvalidParam("Signed join was not accepted as a timeline event.")))
893 })?;
894
895 Ok(())
896}
897
898#[implement(Service)]
899#[expect(clippy::too_many_arguments)]
900#[tracing::instrument(name = "make_join", level = "debug", skip_all)]
901async fn create_join_event(
902 &self,
903 room_id: &RoomId,
904 sender_user: &UserId,
905 join_event_stub: &RawJsonValue,
906 room_version_id: &RoomVersionId,
907 room_version_rules: &RoomVersionRules,
908 reason: Option<String>,
909 extra_content: Option<CanonicalJsonObject>,
910) -> Result<(CanonicalJsonObject, OwnedEventId, Option<OwnedUserId>)> {
911 let mut event: CanonicalJsonObject =
912 serde_json::from_str(join_event_stub.get()).map_err(|e| {
913 err!(BadServerResponse("Invalid make_join event json received from server: {e:?}"))
914 })?;
915
916 let join_authorized_via_users_server = room_version_rules
917 .authorization
918 .restricted_join_rule
919 .then(|| event.get("content"))
920 .flatten()
921 .and_then(|s| {
922 s.as_object()?
923 .get("join_authorised_via_users_server")
924 })
925 .and_then(|s| OwnedUserId::try_from(s.as_str().unwrap_or_default()).ok());
926
927 let content = self
928 .services
929 .profile
930 .fill_content(sender_user, RoomMemberEventContent {
931 reason,
932 join_authorized_via_users_server: join_authorized_via_users_server.clone(),
933 ..RoomMemberEventContent::new(MembershipState::Join)
934 })
935 .await;
936
937 let content = merge_member_content(content, extra_content.as_ref())?;
938
939 event.insert("content".into(), content);
940
941 event.insert(
942 "origin".into(),
943 CanonicalJsonValue::String(
944 self.services
945 .globals
946 .server_name()
947 .as_str()
948 .to_owned(),
949 ),
950 );
951
952 event.insert(
953 "origin_server_ts".into(),
954 CanonicalJsonValue::Integer(utils::millis_since_unix_epoch().try_into()?),
955 );
956
957 event.insert("room_id".into(), CanonicalJsonValue::String(room_id.as_str().into()));
958
959 event.insert("sender".into(), CanonicalJsonValue::String(sender_user.as_str().into()));
960
961 event.insert("state_key".into(), CanonicalJsonValue::String(sender_user.as_str().into()));
962
963 event.insert("type".into(), CanonicalJsonValue::String("m.room.member".into()));
964
965 let event_id = self
966 .services
967 .server_keys
968 .gen_id_hash_and_sign_event(&mut event, room_version_id)?;
969
970 check_rules(&event, &room_version_rules.event_format)?;
971
972 Ok((event, event_id, join_authorized_via_users_server))
973}
974
975fn merge_member_content(
977 content: RoomMemberEventContent,
978 extra_content: Option<&CanonicalJsonObject>,
979) -> Result<CanonicalJsonValue> {
980 let mut content = to_canonical_value(content)?;
981
982 if let (CanonicalJsonValue::Object(content), Some(extra_content)) =
983 (&mut content, extra_content)
984 {
985 for (key, value) in extra_content {
986 content
987 .entry(key.clone())
988 .or_insert_with(|| value.clone());
989 }
990 }
991
992 Ok(content)
993}
994
995#[implement(Service)]
996#[tracing::instrument(
997 name = "make_join",
998 level = "debug",
999 skip_all,
1000 fields(?servers)
1001)]
1002async fn make_join_request(
1003 &self,
1004 sender_user: &UserId,
1005 room_id: &RoomId,
1006 servers: &[OwnedServerName],
1007) -> Result<(federation::membership::prepare_join_event::v1::Response, OwnedServerName)> {
1008 let mut make_join_response_and_server =
1009 Err!(BadServerResponse("No server available to assist in joining."));
1010
1011 let mut make_join_counter: usize = 0;
1012 let mut incompatible_room_version_count: usize = 0;
1013
1014 for remote_server in servers {
1015 if self
1016 .services
1017 .globals
1018 .server_is_ours(remote_server)
1019 {
1020 continue;
1021 }
1022 info!("Asking {remote_server} for make_join ({make_join_counter})");
1023 let make_join_response = self
1024 .services
1025 .federation
1026 .execute(remote_server, federation::membership::prepare_join_event::v1::Request {
1027 room_id: room_id.to_owned(),
1028 user_id: sender_user.to_owned(),
1029 ver: self
1030 .services
1031 .config
1032 .supported_room_versions()
1033 .map(at!(0))
1034 .collect(),
1035 })
1036 .await;
1037
1038 trace!("make_join response: {make_join_response:?}");
1039 make_join_counter = make_join_counter.saturating_add(1);
1040
1041 if let Err(ref e) = make_join_response {
1042 if matches!(
1043 e.kind(),
1044 ErrorKind::IncompatibleRoomVersion { .. } | ErrorKind::UnsupportedRoomVersion
1045 ) {
1046 incompatible_room_version_count =
1047 incompatible_room_version_count.saturating_add(1);
1048 }
1049
1050 if incompatible_room_version_count > 15 {
1051 info!(
1052 "15 servers have responded with M_INCOMPATIBLE_ROOM_VERSION or \
1053 M_UNSUPPORTED_ROOM_VERSION, assuming that tuwunel does not support the \
1054 room version {room_id}: {e}"
1055 );
1056
1057 make_join_response_and_server =
1058 Err!(BadServerResponse("Room version is not supported by tuwunel"));
1059
1060 return make_join_response_and_server;
1061 }
1062
1063 let max_attempts = self
1064 .services
1065 .config
1066 .max_make_join_attempts_per_join_attempt;
1067
1068 if make_join_counter >= max_attempts {
1069 warn!(?remote_server, "last make_join failure reason: {e}");
1070 warn!(
1071 "{max_attempts} servers failed to provide valid make_join response, \
1072 assuming no server can assist in joining."
1073 );
1074
1075 make_join_response_and_server =
1076 Err!(BadServerResponse("No server available to assist in joining."));
1077
1078 return make_join_response_and_server;
1079 }
1080 }
1081
1082 make_join_response_and_server = make_join_response.map(|r| (r, remote_server.clone()));
1083
1084 if make_join_response_and_server.is_ok() {
1085 break;
1086 }
1087 }
1088
1089 make_join_response_and_server
1090}
1091
1092pub(super) async fn get_servers_for_room(
1093 services: &Services,
1094 user_id: &UserId,
1095 room_id: &RoomId,
1096 orig_room_id: Option<&RoomOrAliasId>,
1097 via: &[OwnedServerName],
1098) -> Result<Vec<OwnedServerName>> {
1099 let additional_servers = services
1101 .state_cache
1102 .servers_invite_via(room_id)
1103 .map(ToOwned::to_owned)
1104 .collect::<Vec<_>>();
1105
1106 let invite_state = services
1107 .state_cache
1108 .invite_state(user_id, room_id)
1109 .map(Result::unwrap_or_default);
1110
1111 let (additional_servers, invite_state) = join(additional_servers, invite_state).await;
1112
1113 let mut servers = Vec::from(via);
1114 shuffle(&mut servers);
1115
1116 let has_remote_via = via
1119 .iter()
1120 .any(|s| !services.globals.server_is_ours(s));
1121
1122 if !has_remote_via {
1123 if let Some(server_name) = room_id.server_name() {
1124 servers.insert(0, server_name.to_owned());
1125 }
1126
1127 if let Some(orig_room_id) = orig_room_id
1128 && let Some(orig_server_name) = orig_room_id.server_name()
1129 {
1130 servers.insert(0, orig_server_name.to_owned());
1131 }
1132 }
1133
1134 let extend_and_shuffle = |mut servers: Vec<OwnedServerName>| {
1135 servers.extend(
1136 invite_state
1137 .iter()
1138 .filter_map(|event| event.get_field("sender").ok().flatten())
1139 .filter_map(|sender: &str| UserId::parse(sender).ok())
1140 .map(|user| user.server_name().to_owned()),
1141 );
1142
1143 shuffle(&mut servers);
1144 servers
1145 };
1146
1147 let additional_servers = extend_and_shuffle(additional_servers);
1148
1149 servers.extend_from_slice(&additional_servers);
1150
1151 debug!(?servers);
1156
1157 let mut set = HashSet::new();
1159 servers.retain(|x| set.insert(x.clone()));
1160 debug!(?servers);
1161
1162 if !servers.is_empty() {
1164 for i in 0..servers.len() {
1165 if services
1166 .server
1167 .config
1168 .deprioritize_joins_through_servers
1169 .is_match(servers[i].host())
1170 {
1171 let server = servers.remove(i);
1172 servers.push(server);
1173 }
1174 }
1175 }
1176
1177 debug_info!(?servers);
1178 Ok(servers)
1179}