Skip to main content

tuwunel_service/appservice/
mod.rs

1mod append;
2mod namespace_regex;
3mod ping;
4mod registration_info;
5pub(crate) mod request;
6mod thirdparty;
7
8use std::{
9	collections::BTreeMap,
10	ffi::OsStr,
11	fs::{self, read_dir},
12	sync::Arc,
13};
14
15use async_trait::async_trait;
16use futures::{FutureExt, Stream, TryStreamExt};
17use ruma::{RoomAliasId, RoomId, UserId, api::appservice::Registration};
18use tokio::sync::{RwLock, RwLockReadGuard, SetOnce};
19use tuwunel_core::{Err, Result, defer, err, utils::stream::IterStream};
20use tuwunel_database::Map;
21
22pub use self::{namespace_regex::NamespaceRegex, registration_info::RegistrationInfo};
23
24pub struct Service {
25	registration_info: RwLock<Registrations>,
26	loaded: SetOnce<()>,
27	services: Arc<crate::services::OnceServices>,
28	db: Data,
29}
30
31struct Data {
32	id_appserviceregistrations: Arc<Map>,
33}
34
35type Registrations = BTreeMap<String, RegistrationInfo>;
36
37#[async_trait]
38impl crate::Service for Service {
39	fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
40		Ok(Arc::new(Self {
41			registration_info: RwLock::new(BTreeMap::new()),
42			loaded: SetOnce::new(),
43			services: args.services.clone(),
44			db: Data {
45				id_appserviceregistrations: args.db["id_appserviceregistrations"].clone(),
46			},
47		}))
48	}
49
50	async fn worker(self: Arc<Self>) -> Result {
51		defer! {{
52			self.loaded.set(()).ok();
53		}}
54
55		self.load().await
56	}
57
58	fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
59}
60
61impl Service {
62	/// Loads every registration source into the runtime registry.
63	///
64	/// The configured `appservice` table is read first, then any YAML under
65	/// `appservice_dir`, then the registrations persisted by the admin command.
66	async fn load(&self) -> Result {
67		for (id, mut appservice) in self.services.config.appservice.clone() {
68			if appservice.id.is_empty() {
69				appservice.id = id.clone();
70			}
71
72			if *id != appservice.id {
73				return Err!(Config(
74					"id",
75					"Registration ID {:?} does not match the configured {id:?}",
76					appservice.id
77				));
78			}
79
80			self.load_appservice(appservice.into()).await?;
81		}
82
83		if let Some(appservice_dir) = &self.services.config.appservice_dir {
84			let entries = read_dir(appservice_dir).map_err(|e| {
85				err!(Config("appservice_dir", "Failed to read {appservice_dir:?}: {e}"))
86			})?;
87
88			for dir_entry in entries {
89				let path = dir_entry?.path();
90
91				if !path.is_file()
92					|| !path
93						.extension()
94						.and_then(OsStr::to_str)
95						.is_some_and(|ext| matches!(ext, "yaml" | "yml"))
96				{
97					continue;
98				}
99
100				let bytes = fs::read(path)?;
101				let registration: Registration = serde_yaml::from_slice(&bytes)?;
102
103				self.load_appservice(registration).await?;
104			}
105		}
106
107		self.iter_db_ids()
108			.try_for_each(|registration| self.load_appservice(registration))
109			.await?;
110
111		Ok(())
112	}
113
114	pub async fn load_appservice(&self, registration: Registration) -> Result {
115		//TODO: Check for collisions between exclusive appservice namespaces
116
117		let registration_info =
118			RegistrationInfo::new(registration, self.services.globals.server_name())?;
119
120		let id = &registration_info.registration.id;
121
122		let mut registrations = self.registration_info.write().await;
123
124		for loaded_registration_info in registrations.values() {
125			let loaded_id = &loaded_registration_info.registration.id;
126
127			if loaded_id == id {
128				return Err!("Duplicate id: {id}");
129			}
130
131			if loaded_registration_info.registration.as_token
132				== registration_info.registration.as_token
133			{
134				return Err!("Duplicate as_token: {loaded_id} {id}");
135			}
136		}
137
138		let appservice_user = &registration_info.sender;
139
140		if !self.services.users.exists(appservice_user).await {
141			self.services
142				.users
143				.create(appservice_user, None, None)
144				.await?;
145		}
146
147		registrations.insert(id.clone(), registration_info);
148
149		Ok(())
150	}
151
152	pub async fn register_appservice(&self, registration: Registration) -> Result {
153		self.loaded().await;
154
155		let id = registration.id.clone();
156
157		let appservice_yaml = serde_yaml::to_string(&registration)?;
158
159		self.load_appservice(registration).await?;
160
161		self.db
162			.id_appserviceregistrations
163			.insert(&id, appservice_yaml);
164
165		Ok(())
166	}
167
168	pub async fn unregister_appservice(&self, appservice_id: &str) -> Result {
169		self.loaded().await;
170
171		let mut registrations = self.registration_info.write().await;
172
173		if !registrations.contains_key(appservice_id) {
174			return Err!("Appservice not found");
175		}
176
177		if self
178			.db
179			.id_appserviceregistrations
180			.exists(appservice_id)
181			.await
182			.is_err()
183		{
184			return Err!("Cannot unregister config appservice");
185		}
186
187		// removes the appservice registration info
188		registrations
189			.remove(appservice_id)
190			.ok_or_else(|| err!("Appservice not found"))?;
191
192		// remove the appservice from the database
193		self.db
194			.id_appserviceregistrations
195			.remove(appservice_id);
196
197		// deletes all active requests for the appservice if there are any so we stop
198		// sending to the URL
199		self.services
200			.sending
201			.cleanup_events(Some(appservice_id), None, None)
202			.await
203	}
204
205	pub async fn get_registration(&self, id: &str) -> Option<Registration> {
206		self.registration_info
207			.read()
208			.await
209			.get(id)
210			.cloned()
211			.map(|info| info.registration)
212	}
213
214	/// Retrieve a registration with its compiled namespaces (`sender`,
215	/// `is_user_match`), which a bare `Registration` lacks.
216	pub async fn get_registration_info(&self, id: &str) -> Option<RegistrationInfo> {
217		self.registration_info
218			.read()
219			.await
220			.get(id)
221			.cloned()
222	}
223
224	pub async fn find_from_access_token(&self, token: &str) -> Result<RegistrationInfo> {
225		self.read()
226			.await
227			.values()
228			.find(|info| info.registration.as_token == token)
229			.cloned()
230			.ok_or_else(|| err!(Request(NotFound("Missing or invalid appservice token"))))
231	}
232
233	/// Checks if a given user id matches any exclusive appservice regex
234	pub async fn is_exclusive_user_id(&self, user_id: &UserId) -> bool {
235		self.read()
236			.await
237			.values()
238			.any(|info| info.is_exclusive_user_match(user_id))
239	}
240
241	/// Checks if a given user id matches any appservice's user namespace.
242	pub async fn is_interested_in_user(&self, user_id: &UserId) -> bool {
243		self.read()
244			.await
245			.values()
246			.any(|info| info.is_user_match(user_id))
247	}
248
249	/// Checks if a given room alias matches any exclusive appservice regex
250	pub async fn is_exclusive_alias(&self, alias: &RoomAliasId) -> bool {
251		self.read()
252			.await
253			.values()
254			.any(|info| info.aliases.is_exclusive_match(alias.as_str()))
255	}
256
257	/// Checks if a given room id matches any exclusive appservice regex
258	///
259	/// TODO: use this?
260	pub async fn is_exclusive_room_id(&self, room_id: &RoomId) -> bool {
261		self.read()
262			.await
263			.values()
264			.any(|info| info.rooms.is_exclusive_match(room_id.as_str()))
265	}
266
267	pub fn iter_ids(&self) -> impl Stream<Item = String> + Send {
268		self.read()
269			.map(|info| info.keys().cloned().collect::<Vec<_>>())
270			.map(IntoIterator::into_iter)
271			.map(IterStream::stream)
272			.flatten_stream()
273	}
274
275	pub fn iter_db_ids(&self) -> impl Stream<Item = Result<Registration>> + Send {
276		self.db
277			.id_appserviceregistrations
278			.keys()
279			.and_then(async move |id: &str| Ok(self.get_db_registration(id).await?))
280	}
281
282	pub async fn get_db_registration(&self, id: &str) -> Result<Registration> {
283		self.db
284			.id_appserviceregistrations
285			.get(id)
286			.await
287			.and_then(|ref bytes| serde_yaml::from_slice(bytes).map_err(Into::into))
288			.map_err(|e| err!(Database("Invalid appservice {id:?} registration: {e:?}")))
289	}
290
291	pub fn read(&self) -> impl Future<Output = RwLockReadGuard<'_, Registrations>> + Send {
292		self.registration_info.read()
293	}
294
295	/// Waits for the boot-time registration load to finish.
296	///
297	/// The latch is released on every exit from the worker, a failed or
298	/// panicking load included, so a waiter is never stranded on a load that
299	/// will not complete.
300	#[inline]
301	pub async fn loaded(&self) { self.loaded.wait().await; }
302}