Merge branch 'command-refactor' into 'next'

Move and refactor admin commands into admin module

See merge request famedly/conduit!253
This commit is contained in:
Timo Kösters 2022-02-03 18:43:54 +00:00
commit 7a388f4d72
6 changed files with 491 additions and 308 deletions

View file

@ -18,7 +18,7 @@ First, go into the #admins room of your homeserver. The first person that
registered on the homeserver automatically joins it. Then send a message into
the room like this:
@conduit:your.server.name: register_appservice
@conduit:your.server.name: register-appservice
```
paste
the
@ -31,7 +31,7 @@ the room like this:
```
You can confirm it worked by sending a message like this:
`@conduit:your.server.name: list_appservices`
`@conduit:your.server.name: list-appservices`
The @conduit bot should answer with `Appservices (1): your-bridge`
@ -46,9 +46,9 @@ could help.
To remove an appservice go to your admin room and execute
```@conduit:your.server.name: unregister_appservice <name>```
```@conduit:your.server.name: unregister-appservice <name>```
where `<name>` one of the output of `list_appservices`.
where `<name>` one of the output of `list-appservices`.
### Tested appservices

75
Cargo.lock generated
View file

@ -269,6 +269,33 @@ dependencies = [
"libloading",
]
[[package]]
name = "clap"
version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a30c3bf9ff12dfe5dae53f0a96e0febcd18420d1c0e7fad77796d9d5c4b5375"
dependencies = [
"bitflags",
"clap_derive",
"indexmap",
"lazy_static",
"os_str_bytes",
"textwrap",
]
[[package]]
name = "clap_derive"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "517358c28fcef6607bf6f76108e02afad7e82297d132a6b846dcc1fc3efcd153"
dependencies = [
"heck 0.4.0",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "color_quant"
version = "1.1.0"
@ -281,6 +308,7 @@ version = "0.2.0"
dependencies = [
"base64 0.13.0",
"bytes",
"clap",
"crossbeam",
"directories",
"heed",
@ -629,7 +657,7 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595"
dependencies = [
"heck",
"heck 0.3.3",
"proc-macro2",
"quote",
"syn",
@ -907,6 +935,12 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "heed"
version = "0.10.6"
@ -1569,6 +1603,15 @@ dependencies = [
"num-traits",
]
[[package]]
name = "os_str_bytes"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
dependencies = [
"memchr",
]
[[package]]
name = "page_size"
version = "0.4.2"
@ -1749,6 +1792,30 @@ dependencies = [
"toml",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
@ -2836,6 +2903,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "textwrap"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
[[package]]
name = "thiserror"
version = "1.0.30"

View file

@ -82,6 +82,8 @@ thread_local = "1.1.3"
# used for TURN server authentication
hmac = "0.11.0"
sha-1 = "0.9.8"
# used for conduit's CLI and admin room command parsing
clap = { version = "3.0.10", default-features = false, features = ["std", "derive"] }
[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemalloc-ctl = { version = "0.4.2", features = ['use_std'] }

View file

@ -1,7 +1,4 @@
use crate::{
database::{admin::AdminCommand, DatabaseGuard},
ConduitResult, Error, Ruma,
};
use crate::{database::DatabaseGuard, ConduitResult, Error, Ruma};
use ruma::{
api::client::{error::ErrorKind, r0::room::report_content},
events::room::message,
@ -50,8 +47,8 @@ pub async fn report_event_route(
));
};
db.admin.send(AdminCommand::SendMessage(
message::RoomMessageEventContent::text_html(
db.admin
.send_message(message::RoomMessageEventContent::text_html(
format!(
"Report received from: {}\n\n\
Event ID: {}\n\
@ -75,8 +72,7 @@ pub async fn report_event_route(
body.score,
RawStr::new(&body.reason).html_escape()
),
),
));
));
db.flush()?;

View file

@ -1,34 +1,39 @@
use std::sync::Arc;
use std::{convert::TryFrom, convert::TryInto, sync::Arc, time::Instant};
use crate::{pdu::PduBuilder, Database};
use rocket::futures::{channel::mpsc, stream::StreamExt};
use crate::{
error::{Error, Result},
pdu::PduBuilder,
server_server, Database, PduEvent,
};
use clap::Parser;
use regex::Regex;
use rocket::{
futures::{channel::mpsc, stream::StreamExt},
http::RawStr,
};
use ruma::{
events::{room::message::RoomMessageEventContent, EventType},
UserId,
EventId, RoomId, RoomVersionId, ServerName, UserId,
};
use serde_json::value::to_raw_value;
use tokio::sync::{MutexGuard, RwLock, RwLockReadGuard};
use tracing::warn;
pub enum AdminCommand {
RegisterAppservice(serde_yaml::Value),
UnregisterAppservice(String),
ListAppservices,
ListLocalUsers,
ShowMemoryUsage,
pub enum AdminRoomEvent {
ProcessMessage(String),
SendMessage(RoomMessageEventContent),
}
#[derive(Clone)]
pub struct Admin {
pub sender: mpsc::UnboundedSender<AdminCommand>,
pub sender: mpsc::UnboundedSender<AdminRoomEvent>,
}
impl Admin {
pub fn start_handler(
&self,
db: Arc<RwLock<Database>>,
mut receiver: mpsc::UnboundedReceiver<AdminCommand>,
mut receiver: mpsc::UnboundedReceiver<AdminRoomEvent>,
) {
tokio::spawn(async move {
// TODO: Use futures when we have long admin commands
@ -47,7 +52,7 @@ impl Admin {
.try_into()
.expect("#admins:server_name is a valid room alias"),
)
.unwrap();
.expect("Admin room must exist");
let conduit_room = match conduit_room {
None => {
@ -96,65 +101,13 @@ impl Admin {
let state_lock = mutex_state.lock().await;
match event {
AdminCommand::ListLocalUsers => {
match guard.users.list_local_users() {
Ok(users) => {
let mut msg: String = format!("Found {} local user account(s):\n", users.len());
msg += &users.join("\n");
send_message(RoomMessageEventContent::text_plain(&msg), guard, &state_lock);
}
Err(e) => {
send_message(RoomMessageEventContent::text_plain(e.to_string()), guard, &state_lock);
}
}
AdminRoomEvent::SendMessage(content) => {
send_message(content, guard, &state_lock);
}
AdminCommand::RegisterAppservice(yaml) => {
match guard.appservice.register_appservice(yaml) {
Ok(id) => {
let msg: String = format!("OK. Appservice {} created", id);
send_message(RoomMessageEventContent::text_plain(msg), guard, &state_lock);
}
Err(_) => {
send_message(RoomMessageEventContent::text_plain("ERR: Failed register appservice. Check server log"), guard, &state_lock);
}
}
}
AdminCommand::UnregisterAppservice(service_name) => {
if let Ok(_) = guard.appservice.unregister_appservice(&service_name) {
if let Ok(_) = guard.sending.cleanup_events(&service_name) {
let msg: String = format!("OK. Appservice {} removed", service_name);
send_message(RoomMessageEventContent::text_plain(msg), guard, &state_lock);
} else {
let msg: String = format!("WARN: Appservice {} removed, but failed to cleanup events", service_name);
send_message(RoomMessageEventContent::text_plain(msg), guard, &state_lock);
}
} else {
let msg: String = format!("ERR. Appservice {} not removed", service_name);
send_message(RoomMessageEventContent::text_plain(msg), guard, &state_lock);
}
}
AdminCommand::ListAppservices => {
if let Ok(appservices) = guard.appservice.iter_ids().map(|ids| ids.collect::<Vec<_>>()) {
let count = appservices.len();
let output = format!(
"Appservices ({}): {}",
count,
appservices.into_iter().filter_map(|r| r.ok()).collect::<Vec<_>>().join(", ")
);
send_message(RoomMessageEventContent::text_plain(output), guard, &state_lock);
} else {
send_message(RoomMessageEventContent::text_plain("Failed to get appservices."), guard, &state_lock);
}
}
AdminCommand::ShowMemoryUsage => {
if let Ok(response) = guard._db.memory_usage() {
send_message(RoomMessageEventContent::text_plain(response), guard, &state_lock);
} else {
send_message(RoomMessageEventContent::text_plain("Failed to get database memory usage.".to_owned()), guard, &state_lock);
}
}
AdminCommand::SendMessage(message) => {
send_message(message, guard, &state_lock);
AdminRoomEvent::ProcessMessage(room_message) => {
let reply_message = process_admin_message(&*guard, room_message);
send_message(reply_message, guard, &state_lock);
}
}
@ -165,7 +118,385 @@ impl Admin {
});
}
pub fn send(&self, command: AdminCommand) {
self.sender.unbounded_send(command).unwrap();
pub fn process_message(&self, room_message: String) {
self.sender
.unbounded_send(AdminRoomEvent::ProcessMessage(room_message))
.unwrap();
}
pub fn send_message(&self, message_content: RoomMessageEventContent) {
self.sender
.unbounded_send(AdminRoomEvent::SendMessage(message_content))
.unwrap();
}
}
// Parse and process a message from the admin room
fn process_admin_message(db: &Database, room_message: String) -> RoomMessageEventContent {
let mut lines = room_message.lines();
let command_line = lines.next().expect("each string has at least one line");
let body: Vec<_> = lines.collect();
let admin_command = match parse_admin_command(&command_line) {
Ok(command) => command,
Err(error) => {
let server_name = db.globals.server_name();
let message = error
.to_string()
.replace("server.name", server_name.as_str());
let html_message = usage_to_html(&message, server_name);
return RoomMessageEventContent::text_html(message, html_message);
}
};
match process_admin_command(db, admin_command, body) {
Ok(reply_message) => reply_message,
Err(error) => {
let markdown_message = format!(
"Encountered an error while handling the command:\n\
```\n{}\n```",
error,
);
let html_message = format!(
"Encountered an error while handling the command:\n\
<pre>\n{}\n</pre>",
error,
);
RoomMessageEventContent::text_html(markdown_message, html_message)
}
}
}
// Parse chat messages from the admin room into an AdminCommand object
fn parse_admin_command(command_line: &str) -> std::result::Result<AdminCommand, String> {
// Note: argv[0] is `@conduit:servername:`, which is treated as the main command
let mut argv: Vec<_> = command_line.split_whitespace().collect();
// Replace `help command` with `command --help`
// Clap has a help subcommand, but it omits the long help description.
if argv.len() > 1 && argv[1] == "help" {
argv.remove(1);
argv.push("--help");
}
// Backwards compatibility with `register_appservice`-style commands
let command_with_dashes;
if argv.len() > 1 && argv[1].contains("_") {
command_with_dashes = argv[1].replace("_", "-");
argv[1] = &command_with_dashes;
}
AdminCommand::try_parse_from(argv).map_err(|error| error.to_string())
}
#[derive(Parser)]
#[clap(name = "@conduit:server.name:", version = env!("CARGO_PKG_VERSION"))]
enum AdminCommand {
#[clap(verbatim_doc_comment)]
/// Register an appservice using its registration YAML
///
/// This command needs a YAML generated by an appservice (such as a bridge),
/// which must be provided in a Markdown code-block below the command.
///
/// Registering a new bridge using the ID of an existing bridge will replace
/// the old one.
///
/// [commandbody]
/// # ```
/// # yaml content here
/// # ```
RegisterAppservice,
/// Unregister an appservice using its ID
///
/// You can find the ID using the `list-appservices` command.
UnregisterAppservice {
/// The appservice to unregister
appservice_identifier: String,
},
/// List all the currently registered appservices
ListAppservices,
/// List users in the database
ListLocalUsers,
/// Get the auth_chain of a PDU
GetAuthChain {
/// An event ID (the $ character followed by the base64 reference hash)
event_id: Box<EventId>,
},
#[clap(verbatim_doc_comment)]
/// Parse and print a PDU from a JSON
///
/// The PDU event is only checked for validity and is not added to the
/// database.
///
/// [commandbody]
/// # ```
/// # PDU json content here
/// # ```
ParsePdu,
/// Retrieve and print a PDU by ID from the Conduit database
GetPdu {
/// An event ID (a $ followed by the base64 reference hash)
event_id: Box<EventId>,
},
/// Print database memory usage statistics
DatabaseMemoryUsage,
}
fn process_admin_command(
db: &Database,
command: AdminCommand,
body: Vec<&str>,
) -> Result<RoomMessageEventContent> {
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::<serde_yaml::Value>(&appservice_config);
match parsed_config {
Ok(yaml) => match db.appservice.register_appservice(yaml) {
Ok(id) => RoomMessageEventContent::text_plain(format!(
"Appservice registered with ID: {}.",
id
)),
Err(e) => RoomMessageEventContent::text_plain(format!(
"Failed to register appservice: {}",
e
)),
},
Err(e) => RoomMessageEventContent::text_plain(format!(
"Could not parse appservice config: {}",
e
)),
}
} else {
RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
)
}
}
AdminCommand::UnregisterAppservice {
appservice_identifier,
} => match db.appservice.unregister_appservice(&appservice_identifier) {
Ok(()) => RoomMessageEventContent::text_plain("Appservice unregistered."),
Err(e) => RoomMessageEventContent::text_plain(format!(
"Failed to unregister appservice: {}",
e
)),
},
AdminCommand::ListAppservices => {
if let Ok(appservices) = db.appservice.iter_ids().map(|ids| ids.collect::<Vec<_>>()) {
let count = appservices.len();
let output = format!(
"Appservices ({}): {}",
count,
appservices
.into_iter()
.filter_map(|r| r.ok())
.collect::<Vec<_>>()
.join(", ")
);
RoomMessageEventContent::text_plain(output)
} else {
RoomMessageEventContent::text_plain("Failed to get appservices.")
}
}
AdminCommand::ListLocalUsers => match db.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::GetAuthChain { event_id } => {
let event_id = Arc::<EventId>::from(event_id);
if let Some(event) = db.rooms.get_pdu_json(&event_id)? {
let room_id_str = event
.get("room_id")
.and_then(|val| val.as_str())
.ok_or_else(|| Error::bad_database("Invalid event in database"))?;
let room_id = <&RoomId>::try_from(room_id_str).map_err(|_| {
Error::bad_database("Invalid room id field in event in database")
})?;
let start = Instant::now();
let count = server_server::get_auth_chain(room_id, vec![event_id], db)?.count();
let elapsed = start.elapsed();
RoomMessageEventContent::text_plain(format!(
"Loaded auth chain with length {} in {:?}",
count, elapsed
))
} else {
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) {
Ok(value) => {
let event_id = EventId::parse(format!(
"${}",
// Anything higher than version3 behaves the same
ruma::signatures::reference_hash(&value, &RoomVersionId::V6)
.expect("ruma can calculate reference hashes")
))
.expect("ruma's reference hashes are valid event ids");
match serde_json::from_value::<PduEvent>(
serde_json::to_value(value).expect("value is json"),
) {
Ok(pdu) => RoomMessageEventContent::text_plain(format!(
"EventId: {:?}\n{:#?}",
event_id, pdu
)),
Err(e) => RoomMessageEventContent::text_plain(format!(
"EventId: {:?}\nCould not parse event: {}",
event_id, e
)),
}
}
Err(e) => RoomMessageEventContent::text_plain(format!(
"Invalid json in command body: {}",
e
)),
}
} else {
RoomMessageEventContent::text_plain("Expected code block in command body.")
}
}
AdminCommand::GetPdu { event_id } => {
let mut outlier = false;
let mut pdu_json = db.rooms.get_non_outlier_pdu_json(&event_id)?;
if pdu_json.is_none() {
outlier = true;
pdu_json = db.rooms.get_pdu_json(&event_id)?;
}
match pdu_json {
Some(json) => {
let json_text =
serde_json::to_string_pretty(&json).expect("canonical json is valid json");
RoomMessageEventContent::text_html(
format!(
"{}\n```json\n{}\n```",
if outlier {
"PDU is outlier"
} else {
"PDU was accepted"
},
json_text
),
format!(
"<p>{}</p>\n<pre><code class=\"language-json\">{}\n</code></pre>\n",
if outlier {
"PDU is outlier"
} else {
"PDU was accepted"
},
RawStr::new(&json_text).html_escape()
),
)
}
None => RoomMessageEventContent::text_plain("PDU not found."),
}
}
AdminCommand::DatabaseMemoryUsage => match db._db.memory_usage() {
Ok(response) => RoomMessageEventContent::text_plain(response),
Err(e) => RoomMessageEventContent::text_plain(format!(
"Failed to get database memory usage: {}",
e
)),
},
};
Ok(reply_message_content)
}
// Utility to turn clap's `--help` text to HTML.
fn usage_to_html(text: &str, server_name: &ServerName) -> String {
// Replace `@conduit:servername:-subcmdname` with `@conduit:servername: subcmdname`
let text = text.replace(
&format!("@conduit:{}:-", server_name),
&format!("@conduit:{}: ", server_name),
);
// For the conduit admin room, subcommands become main commands
let text = text.replace("SUBCOMMAND", "COMMAND");
let text = text.replace("subcommand", "command");
// Escape option names (e.g. `<element-id>`) since they look like HTML tags
let text = text.replace("<", "&lt;").replace(">", "&gt;");
// Italicize the first line (command name and version text)
let re = Regex::new("^(.*?)\n").expect("Regex compilation should not fail");
let text = re.replace_all(&text, "<em>$1</em>\n");
// Unmerge wrapped lines
let text = text.replace("\n ", " ");
// Wrap option names in backticks. The lines look like:
// -V, --version Prints version information
// And are converted to:
// <code>-V, --version</code>: Prints version information
// (?m) enables multi-line mode for ^ and $
let re = Regex::new("(?m)^ (([a-zA-Z_&;-]+(, )?)+) +(.*)$")
.expect("Regex compilation should not fail");
let text = re.replace_all(&text, "<code>$1</code>: $4");
// Look for a `[commandbody]` tag. If it exists, use all lines below it that
// start with a `#` in the USAGE section.
let mut text_lines: Vec<&str> = text.lines().collect();
let mut command_body = String::new();
if let Some(line_index) = text_lines.iter().position(|line| *line == "[commandbody]") {
text_lines.remove(line_index);
while text_lines
.get(line_index)
.map(|line| line.starts_with("#"))
.unwrap_or(false)
{
command_body += if text_lines[line_index].starts_with("# ") {
&text_lines[line_index][2..]
} else {
&text_lines[line_index][1..]
};
command_body += "[nobr]\n";
text_lines.remove(line_index);
}
}
let text = text_lines.join("\n");
// Improve the usage section
let text = if command_body.is_empty() {
// Wrap the usage line in code tags
let re = Regex::new("(?m)^USAGE:\n (@conduit:.*)$")
.expect("Regex compilation should not fail");
re.replace_all(&text, "USAGE:\n<code>$1</code>").to_string()
} else {
// Wrap the usage line in a code block, and add a yaml block example
// This makes the usage of e.g. `register-appservice` more accurate
let re =
Regex::new("(?m)^USAGE:\n (.*?)\n\n").expect("Regex compilation should not fail");
re.replace_all(&text, "USAGE:\n<pre>$1[nobr]\n[commandbodyblock]</pre>")
.replace("[commandbodyblock]", &command_body)
};
// Add HTML line-breaks
let text = text
.replace("\n\n\n", "\n\n")
.replace("\n", "<br>\n")
.replace("[nobr]<br>", "");
text
}

View file

@ -4,12 +4,11 @@ pub use edus::RoomEdus;
use crate::{
pdu::{EventHash, PduBuilder},
server_server, utils, Database, Error, PduEvent, Result,
utils, Database, Error, PduEvent, Result,
};
use lru_cache::LruCache;
use regex::Regex;
use ring::digest;
use rocket::http::RawStr;
use ruma::{
api::{client::error::ErrorKind, federation},
events::{
@ -19,7 +18,6 @@ use ruma::{
room::{
create::RoomCreateEventContent,
member::{MembershipState, RoomMemberEventContent},
message::RoomMessageEventContent,
power_levels::RoomPowerLevelsEventContent,
},
tag::TagEvent,
@ -39,12 +37,11 @@ use std::{
iter,
mem::size_of,
sync::{Arc, Mutex, RwLock},
time::Instant,
};
use tokio::sync::MutexGuard;
use tracing::{error, warn};
use super::{abstraction::Tree, admin::AdminCommand, pusher};
use super::{abstraction::Tree, pusher};
/// The unique identifier of each state group.
///
@ -1491,223 +1488,7 @@ impl Rooms {
.as_ref()
== Some(&pdu.room_id)
{
let mut lines = body.lines();
let command_line = lines.next().expect("each string has at least one line");
let body: Vec<_> = lines.collect();
let mut parts = command_line.split_whitespace().skip(1);
if let Some(command) = parts.next() {
let args: Vec<_> = parts.collect();
match command {
"register_appservice" => {
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::<serde_yaml::Value>(
&appservice_config,
);
match parsed_config {
Ok(yaml) => {
db.admin
.send(AdminCommand::RegisterAppservice(yaml));
}
Err(e) => {
db.admin.send(AdminCommand::SendMessage(
RoomMessageEventContent::text_plain(format!(
"Could not parse appservice config: {}",
e
)),
));
}
}
} else {
db.admin.send(AdminCommand::SendMessage(
RoomMessageEventContent::text_plain(
"Expected code block in command body.",
),
));
}
}
"unregister_appservice" => {
if args.len() == 1 {
db.admin.send(AdminCommand::UnregisterAppservice(
args[0].to_owned(),
));
} else {
db.admin.send(AdminCommand::SendMessage(
RoomMessageEventContent::text_plain(
"Missing appservice identifier",
),
));
}
}
"list_appservices" => {
db.admin.send(AdminCommand::ListAppservices);
}
"list_local_users" => {
db.admin.send(AdminCommand::ListLocalUsers);
}
"get_auth_chain" => {
if args.len() == 1 {
if let Ok(event_id) = EventId::parse_arc(args[0]) {
if let Some(event) = db.rooms.get_pdu_json(&event_id)? {
let room_id_str = event
.get("room_id")
.and_then(|val| val.as_str())
.ok_or_else(|| {
Error::bad_database(
"Invalid event in database",
)
})?;
let room_id = <&RoomId>::try_from(room_id_str)
.map_err(|_| Error::bad_database("Invalid room id field in event in database"))?;
let start = Instant::now();
let count = server_server::get_auth_chain(
room_id,
vec![event_id],
db,
)?
.count();
let elapsed = start.elapsed();
db.admin.send(AdminCommand::SendMessage(
RoomMessageEventContent::text_plain(format!(
"Loaded auth chain with length {} in {:?}",
count, elapsed
)),
));
}
}
}
}
"parse_pdu" => {
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) {
Ok(value) => {
let event_id = EventId::parse(format!(
"${}",
// Anything higher than version3 behaves the same
ruma::signatures::reference_hash(
&value,
&RoomVersionId::V6
)
.expect("ruma can calculate reference hashes")
))
.expect(
"ruma's reference hashes are valid event ids",
);
match serde_json::from_value::<PduEvent>(
serde_json::to_value(value)
.expect("value is json"),
) {
Ok(pdu) => {
db.admin.send(AdminCommand::SendMessage(
RoomMessageEventContent::text_plain(
format!(
"EventId: {:?}\n{:#?}",
event_id, pdu
),
),
));
}
Err(e) => {
db.admin.send(AdminCommand::SendMessage(
RoomMessageEventContent::text_plain(
format!("EventId: {:?}\nCould not parse event: {}", event_id, e),
),
));
}
}
}
Err(e) => {
db.admin.send(AdminCommand::SendMessage(
RoomMessageEventContent::text_plain(format!(
"Invalid json in command body: {}",
e
)),
));
}
}
} else {
db.admin.send(AdminCommand::SendMessage(
RoomMessageEventContent::text_plain(
"Expected code block in command body.",
),
));
}
}
"get_pdu" => {
if args.len() == 1 {
if let Ok(event_id) = EventId::parse(args[0]) {
let mut outlier = false;
let mut pdu_json =
db.rooms.get_non_outlier_pdu_json(&event_id)?;
if pdu_json.is_none() {
outlier = true;
pdu_json = db.rooms.get_pdu_json(&event_id)?;
}
match pdu_json {
Some(json) => {
let json_text =
serde_json::to_string_pretty(&json)
.expect("canonical json is valid json");
db.admin.send(AdminCommand::SendMessage(
RoomMessageEventContent::text_html(
format!("{}\n```json\n{}\n```",
if outlier {
"PDU is outlier"
} else { "PDU was accepted"}, json_text),
format!("<p>{}</p>\n<pre><code class=\"language-json\">{}\n</code></pre>\n",
if outlier {
"PDU is outlier"
} else { "PDU was accepted"}, RawStr::new(&json_text).html_escape())
),
));
}
None => {
db.admin.send(AdminCommand::SendMessage(
RoomMessageEventContent::text_plain(
"PDU not found.",
),
));
}
}
} else {
db.admin.send(AdminCommand::SendMessage(
RoomMessageEventContent::text_plain(
"Event ID could not be parsed.",
),
));
}
} else {
db.admin.send(AdminCommand::SendMessage(
RoomMessageEventContent::text_plain(
"Usage: get_pdu <eventid>",
),
));
}
}
"database_memory_usage" => {
db.admin.send(AdminCommand::ShowMemoryUsage);
}
_ => {
db.admin.send(AdminCommand::SendMessage(
RoomMessageEventContent::text_plain(format!(
"Unrecognized command: {}",
command
)),
));
}
}
}
db.admin.process_message(body.to_string());
}
}
}