diff --git a/src/api/client_server/account.rs b/src/api/client_server/account.rs index 9b6bb5f8..89e88b77 100644 --- a/src/api/client_server/account.rs +++ b/src/api/client_server/account.rs @@ -249,11 +249,15 @@ pub async fn register_route(body: Ruma) -> Result { if pwd_set { warn!("The Conduit account emergency password is set! Please unset it as soon as you finish admin account recovery!"); - services().admin.send_message(RoomMessageEventContent::text_plain("The Conduit account emergency password is set! Please unset it as soon as you finish admin account recovery!")); + if let Err(err) = services().admin.send_message( + &RoomMessageEventContent::text_plain("The Conduit account emergency password is set! Please unset it as soon as you finish admin account recovery!") + ).await { + tracing::error!("Failed to send a message to admin room: {err}"); + }; } } Err(e) => { @@ -1043,12 +1047,14 @@ impl KeyValueDatabase { last_update_id = last_update_id.max(update.id); if update.id > services().globals.last_check_for_updates_id()? { println!("{}", update.message); - services() + if let Err(err) = services() .admin - .send_message(RoomMessageEventContent::text_plain(format!( + .send_message(&RoomMessageEventContent::text_plain(format!( "@room: The following is a message from the Conduit developers. It was sent on '{}':\n\n{}", update.date, update.message - ))) + ))).await { + tracing::error!("Failed to send a message to admin room: {err}"); + } } } services() diff --git a/src/service/admin/mod.rs b/src/service/admin/mod.rs index 484fc134..624809d5 100644 --- a/src/service/admin/mod.rs +++ b/src/service/admin/mod.rs @@ -20,6 +20,7 @@ use ruma::{ message::RoomMessageEventContent, name::RoomNameEventContent, power_levels::RoomPowerLevelsEventContent, + redaction::RoomRedactionEventContent, topic::RoomTopicEventContent, }, TimelineEventType, @@ -182,8 +183,7 @@ enum AdminCommand { #[derive(Debug)] pub enum AdminRoomEvent { - ProcessMessage(String), - SendMessage(RoomMessageEventContent), + ProcessMessage(String, ruma::OwnedEventId), } pub struct Service { @@ -220,58 +220,150 @@ impl Service { tokio::select! { Some(event) = receiver.recv() => { let message_content = match event { - AdminRoomEvent::SendMessage(content) => content, - AdminRoomEvent::ProcessMessage(room_message) => self.process_admin_message(room_message).await + AdminRoomEvent::ProcessMessage(room_message, event_id) => self.process_admin_message(room_message, &event_id).await }; + if let Some(message_content) = message_content { + let mutex_state = Arc::clone( + services().globals + .roomid_mutex_state + .write() + .await + .entry(conduit_room.to_owned()) + .or_default(), + ); - let mutex_state = Arc::clone( - services().globals - .roomid_mutex_state - .write() - .await - .entry(conduit_room.to_owned()) - .or_default(), - ); + let state_lock = mutex_state.lock().await; - let state_lock = mutex_state.lock().await; - - services() - .rooms - .timeline - .build_and_append_pdu( - PduBuilder { - event_type: TimelineEventType::RoomMessage, - content: to_raw_value(&message_content) - .expect("event is valid, we just created it"), - unsigned: None, - state_key: None, - redacts: None, - }, - &conduit_user, - &conduit_room, - &state_lock, - ) - .await.unwrap(); + services() + .rooms + .timeline + .build_and_append_pdu( + PduBuilder { + event_type: TimelineEventType::RoomMessage, + content: to_raw_value(&message_content) + .expect("event is valid, we just created it"), + unsigned: None, + state_key: None, + redacts: None, + }, + &conduit_user, + &conduit_room, + &state_lock, + ) + .await.unwrap(); + } } } } } } - pub fn process_message(&self, room_message: String) { + pub fn process_message(&self, room_message: String, event_id: &EventId) { self.sender - .send(AdminRoomEvent::ProcessMessage(room_message)) + .send(AdminRoomEvent::ProcessMessage( + room_message, + event_id.into(), + )) .unwrap(); } - pub fn send_message(&self, message_content: RoomMessageEventContent) { - self.sender - .send(AdminRoomEvent::SendMessage(message_content)) - .unwrap(); + /// Delete user message in the conduit admin room. + pub async fn delete_user_message( + &self, + event_id: &EventId, + reason: Option>, + ) -> Result<()> { + let conduit_user = + UserId::parse_with_server_name("conduit", services().globals.server_name()) + .expect("@conduit:server_name is valid"); + if let Some(room_id) = services().admin.get_admin_room()? { + let mutex_state = Arc::clone( + services() + .globals + .roomid_mutex_state + .write() + .await + .entry(room_id.clone()) + .or_default(), + ); + let state_lock = mutex_state.lock().await; + + services() + .rooms + .timeline + .build_and_append_pdu( + PduBuilder { + event_type: TimelineEventType::RoomRedaction, + content: to_raw_value(&RoomRedactionEventContent { + redacts: Some(event_id.into()), + reason: reason.map(Into::into), + }) + .expect("event is valid, we just created it"), + unsigned: None, + state_key: None, + redacts: Some(event_id.into()), + }, + &conduit_user, + &room_id, + &state_lock, + ) + .await?; + } + Ok(()) } - // Parse and process a message from the admin room - async fn process_admin_message(&self, room_message: String) -> RoomMessageEventContent { + /// Send the message content and return it's result + /// + /// Note: Will return Ok(None) if there is no admin room + pub async fn send_message( + &self, + message_content: &RoomMessageEventContent, + ) -> Result>> { + let conduit_user = + UserId::parse_with_server_name("conduit", services().globals.server_name()) + .expect("@conduit:server_name is valid"); + if let Some(room_id) = services().admin.get_admin_room()? { + let mutex_state = Arc::clone( + services() + .globals + .roomid_mutex_state + .write() + .await + .entry(room_id.clone()) + .or_default(), + ); + let state_lock = mutex_state.lock().await; + services() + .rooms + .timeline + .build_and_append_pdu( + PduBuilder { + event_type: TimelineEventType::RoomMessage, + content: to_raw_value(message_content) + .expect("event is valid, we just created it"), + unsigned: None, + state_key: None, + redacts: None, + }, + &conduit_user, + &room_id, + &state_lock, + ) + .await + .map(Some) + } else { + Ok(None) + } + } + + /// Parse and process a message from the admin room + /// + /// May return `Option::None` if there is no process case for the message + async fn process_admin_message( + &self, + room_message: String, + event_id: &EventId, + ) -> Option { let mut lines = room_message.lines().filter(|l| !l.trim().is_empty()); let command_line = lines.next().expect("each string has at least one line"); let body: Vec<_> = lines.collect(); @@ -283,12 +375,16 @@ impl Service { let message = error.replace("server.name", server_name.as_str()); let html_message = self.usage_to_html(&message, server_name); - return RoomMessageEventContent::text_html(message, html_message); + return Some(RoomMessageEventContent::text_html(message, html_message)); } }; - match self.process_admin_command(admin_command, body).await { - Ok(reply_message) => reply_message, + match self + .process_admin_command(admin_command, body, event_id) + .await + { + Ok(Some(reply_message)) => Some(reply_message), + Ok(None) => None, Err(error) => { let markdown_message = format!( "Encountered an error while handling the command:\n\ @@ -299,7 +395,10 @@ impl Service {
\n{error}\n
", ); - RoomMessageEventContent::text_html(markdown_message, html_message) + Some(RoomMessageEventContent::text_html( + markdown_message, + html_message, + )) } } } @@ -326,18 +425,22 @@ impl Service { AdminCommand::try_parse_from(argv).map_err(|error| error.to_string()) } + /// Process the entered admin command + /// + /// May return `Ok(Option::None)` if there is no process case for the message async fn process_admin_command( &self, command: AdminCommand, body: Vec<&str>, - ) -> Result { + event_id: &EventId, + ) -> Result> { let reply_message_content = match command { AdminCommand::RegisterAppservice => { if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```" { let appservice_config = body[1..body.len() - 1].join("\n"); let parsed_config = serde_yaml::from_str::(&appservice_config); - match parsed_config { + Some(match parsed_config { Ok(yaml) => match services().appservice.register_appservice(yaml).await { Ok(id) => RoomMessageEventContent::text_plain(format!( "Appservice registered with ID: {id}." @@ -349,25 +452,27 @@ impl Service { Err(e) => RoomMessageEventContent::text_plain(format!( "Could not parse appservice config: {e}" )), - } + }) } else { - RoomMessageEventContent::text_plain( + Some(RoomMessageEventContent::text_plain( "Expected code block in command body. Add --help for details.", - ) + )) } } AdminCommand::UnregisterAppservice { appservice_identifier, - } => match services() - .appservice - .unregister_appservice(&appservice_identifier) - .await - { - Ok(()) => RoomMessageEventContent::text_plain("Appservice unregistered."), - Err(e) => RoomMessageEventContent::text_plain(format!( - "Failed to unregister appservice: {e}" - )), - }, + } => Some( + match services() + .appservice + .unregister_appservice(&appservice_identifier) + .await + { + Ok(()) => RoomMessageEventContent::text_plain("Appservice unregistered."), + Err(e) => RoomMessageEventContent::text_plain(format!( + "Failed to unregister appservice: {e}" + )), + }, + ), AdminCommand::ListAppservices => { let appservices = services().appservice.iter_ids().await; let output = format!( @@ -375,7 +480,7 @@ impl Service { appservices.len(), appservices.join(", ") ); - RoomMessageEventContent::text_plain(output) + Some(RoomMessageEventContent::text_plain(output)) } AdminCommand::ListRooms => { let room_ids = services().rooms.metadata.iter_ids(); @@ -396,16 +501,16 @@ impl Service { .collect::>() .join("\n") ); - RoomMessageEventContent::text_plain(output) + Some(RoomMessageEventContent::text_plain(output)) } - AdminCommand::ListLocalUsers => match services().users.list_local_users() { + AdminCommand::ListLocalUsers => Some(match services().users.list_local_users() { Ok(users) => { let mut msg: String = format!("Found {} local user account(s):\n", users.len()); msg += &users.join("\n"); RoomMessageEventContent::text_plain(&msg) } Err(e) => RoomMessageEventContent::text_plain(e.to_string()), - }, + }), AdminCommand::IncomingFederation => { let map = services().globals.roomid_federationhandletime.read().await; let mut msg: String = format!("Handling {} incoming pdus:\n", map.len()); @@ -420,7 +525,7 @@ impl Service { elapsed.as_secs() % 60 ); } - RoomMessageEventContent::text_plain(&msg) + Some(RoomMessageEventContent::text_plain(&msg)) } AdminCommand::GetAuthChain { event_id } => { let event_id = Arc::::from(event_id); @@ -441,18 +546,18 @@ impl Service { .await? .count(); let elapsed = start.elapsed(); - RoomMessageEventContent::text_plain(format!( + Some(RoomMessageEventContent::text_plain(format!( "Loaded auth chain with length {count} in {elapsed:?}" - )) + ))) } else { - RoomMessageEventContent::text_plain("Event not found.") + Some(RoomMessageEventContent::text_plain("Event not found.")) } } AdminCommand::ParsePdu => { if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```" { let string = body[1..body.len() - 1].join("\n"); - match serde_json::from_str(&string) { + Some(match serde_json::from_str(&string) { Ok(value) => { match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) { Ok(hash) => { @@ -477,9 +582,11 @@ impl Service { Err(e) => RoomMessageEventContent::text_plain(format!( "Invalid json in command body: {e}" )), - } + }) } else { - RoomMessageEventContent::text_plain("Expected code block in command body.") + Some(RoomMessageEventContent::text_plain( + "Expected code block in command body.", + )) } } AdminCommand::GetPdu { event_id } => { @@ -496,7 +603,7 @@ impl Service { Some(json) => { let json_text = serde_json::to_string_pretty(&json) .expect("canonical json is valid json"); - RoomMessageEventContent::text_html( + Some(RoomMessageEventContent::text_html( format!( "{}\n```json\n{}\n```", if outlier { @@ -515,32 +622,35 @@ impl Service { }, HtmlEscape(&json_text) ), - ) + )) } - None => RoomMessageEventContent::text_plain("PDU not found."), + None => Some(RoomMessageEventContent::text_plain("PDU not found.")), } } AdminCommand::MemoryUsage => { let response1 = services().memory_usage().await; let response2 = services().globals.db.memory_usage(); - RoomMessageEventContent::text_plain(format!( + Some(RoomMessageEventContent::text_plain(format!( "Services:\n{response1}\n\nDatabase:\n{response2}" - )) + ))) } AdminCommand::ClearDatabaseCaches { amount } => { services().globals.db.clear_caches(amount); - RoomMessageEventContent::text_plain("Done.") + Some(RoomMessageEventContent::text_plain("Done.")) } AdminCommand::ClearServiceCaches { amount } => { services().clear_caches(amount).await; - RoomMessageEventContent::text_plain("Done.") + Some(RoomMessageEventContent::text_plain("Done.")) } AdminCommand::ShowConfig => { // Construct and send the response - RoomMessageEventContent::text_plain(format!("{}", services().globals.config)) + Some(RoomMessageEventContent::text_plain(format!( + "{}", + services().globals.config + ))) } AdminCommand::ResetPassword { username } => { let user_id = match UserId::parse_with_server_name( @@ -549,17 +659,17 @@ impl Service { ) { Ok(id) => id, Err(e) => { - return Ok(RoomMessageEventContent::text_plain(format!( + return Ok(Some(RoomMessageEventContent::text_plain(format!( "The supplied username is not a valid username: {e}" - ))) + )))) } }; // Checks if user is local if user_id.server_name() != services().globals.server_name() { - return Ok(RoomMessageEventContent::text_plain( + return Ok(Some(RoomMessageEventContent::text_plain( "The specified user is not from this server!", - )); + ))); }; // Check if the specified user is valid @@ -571,26 +681,55 @@ impl Service { ) .expect("conduit user exists") { - return Ok(RoomMessageEventContent::text_plain( + return Ok(Some(RoomMessageEventContent::text_plain( "The specified user does not exist!", - )); + ))); } let new_password = utils::random_string(AUTO_GEN_PASSWORD_LENGTH); - match services() + if let Err(err) = services() .users .set_password(&user_id, Some(new_password.as_str())) { - Ok(()) => RoomMessageEventContent::text_plain(format!( - "Successfully reset the password for user {user_id}: {new_password}" - )), - Err(e) => RoomMessageEventContent::text_plain(format!( - "Couldn't reset the password for user {user_id}: {e}" - )), + Some(RoomMessageEventContent::text_plain(format!( + "Couldn't reset the password for user {user_id}: {err}" + ))) + } else { + // Send the reset password message to the user, we + // need it's event id to delete it after 60s + let Some(sended_message_event_id) = services() + .admin + .send_message(&RoomMessageEventContent::text_plain(format!( + "Successfully reset the password for user {user_id}: {new_password} (This message will be deleted after 60s)" + ))) + .await? + else { + return Ok(None); + }; + + // Delete the message after 60s because it's contain a plain password + // and the admin room are not encrypted + tokio::spawn(async move { + tokio::time::sleep(tokio::time::Duration::from_secs(60)).await; + if let Err(err) = services() + .admin + .delete_user_message( + sended_message_event_id.as_ref(), + Some("Message contained a plaintext password"), + ) + .await + { + tracing::warn!( + "Couldn't delete message containing a plaintext password {err}" + ) + } + }); + None } } AdminCommand::CreateUser { username, password } => { + let is_auto_generated_password = password.is_none(); let password = password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH)); // Validate user id @@ -600,20 +739,20 @@ impl Service { ) { Ok(id) => id, Err(e) => { - return Ok(RoomMessageEventContent::text_plain(format!( + return Ok(Some(RoomMessageEventContent::text_plain(format!( "The supplied username is not a valid username: {e}" - ))) + )))) } }; if user_id.is_historical() { - return Ok(RoomMessageEventContent::text_plain(format!( + return Ok(Some(RoomMessageEventContent::text_plain(format!( "Userid {user_id} is not allowed due to historical" - ))); + )))); } if services().users.exists(&user_id)? { - return Ok(RoomMessageEventContent::text_plain(format!( + return Ok(Some(RoomMessageEventContent::text_plain(format!( "Userid {user_id} already exists" - ))); + )))); } // Create user services().users.create(&user_id, Some(password.as_str()))?; @@ -631,6 +770,7 @@ impl Service { .set_displayname(&user_id, Some(displayname))?; // Initial account data + // we dont add a device since we're not the user, just the creator services().account_data.update( None, &user_id, @@ -645,20 +785,57 @@ impl Service { .expect("to json value always works"), )?; - // we dont add a device since we're not the user, just the creator + // We'll delete the user message because it's contain a plain password + // and the admin room are not encrypted + if !is_auto_generated_password { + services() + .admin + .delete_user_message( + event_id, + Some("Message contained a plaintext password"), + ) + .await?; + } - // Inhibit login does not work for guests - RoomMessageEventContent::text_plain(format!( - "Created user with user_id: {user_id} and password: {password}" - )) + // Send the created user message to the user, we + // need it's event id to delete it after 60s + let Some(sended_message_event_id) = services() + .admin + .send_message(&RoomMessageEventContent::text_plain(format!( + "Created user with user_id: {user_id} and password: {password} (This message will be deleted after 60s)" + ))) + .await? + else { + return Ok(None); + }; + + // Delete the message after 60s because it's contain a plain password + // and the admin room are not encrypted + tokio::spawn(async move { + tokio::time::sleep(tokio::time::Duration::from_secs(60)).await; + if let Err(err) = services() + .admin + .delete_user_message( + sended_message_event_id.as_ref(), + Some("Message contained a plaintext password"), + ) + .await + { + tracing::warn!( + "Couldn't delete message containing a plaintext password {err}" + ) + } + }); + + None } AdminCommand::DisableRoom { room_id } => { services().rooms.metadata.disable_room(&room_id, true)?; - RoomMessageEventContent::text_plain("Room disabled.") + Some(RoomMessageEventContent::text_plain("Room disabled.")) } AdminCommand::EnableRoom { room_id } => { services().rooms.metadata.disable_room(&room_id, false)?; - RoomMessageEventContent::text_plain("Room enabled.") + Some(RoomMessageEventContent::text_plain("Room enabled.")) } AdminCommand::DeactivateUser { leave_rooms, @@ -666,14 +843,15 @@ impl Service { } => { let user_id = Arc::::from(user_id); if !services().users.exists(&user_id)? { - RoomMessageEventContent::text_plain(format!( + Some(RoomMessageEventContent::text_plain(format!( "User {user_id} doesn't exist on this server" - )) + ))) } else if user_id.server_name() != services().globals.server_name() { - RoomMessageEventContent::text_plain(format!( + Some(RoomMessageEventContent::text_plain(format!( "User {user_id} is not from this server" - )) + ))) } else { + // FIXME: Why this is here! RoomMessageEventContent::text_plain(format!( "Making {user_id} leave all rooms before deactivation..." )); @@ -684,9 +862,9 @@ impl Service { leave_all_rooms(&user_id).await?; } - RoomMessageEventContent::text_plain(format!( + Some(RoomMessageEventContent::text_plain(format!( "User {user_id} has been deactivated" - )) + ))) } } AdminCommand::DeactivateAll { leave_rooms, force } => { @@ -751,10 +929,10 @@ impl Service { html_message.push_str("\n\n"); } if !markdown_message.is_empty() { - return Ok(RoomMessageEventContent::text_html( + return Ok(Some(RoomMessageEventContent::text_html( markdown_message, html_message, - )); + ))); } let mut deactivation_count = 0; @@ -786,16 +964,16 @@ impl Service { } if admins.is_empty() { - RoomMessageEventContent::text_plain(format!( + Some(RoomMessageEventContent::text_plain(format!( "Deactivated {deactivation_count} accounts." - )) + ))) } else { - RoomMessageEventContent::text_plain(format!("Deactivated {} accounts.\nSkipped admin accounts: {:?}. Use --force to deactivate admin accounts", deactivation_count, admins.join(", "))) + Some(RoomMessageEventContent::text_plain(format!("Deactivated {} accounts.\nSkipped admin accounts: {:?}. Use --force to deactivate admin accounts", deactivation_count, admins.join(", ")))) } } else { - RoomMessageEventContent::text_plain( + Some(RoomMessageEventContent::text_plain( "Expected code block in command body. Add --help for details.", - ) + )) } } AdminCommand::SignJson => { @@ -812,14 +990,16 @@ impl Service { .expect("our request json is what ruma expects"); let json_text = serde_json::to_string_pretty(&value) .expect("canonical json is valid json"); - RoomMessageEventContent::text_plain(json_text) + Some(RoomMessageEventContent::text_plain(json_text)) } - Err(e) => RoomMessageEventContent::text_plain(format!("Invalid json: {e}")), + Err(e) => Some(RoomMessageEventContent::text_plain(format!( + "Invalid json: {e}" + ))), } } else { - RoomMessageEventContent::text_plain( + Some(RoomMessageEventContent::text_plain( "Expected code block in command body. Add --help for details.", - ) + )) } } AdminCommand::VerifyJson => { @@ -838,18 +1018,22 @@ impl Service { let pub_key_map = pub_key_map.read().await; match ruma::signatures::verify_json(&pub_key_map, &value) { - Ok(_) => RoomMessageEventContent::text_plain("Signature correct"), - Err(e) => RoomMessageEventContent::text_plain(format!( + Ok(_) => { + Some(RoomMessageEventContent::text_plain("Signature correct")) + } + Err(e) => Some(RoomMessageEventContent::text_plain(format!( "Signature verification failed: {e}" - )), + ))), } } - Err(e) => RoomMessageEventContent::text_plain(format!("Invalid json: {e}")), + Err(e) => Some(RoomMessageEventContent::text_plain(format!( + "Invalid json: {e}" + ))), } } else { - RoomMessageEventContent::text_plain( + Some(RoomMessageEventContent::text_plain( "Expected code block in command body. Add --help for details.", - ) + )) } } }; diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index acb00d01..5b309a0a 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -499,7 +499,7 @@ impl Service { if let Some(admin_room) = services().admin.get_admin_room()? { if to_conduit && !from_conduit && admin_room == pdu.room_id { - services().admin.process_message(body); + services().admin.process_message(body, &pdu.event_id); } } }