Compare commits

..

2 Commits

Author SHA1 Message Date
Ginger 29f138ed0f feat: Make a few improvements to the systemd unit
- Use systemd's credential system to supply our config file
- Remove `ConfigurationDirectory` to prevent conflicts with package managers
- Set `config_reload_signal` to true using an envvar
2025-09-24 12:33:32 +00:00
Ginger 5c07d60403 fix: Use mode 600 for config files on Fedora because they contain secret info 2025-09-24 12:33:32 +00:00
10 changed files with 87 additions and 214 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
name: Renovate
runs-on: ubuntu-latest
container:
image: ghcr.io/renovatebot/renovate:41.130.1@sha256:44c4cfceb47d283b5adc654ea445909de4d89e69f94b109e522ca32593a436b5
image: ghcr.io/renovatebot/renovate:41.127.2@sha256:66bc84e2f889025fbb3c9df863500dcc18bc64ac85bcf629d015064377d77f31
options: --tmpfs /tmp:exec
steps:
- name: Checkout
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
submodules: false
persist-credentials: false
- uses: https://github.com/cachix/install-nix-action@9280e7aca88deada44c930f1e2c78e21c3ae3edd # v31.7.0
- uses: https://github.com/cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2
with:
nix_path: nixpkgs=channel:nixos-unstable
+7 -5
View File
@@ -12,13 +12,14 @@ Group=conduwuit
Type=notify-reload
ReloadSignal=SIGUSR1
Environment="CONTINUWUITY_CONFIG=/etc/conduwuit/conduwuit.toml"
Environment="CONTINUWUITY_LOG_TO_JOURNALD=true"
Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N"
Environment="CONTINUWUITY_DATABASE_PATH=/var/lib/conduwuit"
Environment="CONTINUWUITY_DATABASE_PATH=%S/conduwuit"
Environment="CONTINUWUITY_CONFIG_RELOAD_SIGNAL=true"
ExecStart=/usr/bin/conduwuit
LoadCredential=conduwuit.toml:/etc/conduwuit/conduwuit.toml
ExecStart=/usr/bin/conduwuit --config ${CREDENTIALS_DIRECTORY}/conduwuit.toml
AmbientCapabilities=
CapabilityBoundingSet=
@@ -52,8 +53,9 @@ SystemCallFilter=@system-service @resources
SystemCallFilter=~@clock @debug @module @mount @reboot @swap @cpu-emulation @obsolete @timer @chown @setuid @privileged @keyring @ipc
SystemCallErrorNumber=EPERM
# ConfigurationDirectory isn't specified here because it's created by
# the distro's package manager.
StateDirectory=conduwuit
ConfigurationDirectory=conduwuit
RuntimeDirectory=conduwuit
RuntimeDirectoryMode=0750
+2 -2
View File
@@ -51,7 +51,7 @@ find .cargo/registry/ -executable -name "*.rs" -exec chmod -x {} +
%install
install -Dpm0755 target/rpm/conduwuit -t %{buildroot}%{_bindir}
install -Dpm0644 pkg/conduwuit.service -t %{buildroot}%{_unitdir}
install -Dpm0644 conduwuit-example.toml %{buildroot}%{_sysconfdir}/conduwuit/conduwuit.toml
install -Dpm0600 conduwuit-example.toml %{buildroot}%{_sysconfdir}/conduwuit/conduwuit.toml
%files
%license LICENSE
@@ -60,7 +60,7 @@ install -Dpm0644 conduwuit-example.toml %{buildroot}%{_sysconfdir}/conduwuit/con
%doc CONTRIBUTING.md
%doc README.md
%doc SECURITY.md
%config %{_sysconfdir}/conduwuit/conduwuit.toml
%config(noreplace) %{_sysconfdir}/conduwuit/conduwuit.toml
%{_bindir}/conduwuit
%{_unitdir}/conduwuit.service
+38 -113
View File
@@ -2,7 +2,7 @@
use axum::extract::State;
use conduwuit::{
Err, Error, Event, Result, RoomVersion, debug, err, info,
Err, Error, Event, Result, debug, err, info,
matrix::{StateKey, pdu::PduBuilder},
};
use futures::{FutureExt, StreamExt};
@@ -68,76 +68,37 @@ pub(crate) async fn upgrade_room_route(
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
// First, check if the user has permission to upgrade the room (send tombstone
// event)
let old_room_state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
// Check tombstone permission by attempting to create (but not send) the event
// Note that this does internally call the policy server with a fake room ID,
// which may not be good?
let tombstone_test_result = services
.rooms
.timeline
.create_hash_and_sign_event(
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: RoomId::new(services.globals.server_name()),
}),
sender_user,
Some(&body.room_id),
&old_room_state_lock,
)
.await;
if let Err(_e) = tombstone_test_result {
return Err!(Request(Forbidden("User does not have permission to upgrade this room.")));
}
drop(old_room_state_lock);
// Create a replacement room
let room_features = RoomVersion::new(&body.new_version)?;
let replacement_room: Option<&RoomId> = if room_features.room_ids_as_hashes {
None
} else {
Some(&RoomId::new(services.globals.server_name()))
};
let replacement_room_tmp = match replacement_room {
| Some(v) => v,
| None => &RoomId::new(services.globals.server_name()),
};
let replacement_room = RoomId::new(services.globals.server_name());
let _short_id = services
.rooms
.short
.get_or_create_shortroomid(replacement_room_tmp)
.get_or_create_shortroomid(&replacement_room)
.await;
// For pre-v12 rooms, send tombstone before creating replacement room
let tombstone_event_id = if !room_features.room_ids_as_hashes {
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
// Send a m.room.tombstone event to the old room to indicate that it is not
// intended to be used any further
let tombstone_event_id = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: replacement_room.unwrap().to_owned(),
}),
sender_user,
Some(&body.room_id),
&state_lock,
)
.await?;
// Change lock to replacement room
drop(state_lock);
Some(tombstone_event_id)
} else {
None
};
let state_lock = services.rooms.state.mutex.lock(replacement_room_tmp).await;
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
// Send a m.room.tombstone event to the old room to indicate that it is not
// intended to be used any further Fail if the sender does not have the required
// permissions
let tombstone_event_id = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: replacement_room.clone(),
}),
sender_user,
Some(&body.room_id),
&state_lock,
)
.await?;
// Change lock to replacement room
drop(state_lock);
let state_lock = services.rooms.state.mutex.lock(&replacement_room).await;
// Get the old room creation event
let mut create_event_content: CanonicalJsonObject = services
@@ -150,7 +111,7 @@ pub(crate) async fn upgrade_room_route(
// Use the m.room.tombstone event as the predecessor
let predecessor = Some(ruma::events::room::create::PreviousRoom::new(
body.room_id.clone(),
tombstone_event_id,
Some(tombstone_event_id),
));
// Send a m.room.create event containing a predecessor field and the applicable
@@ -171,7 +132,6 @@ pub(crate) async fn upgrade_room_route(
// "creator" key no longer exists in V11 rooms
create_event_content.remove("creator");
},
// TODO(hydra): additional_creators
}
}
@@ -199,7 +159,7 @@ pub(crate) async fn upgrade_room_route(
return Err(Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"));
}
let create_event_id = services
services
.rooms
.timeline
.build_and_append_pdu(
@@ -213,18 +173,11 @@ pub(crate) async fn upgrade_room_route(
timestamp: None,
},
sender_user,
replacement_room,
Some(&replacement_room),
&state_lock,
)
.boxed()
.await?;
let create_id = create_event_id.as_str().replace('$', "!");
let (replacement_room, state_lock) = if room_features.room_ids_as_hashes {
let parsed_room_id = RoomId::parse(&create_id)?;
(Some(parsed_room_id), services.rooms.state.mutex.lock(parsed_room_id).await)
} else {
(replacement_room, state_lock)
};
// Join the new room
services
@@ -251,7 +204,7 @@ pub(crate) async fn upgrade_room_route(
timestamp: None,
},
sender_user,
replacement_room,
Some(&replacement_room),
&state_lock,
)
.boxed()
@@ -290,7 +243,7 @@ pub(crate) async fn upgrade_room_route(
..Default::default()
},
sender_user,
replacement_room,
Some(&replacement_room),
&state_lock,
)
.boxed()
@@ -315,7 +268,7 @@ pub(crate) async fn upgrade_room_route(
services
.rooms
.alias
.set_alias(alias, replacement_room.unwrap(), sender_user)?;
.set_alias(alias, &replacement_room, sender_user)?;
}
// Get the old room power levels
@@ -357,27 +310,6 @@ pub(crate) async fn upgrade_room_route(
drop(state_lock);
// For v12 rooms, send tombstone AFTER creating replacement room
if room_features.room_ids_as_hashes {
let old_room_state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
// For v12 rooms, no event reference in predecessor due to cyclic dependency -
// could best effort one maybe?
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: replacement_room.unwrap().to_owned(),
}),
sender_user,
Some(&body.room_id),
&old_room_state_lock,
)
.await?;
drop(old_room_state_lock);
}
// Check if the old room has a space parent, and if so, whether we should update
// it (m.space.parent, room_id)
let parents = services
@@ -402,9 +334,8 @@ pub(crate) async fn upgrade_room_route(
continue;
};
debug!(
"Updating space {space_id} child event for room {} to {}",
&body.room_id,
replacement_room.unwrap()
"Updating space {space_id} child event for room {} to {replacement_room}",
&body.room_id
);
// First, drop the space's child event
let state_lock = services.rooms.state.mutex.lock(space_id).await;
@@ -428,10 +359,7 @@ pub(crate) async fn upgrade_room_route(
.await
.ok();
// Now, add a new child event for the replacement room
debug!(
"Adding space child event for room {} in space {space_id}",
replacement_room.unwrap()
);
debug!("Adding space child event for room {replacement_room} in space {space_id}");
services
.rooms
.timeline
@@ -444,7 +372,7 @@ pub(crate) async fn upgrade_room_route(
suggested: child.suggested,
})
.expect("event is valid, we just created it"),
state_key: Some(replacement_room.unwrap().as_str().into()),
state_key: Some(replacement_room.as_str().into()),
..Default::default()
},
sender_user,
@@ -455,15 +383,12 @@ pub(crate) async fn upgrade_room_route(
.await
.ok();
debug!(
"Finished updating space {space_id} child event for room {} to {}",
&body.room_id,
replacement_room.unwrap()
"Finished updating space {space_id} child event for room {} to {replacement_room}",
&body.room_id
);
drop(state_lock);
}
// Return the replacement room id
Ok(upgrade_room::v3::Response {
replacement_room: replacement_room.unwrap().to_owned(),
})
Ok(upgrade_room::v3::Response { replacement_room })
}
+3 -14
View File
@@ -36,7 +36,7 @@
room_version::RoomVersion,
};
use crate::{
debug, debug_error, err,
debug, debug_error,
matrix::{Event, StateKey},
state_res::room_version::StateResolutionVersion,
trace,
@@ -319,19 +319,8 @@ async fn calculate_conflicted_subgraph<F, Fut, E>(
path.pop();
continue;
}
trace!(event_id = event_id.as_str(), "fetching event for its auth events");
let evt = fetch_event(event_id.clone()).await;
if evt.is_none() {
err!("could not fetch event {} to calculate conflicted subgraph", event_id);
path.pop();
continue;
}
stack.push(
evt.expect("checked")
.auth_events()
.map(ToOwned::to_owned)
.collect(),
);
let evt = fetch_event(event_id.clone()).await?;
stack.push(evt.auth_events().map(ToOwned::to_owned).collect());
seen.insert(event_id);
}
Some(subgraph)
+1 -2
View File
@@ -9,7 +9,6 @@
},
warn,
};
use database::Json;
use futures::{FutureExt, StreamExt, TryStreamExt};
use itertools::Itertools;
use ruma::{
@@ -607,7 +606,7 @@ async fn fix_corrupt_msc4133_fields(services: &Services) -> Result {
);
};
useridprofilekey_value.put((user, key), Json(new_value));
useridprofilekey_value.put((user, key), new_value);
fixed = fixed.saturating_add(1);
}
total = total.saturating_add(1);
@@ -4,8 +4,9 @@
};
use conduwuit::{
Event, PduEvent, debug, debug_warn, implement, matrix::event::gen_event_id_canonical_json,
trace, utils::continue_exponential_backoff_secs, warn,
Event, PduEvent, debug, debug_error, debug_warn, implement,
matrix::event::gen_event_id_canonical_json, trace, utils::continue_exponential_backoff_secs,
warn,
};
use ruma::{
CanonicalJsonValue, EventId, OwnedEventId, RoomId, ServerName,
@@ -51,14 +52,12 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
};
let mut events_with_auth_events = Vec::with_capacity(events.clone().count());
trace!("Fetching {} outlier pdus", events.clone().count());
for id in events {
// a. Look in the main timeline (pduid_pdu tree)
// b. Look at outlier pdu tree
// (get_pdu_json checks both)
if let Ok(local_pdu) = self.services.timeline.get_pdu(id).await {
trace!("Found {id} in main timeline or outlier tree");
events_with_auth_events.push((id.to_owned(), Some(local_pdu), vec![]));
continue;
}
@@ -105,7 +104,7 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
continue;
}
debug!("Fetching {next_id} over federation from {origin}.");
debug!("Fetching {next_id} over federation.");
match self
.services
.sending
@@ -116,7 +115,7 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
.await
{
| Ok(res) => {
debug!("Got {next_id} over federation from {origin}");
debug!("Got {next_id} over federation");
let Ok(room_version_id) = get_room_version_id(create_event) else {
back_off((*next_id).to_owned());
continue;
@@ -146,9 +145,6 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
auth_event.clone().into(),
) {
| Ok(auth_event) => {
trace!(
"Found auth event id {auth_event} for event {next_id}"
);
todo_auth_events.push_back(auth_event);
},
| _ => {
@@ -164,7 +160,7 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
events_all.insert(next_id);
},
| Err(e) => {
warn!("Failed to fetch auth event {next_id} from {origin}: {e}");
debug_error!("Failed to fetch event {next_id}: {e}");
back_off((*next_id).to_owned());
},
}
@@ -179,7 +175,7 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
// b. Look at outlier pdu tree
// (get_pdu_json checks both)
if let Some(local_pdu) = local_pdu {
trace!("Found {id} in main timeline or outlier tree");
trace!("Found {id} in db");
pdus.push((local_pdu.clone(), None));
}
@@ -205,7 +201,6 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
}
}
trace!("Handling outlier {next_id}");
match Box::pin(self.handle_outlier_pdu(
origin,
create_event,
@@ -218,7 +213,6 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
{
| Ok((pdu, json)) =>
if next_id == *id {
trace!("Handled outlier {next_id} (original request)");
pdus.push((pdu, Some(json)));
},
| Err(e) => {
@@ -228,6 +222,6 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
}
}
}
trace!("Fetched and handled {} outlier pdus", pdus.len());
pdus
}
@@ -1,12 +1,11 @@
use std::collections::{BTreeMap, HashMap, hash_map};
use conduwuit::{
Err, Event, PduEvent, Result, debug, debug_info, debug_warn, err, implement, state_res, trace,
Err, Event, PduEvent, Result, debug, debug_info, err, implement, state_res, trace, warn,
};
use futures::future::ready;
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, RoomId, ServerName,
events::StateEventType,
CanonicalJsonObject, CanonicalJsonValue, EventId, RoomId, ServerName, events::StateEventType,
};
use super::{check_room_id, get_room_version_id, to_room_version};
@@ -75,73 +74,36 @@ pub(super) async fn handle_outlier_pdu<'a, Pdu>(
check_room_id(room_id, &pdu_event)?;
// Fetch all auth events
let mut auth_events: HashMap<OwnedEventId, PduEvent> = HashMap::new();
for aid in pdu_event.auth_events() {
if let Ok(auth_event) = self.services.timeline.get_pdu(aid).await {
check_room_id(room_id, &auth_event)?;
trace!("Found auth event {aid} for outlier event {event_id} locally");
auth_events.insert(aid.to_owned(), auth_event);
} else {
debug_warn!("Could not find auth event {aid} for outlier event {event_id} locally");
}
}
// Fetch any missing ones & reject invalid ones
let missing_auth_events = if auth_events_known {
pdu_event
.auth_events()
.filter(|id| !auth_events.contains_key(*id))
.collect::<Vec<_>>()
} else {
pdu_event.auth_events().collect::<Vec<_>>()
};
if !missing_auth_events.is_empty() || !auth_events_known {
debug_info!(
"Fetching {} missing auth events for outlier event {event_id}",
missing_auth_events.len()
);
for (pdu, _) in self
.fetch_and_handle_outliers(
origin,
missing_auth_events.iter().copied(),
create_event,
room_id,
)
.await
{
auth_events.insert(pdu.event_id().to_owned(), pdu);
}
} else {
debug!("No missing auth events for outlier event {event_id}");
}
// reject if we are still missing some
let still_missing = pdu_event
.auth_events()
.filter(|id| !auth_events.contains_key(*id))
.collect::<Vec<_>>();
if !still_missing.is_empty() {
return Err!(Request(InvalidParam(
"Could not fetch all auth events for outlier event {event_id}, still missing: \
{still_missing:?}"
)));
if !auth_events_known {
// 4. fetch any missing auth events doing all checks listed here starting at 1.
// These are not timeline events
// 5. Reject "due to auth events" if can't get all the auth events or some of
// the auth events are also rejected "due to auth events"
// NOTE: Step 5 is not applied anymore because it failed too often
debug!("Fetching auth events");
Box::pin(self.fetch_and_handle_outliers(
origin,
pdu_event.auth_events(),
create_event,
room_id,
))
.await;
}
// 6. Reject "due to auth events" if the event doesn't pass auth based on the
// auth events
debug!("Checking based on auth events");
let mut auth_events_by_key: HashMap<_, _> = HashMap::with_capacity(auth_events.len());
// Build map of auth events
let mut auth_events = HashMap::with_capacity(pdu_event.auth_events().count());
for id in pdu_event.auth_events() {
let auth_event = auth_events
.get(id)
.expect("we just checked that we have all auth events")
.to_owned();
let Ok(auth_event) = self.services.timeline.get_pdu(id).await else {
warn!("Could not find auth event {id}");
continue;
};
check_room_id(room_id, &auth_event)?;
match auth_events_by_key.entry((
match auth_events.entry((
auth_event.kind.to_string().into(),
auth_event
.state_key
@@ -161,7 +123,7 @@ pub(super) async fn handle_outlier_pdu<'a, Pdu>(
// The original create event must be in the auth events
if !matches!(
auth_events_by_key.get(&(StateEventType::RoomCreate, String::new().into())),
auth_events.get(&(StateEventType::RoomCreate, String::new().into())),
Some(_) | None
) {
return Err!(Request(InvalidParam("Incoming event refers to wrong create event.")));
@@ -169,7 +131,7 @@ pub(super) async fn handle_outlier_pdu<'a, Pdu>(
let state_fetch = |ty: &StateEventType, sk: &str| {
let key = (ty.to_owned(), sk.into());
ready(auth_events_by_key.get(&key).map(ToOwned::to_owned))
ready(auth_events.get(&key).map(ToOwned::to_owned))
};
let auth_check = state_res::event_auth::auth_check(
+2
View File
@@ -274,6 +274,8 @@ fn from_evt(
pdu_json.insert("event_id".into(), CanonicalJsonValue::String(pdu.event_id.clone().into()));
// Check with the policy server
// TODO(hydra): Skip this check for create events (why didnt we do this
// already?)
if room_id.is_some() {
trace!(
"Checking event {} in room {} with policy server",