rpc: add server/stats/ban/message/whowas/spamfilter providers; fix REHASH to reload raw_config
This commit is contained in:
parent
ee445396eb
commit
89f508aca9
10 changed files with 408 additions and 11 deletions
|
|
@ -56,17 +56,7 @@ impl Command for Rehash {
|
||||||
"admin {who} has changed the configuration of the server."
|
"admin {who} has changed the configuration of the server."
|
||||||
));
|
));
|
||||||
s.announce(&format!("{who} is rehashing the server config file."));
|
s.announce(&format!("{who} is rehashing the server config file."));
|
||||||
s.motd = fresh.motd;
|
s.apply_config(fresh);
|
||||||
s.opers = fresh.opers;
|
|
||||||
s.cloak_key = fresh.cloak_key;
|
|
||||||
s.censor = fresh.censor;
|
|
||||||
s.amu = fresh.amu;
|
|
||||||
s.resolve_hosts = fresh.resolve_hosts;
|
|
||||||
s.use_resolved_host = fresh.use_resolved_host;
|
|
||||||
s.dnsbl_zones = fresh.dnsbl_zones;
|
|
||||||
s.dnsbl_action = fresh.dnsbl_action;
|
|
||||||
s.dnsbl_reason = fresh.dnsbl_reason;
|
|
||||||
s.sasl_server = fresh.sasl_server;
|
|
||||||
s.announce("Server configuration reloaded.");
|
s.announce("Server configuration reloaded.");
|
||||||
s.numeric(uid, RPL_REHASHING, &format!("{path} :Rehashing"));
|
s.numeric(uid, RPL_REHASHING, &format!("{path} :Rehashing"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,14 @@ pub fn default_modules() -> Vec<Box<dyn Module>> {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The names of the modules loaded at boot (drives the `module.list` RPC).
|
||||||
|
pub fn module_names() -> Vec<String> {
|
||||||
|
default_modules()
|
||||||
|
.iter()
|
||||||
|
.map(|m| m.name().to_string())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Commands contributed by modules (chained into the core command table), so a
|
/// Commands contributed by modules (chained into the core command table), so a
|
||||||
/// module that adds a command keeps it in its own file, InspIRCd-style.
|
/// module that adds a command keeps it in its own file, InspIRCd-style.
|
||||||
pub fn module_commands() -> Vec<Box<dyn Command>> {
|
pub fn module_commands() -> Vec<Box<dyn Command>> {
|
||||||
|
|
|
||||||
83
src/modules/rpc/ban.rs
Normal file
83
src/modules/rpc/ban.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
//! rpc ban provider — `xline.list`, `xline.add`, `xline.del`. InspIRCd's
|
||||||
|
//! `m_rpc_ban`. Covers every echoIRCd x-line kind (K/G/Z/E/SHUN/Q/CBAN) through the
|
||||||
|
//! same `add_xline`/`remove_xline` primitives the oper commands use.
|
||||||
|
|
||||||
|
use super::json::{self, obj, qstr};
|
||||||
|
use super::RpcError;
|
||||||
|
use crate::server::Server;
|
||||||
|
use crate::xline::{parse_duration, XKind};
|
||||||
|
|
||||||
|
/// Map a request `type` (letter or unreal-ish name) to an `XKind`.
|
||||||
|
fn kind_of(t: &str) -> Option<XKind> {
|
||||||
|
let up = t.to_ascii_uppercase();
|
||||||
|
XKind::from_tag(&up).or_else(|| {
|
||||||
|
Some(match up.as_str() {
|
||||||
|
"KLINE" => XKind::Kline,
|
||||||
|
"GLINE" => XKind::Gline,
|
||||||
|
"ZLINE" => XKind::Zline,
|
||||||
|
"ELINE" => XKind::Eline,
|
||||||
|
"QLINE" => XKind::Qline,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(s: &mut Server, action: &str, params: &str) -> Result<String, RpcError> {
|
||||||
|
match action {
|
||||||
|
"list" => {
|
||||||
|
let want = json::get_str(params, "type").and_then(|t| kind_of(&t));
|
||||||
|
let items: Vec<String> = s
|
||||||
|
.xlines
|
||||||
|
.iter()
|
||||||
|
.filter(|x| want.is_none_or(|k| x.kind == k))
|
||||||
|
.map(|x| {
|
||||||
|
let expires_at = if x.expires == 0 {
|
||||||
|
"null".to_string()
|
||||||
|
} else {
|
||||||
|
x.expires.to_string()
|
||||||
|
};
|
||||||
|
obj(&[
|
||||||
|
("type", qstr(x.kind.tag())),
|
||||||
|
("mask", qstr(&x.mask)),
|
||||||
|
("reason", qstr(&x.reason)),
|
||||||
|
("setter", qstr(&x.setter)),
|
||||||
|
("expires_at", expires_at),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(obj(&[("xlines", format!("[{}]", items.join(",")))]))
|
||||||
|
}
|
||||||
|
"add" => {
|
||||||
|
let kind = json::get_str(params, "type")
|
||||||
|
.and_then(|t| kind_of(&t))
|
||||||
|
.ok_or_else(|| RpcError::invalid_params("missing/unknown 'type'"))?;
|
||||||
|
let mask = json::get_str(params, "mask")
|
||||||
|
.or_else(|| json::get_str(params, "name"))
|
||||||
|
.ok_or_else(|| RpcError::invalid_params("missing 'mask'"))?;
|
||||||
|
let duration = json::get_num::<u64>(params, "duration")
|
||||||
|
.or_else(|| {
|
||||||
|
json::get_str(params, "duration_string").and_then(|d| parse_duration(&d))
|
||||||
|
})
|
||||||
|
.unwrap_or(0);
|
||||||
|
let reason = json::get_str(params, "reason").unwrap_or_else(|| "Set via RPC".into());
|
||||||
|
let setter = json::get_str(params, "setter").unwrap_or_else(|| "RPC".into());
|
||||||
|
s.add_xline(kind, &mask, duration, &setter, &reason);
|
||||||
|
Ok(obj(&[("result", "true".into())]))
|
||||||
|
}
|
||||||
|
"del" => {
|
||||||
|
let kind = json::get_str(params, "type")
|
||||||
|
.and_then(|t| kind_of(&t))
|
||||||
|
.ok_or_else(|| RpcError::invalid_params("missing/unknown 'type'"))?;
|
||||||
|
let mask = json::get_str(params, "mask")
|
||||||
|
.or_else(|| json::get_str(params, "name"))
|
||||||
|
.ok_or_else(|| RpcError::invalid_params("missing 'mask'"))?;
|
||||||
|
let removed = s.remove_xline(kind, &mask);
|
||||||
|
if removed {
|
||||||
|
Ok(obj(&[("result", "true".into())]))
|
||||||
|
} else {
|
||||||
|
Err(RpcError::not_found("no matching x-line"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Err(RpcError::method_not_found(&format!("xline.{other}"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/modules/rpc/message.rs
Normal file
48
src/modules/rpc/message.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
//! rpc message provider — `message.send_notice`. InspIRCd's `m_rpc_message`.
|
||||||
|
//! Sends a server NOTICE to a channel (`#…`), a single user (nick), or every
|
||||||
|
//! local user (`*` / `$*`).
|
||||||
|
|
||||||
|
use super::json::{self, obj};
|
||||||
|
use super::RpcError;
|
||||||
|
use crate::server::Server;
|
||||||
|
|
||||||
|
pub fn handle(s: &mut Server, action: &str, params: &str) -> Result<String, RpcError> {
|
||||||
|
match action {
|
||||||
|
"send_notice" => {
|
||||||
|
let target = json::get_str(params, "target")
|
||||||
|
.ok_or_else(|| RpcError::invalid_params("missing 'target'"))?;
|
||||||
|
let text = json::get_str(params, "message")
|
||||||
|
.ok_or_else(|| RpcError::invalid_params("missing 'message'"))?;
|
||||||
|
let src = s.name.clone();
|
||||||
|
if target == "*" || target == "$*" {
|
||||||
|
let uids: Vec<crate::Uid> = s.users.keys().copied().collect();
|
||||||
|
for uid in uids {
|
||||||
|
let nick = s
|
||||||
|
.users
|
||||||
|
.get(&uid)
|
||||||
|
.map(|u| u.nick.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
s.send(uid, format!(":{src} NOTICE {nick} :{text}"));
|
||||||
|
}
|
||||||
|
} else if target.starts_with('#') {
|
||||||
|
let key = target.to_ascii_lowercase();
|
||||||
|
if !s.channels.contains_key(&key) {
|
||||||
|
return Err(RpcError::not_found("no such channel"));
|
||||||
|
}
|
||||||
|
s.to_channel(&key, &format!(":{src} NOTICE {target} :{text}"), None);
|
||||||
|
} else {
|
||||||
|
let uid = s
|
||||||
|
.find_nick(&target)
|
||||||
|
.ok_or_else(|| RpcError::not_found("no such nick"))?;
|
||||||
|
let nick = s
|
||||||
|
.users
|
||||||
|
.get(&uid)
|
||||||
|
.map(|u| u.nick.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
s.send(uid, format!(":{src} NOTICE {nick} :{text}"));
|
||||||
|
}
|
||||||
|
Ok(obj(&[("result", "true".into())]))
|
||||||
|
}
|
||||||
|
other => Err(RpcError::method_not_found(&format!("message.{other}"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,11 +14,17 @@
|
||||||
//! (default `127.0.0.1:8080`); every request must carry the token (HTTP Basic or
|
//! (default `127.0.0.1:8080`); every request must carry the token (HTTP Basic or
|
||||||
//! Bearer), checked constant-time on the listener thread before anything dispatches.
|
//! Bearer), checked constant-time on the listener thread before anything dispatches.
|
||||||
|
|
||||||
|
pub mod ban;
|
||||||
pub mod channel;
|
pub mod channel;
|
||||||
pub mod core;
|
pub mod core;
|
||||||
pub mod httpd;
|
pub mod httpd;
|
||||||
pub mod json;
|
pub mod json;
|
||||||
|
pub mod message;
|
||||||
|
pub mod server;
|
||||||
|
pub mod spamfilter;
|
||||||
|
pub mod stats;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
pub mod whowas;
|
||||||
|
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
|
|
@ -77,6 +83,20 @@ pub const ALL_METHODS: &[&str] = &[
|
||||||
"channel.get",
|
"channel.get",
|
||||||
"channel.kick",
|
"channel.kick",
|
||||||
"channel.set_topic",
|
"channel.set_topic",
|
||||||
|
"server.list",
|
||||||
|
"server.rehash",
|
||||||
|
"server.disconnect",
|
||||||
|
"module.list",
|
||||||
|
"oper.list",
|
||||||
|
"security_group.list",
|
||||||
|
"xline.list",
|
||||||
|
"xline.add",
|
||||||
|
"xline.del",
|
||||||
|
"message.send_notice",
|
||||||
|
"whowas.get",
|
||||||
|
"spamfilter.list",
|
||||||
|
"spamfilter.add",
|
||||||
|
"spamfilter.del",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Run a parsed JSON-RPC request on the core thread. `params` is the raw JSON of
|
/// Run a parsed JSON-RPC request on the core thread. `params` is the raw JSON of
|
||||||
|
|
@ -86,9 +106,15 @@ pub fn dispatch(s: &mut Server, method: &str, params: &str, id: &str) -> String
|
||||||
let result: Result<String, RpcError> = match method {
|
let result: Result<String, RpcError> = match method {
|
||||||
"rpc.methods" | "rpc.info" => core::rpc_info(s, method),
|
"rpc.methods" | "rpc.info" => core::rpc_info(s, method),
|
||||||
"server.info" | "stats.get" => core::server_info(s),
|
"server.info" | "stats.get" => core::server_info(s),
|
||||||
|
"module.list" | "oper.list" | "security_group.list" => stats::handle(s, method, params),
|
||||||
_ => match method.split_once('.') {
|
_ => match method.split_once('.') {
|
||||||
Some(("user", action)) => user::handle(s, action, params),
|
Some(("user", action)) => user::handle(s, action, params),
|
||||||
Some(("channel", action)) => channel::handle(s, action, params),
|
Some(("channel", action)) => channel::handle(s, action, params),
|
||||||
|
Some(("server", action)) => server::handle(s, action, params),
|
||||||
|
Some(("xline", action)) => ban::handle(s, action, params),
|
||||||
|
Some(("message", action)) => message::handle(s, action, params),
|
||||||
|
Some(("whowas", action)) => whowas::handle(s, action, params),
|
||||||
|
Some(("spamfilter", action)) => spamfilter::handle(s, action, params),
|
||||||
_ => Err(RpcError::method_not_found(method)),
|
_ => Err(RpcError::method_not_found(method)),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
62
src/modules/rpc/server.rs
Normal file
62
src/modules/rpc/server.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
//! rpc server provider — `server.list`, `server.rehash`, `server.disconnect`.
|
||||||
|
//! InspIRCd's `m_rpc_server`. (`server.connect` needs the socketengine's dialer,
|
||||||
|
//! which isn't reachable from the core thread — use the `CONNECT` command instead.)
|
||||||
|
|
||||||
|
use super::json::{obj, qstr};
|
||||||
|
use super::RpcError;
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::server::Server;
|
||||||
|
|
||||||
|
pub fn handle(s: &mut Server, action: &str, params: &str) -> Result<String, RpcError> {
|
||||||
|
match action {
|
||||||
|
"list" => {
|
||||||
|
let mut servers = vec![obj(&[
|
||||||
|
("name", qstr(&s.name)),
|
||||||
|
("description", qstr(&s.server_desc)),
|
||||||
|
("uplink", qstr("")),
|
||||||
|
("usercount", s.users.len().to_string()),
|
||||||
|
])];
|
||||||
|
for rs in s.servers.values() {
|
||||||
|
let uplink = s
|
||||||
|
.servers
|
||||||
|
.values()
|
||||||
|
.find(|o| o.sid == rs.sid)
|
||||||
|
.map(|_| s.name.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
servers.push(obj(&[
|
||||||
|
("name", qstr(&rs.name)),
|
||||||
|
("description", qstr(&rs.desc)),
|
||||||
|
("uplink", qstr(&uplink)),
|
||||||
|
("usercount", "0".to_string()),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
Ok(obj(&[("servers", format!("[{}]", servers.join(",")))]))
|
||||||
|
}
|
||||||
|
"rehash" => match Config::try_load(&s.conf_path) {
|
||||||
|
Some(fresh) => {
|
||||||
|
s.apply_config(fresh);
|
||||||
|
s.announce("Server configuration reloaded via RPC.");
|
||||||
|
Ok(obj(&[("result", "true".into())]))
|
||||||
|
}
|
||||||
|
None => Err(RpcError::internal("config file could not be read")),
|
||||||
|
},
|
||||||
|
"disconnect" => {
|
||||||
|
let name = json_name(params)?;
|
||||||
|
let via = s
|
||||||
|
.servers
|
||||||
|
.values()
|
||||||
|
.find(|rs| rs.name.eq_ignore_ascii_case(&name))
|
||||||
|
.map(|rs| rs.via)
|
||||||
|
.ok_or_else(|| RpcError::not_found("no such linked server"))?;
|
||||||
|
s.close_link(via, "Disconnected via RPC");
|
||||||
|
Ok(obj(&[("result", "true".into())]))
|
||||||
|
}
|
||||||
|
other => Err(RpcError::method_not_found(&format!("server.{other}"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_name(params: &str) -> Result<String, RpcError> {
|
||||||
|
super::json::get_str(params, "name")
|
||||||
|
.or_else(|| super::json::get_str(params, "server"))
|
||||||
|
.ok_or_else(|| RpcError::invalid_params("missing 'name'"))
|
||||||
|
}
|
||||||
66
src/modules/rpc/spamfilter.rs
Normal file
66
src/modules/rpc/spamfilter.rs
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
//! rpc spamfilter provider — `spamfilter.list`, `spamfilter.add`, `spamfilter.del`.
|
||||||
|
//! InspIRCd's `m_rpc_spamfilter`. Operates on the same [`crate::modules::filter`]
|
||||||
|
//! rule set (stored in `Server.ext`) that the `FILTER` command and the enforcement
|
||||||
|
//! hook use, so a rule added here takes effect immediately.
|
||||||
|
|
||||||
|
use super::json::{self, obj, qstr};
|
||||||
|
use super::RpcError;
|
||||||
|
use crate::modules::filter::{Filters, SpamFilter};
|
||||||
|
use crate::server::Server;
|
||||||
|
|
||||||
|
pub fn handle(s: &mut Server, action: &str, params: &str) -> Result<String, RpcError> {
|
||||||
|
match action {
|
||||||
|
"list" => {
|
||||||
|
let items: Vec<String> = s
|
||||||
|
.ext
|
||||||
|
.get::<Filters>()
|
||||||
|
.map(|f| {
|
||||||
|
f.0.iter()
|
||||||
|
.map(|r| {
|
||||||
|
obj(&[
|
||||||
|
("pattern", qstr(&r.pattern)),
|
||||||
|
("reason", qstr(&r.reason)),
|
||||||
|
("action", qstr(&r.action)),
|
||||||
|
("duration", r.duration.to_string()),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
Ok(obj(&[("filters", format!("[{}]", items.join(",")))]))
|
||||||
|
}
|
||||||
|
"add" => {
|
||||||
|
let pattern = json::get_str(params, "pattern")
|
||||||
|
.or_else(|| json::get_str(params, "name"))
|
||||||
|
.ok_or_else(|| RpcError::invalid_params("missing 'pattern'"))?;
|
||||||
|
let action = json::get_str(params, "action").unwrap_or_else(|| "block".into());
|
||||||
|
let reason = json::get_str(params, "reason").unwrap_or_else(|| "Set via RPC".into());
|
||||||
|
let duration = json::get_num::<u64>(params, "duration").unwrap_or(0);
|
||||||
|
let set = s.ext.get_or_insert_with::<Filters>(Filters::default);
|
||||||
|
if set.0.iter().any(|f| f.pattern == pattern) {
|
||||||
|
return Err(RpcError::not_found("filter already exists"));
|
||||||
|
}
|
||||||
|
set.0.push(SpamFilter {
|
||||||
|
pattern,
|
||||||
|
action,
|
||||||
|
duration,
|
||||||
|
reason,
|
||||||
|
});
|
||||||
|
Ok(obj(&[("result", "true".into())]))
|
||||||
|
}
|
||||||
|
"del" => {
|
||||||
|
let pattern = json::get_str(params, "pattern")
|
||||||
|
.or_else(|| json::get_str(params, "name"))
|
||||||
|
.ok_or_else(|| RpcError::invalid_params("missing 'pattern'"))?;
|
||||||
|
let set = s.ext.get_or_insert_with::<Filters>(Filters::default);
|
||||||
|
let before = set.0.len();
|
||||||
|
set.0.retain(|f| f.pattern != pattern);
|
||||||
|
if set.0.len() < before {
|
||||||
|
Ok(obj(&[("result", "true".into())]))
|
||||||
|
} else {
|
||||||
|
Err(RpcError::not_found("no matching filter"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => Err(RpcError::method_not_found(&format!("spamfilter.{other}"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/modules/rpc/stats.rs
Normal file
61
src/modules/rpc/stats.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
//! rpc stats provider — read-only introspection: `module.list`, `oper.list`,
|
||||||
|
//! `security_group.list`. InspIRCd's `m_rpc_stats`.
|
||||||
|
|
||||||
|
use super::json::{obj, qstr};
|
||||||
|
use super::RpcError;
|
||||||
|
use crate::server::Server;
|
||||||
|
|
||||||
|
pub fn handle(s: &mut Server, method: &str, _params: &str) -> Result<String, RpcError> {
|
||||||
|
match method {
|
||||||
|
"module.list" => {
|
||||||
|
let mods: Vec<String> = crate::modules::module_names()
|
||||||
|
.iter()
|
||||||
|
.map(|n| {
|
||||||
|
obj(&[
|
||||||
|
("name", qstr(n)),
|
||||||
|
("description", qstr("")),
|
||||||
|
("version", qstr("")),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(obj(&[("modules", format!("[{}]", mods.join(",")))]))
|
||||||
|
}
|
||||||
|
"oper.list" => {
|
||||||
|
// configured oper blocks — names only, never the passwords
|
||||||
|
let opers: Vec<String> = s
|
||||||
|
.opers
|
||||||
|
.iter()
|
||||||
|
.map(|(name, _pass)| {
|
||||||
|
obj(&[
|
||||||
|
("name", qstr(name)),
|
||||||
|
("type", qstr("")),
|
||||||
|
("online", "0".into()),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(obj(&[("opers", format!("[{}]", opers.join(",")))]))
|
||||||
|
}
|
||||||
|
"security_group.list" => {
|
||||||
|
let groups: Vec<String> = s
|
||||||
|
.conf_all("securitygroup")
|
||||||
|
.iter()
|
||||||
|
.filter_map(|line| {
|
||||||
|
let mut it = line.split_whitespace();
|
||||||
|
let name = it.next()?;
|
||||||
|
let criteria = it.collect::<Vec<_>>().join(" ");
|
||||||
|
let public = criteria.split_whitespace().any(|t| t == "public");
|
||||||
|
Some(obj(&[
|
||||||
|
("name", qstr(name)),
|
||||||
|
("criteria", qstr(&criteria)),
|
||||||
|
("public", public.to_string()),
|
||||||
|
]))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(obj(&[(
|
||||||
|
"security_groups",
|
||||||
|
format!("[{}]", groups.join(",")),
|
||||||
|
)]))
|
||||||
|
}
|
||||||
|
other => Err(RpcError::method_not_found(other)),
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/modules/rpc/whowas.rs
Normal file
35
src/modules/rpc/whowas.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
//! rpc whowas provider — `whowas.get`. InspIRCd's `m_rpc_whowas`. Returns the
|
||||||
|
//! recent-nick-history entries the ircd keeps for `WHOWAS`.
|
||||||
|
|
||||||
|
use super::json::{self, obj, qstr};
|
||||||
|
use super::RpcError;
|
||||||
|
use crate::server::Server;
|
||||||
|
|
||||||
|
pub fn handle(s: &mut Server, action: &str, params: &str) -> Result<String, RpcError> {
|
||||||
|
match action {
|
||||||
|
"get" => {
|
||||||
|
let nick = json::get_str(params, "nick")
|
||||||
|
.ok_or_else(|| RpcError::invalid_params("missing 'nick'"))?;
|
||||||
|
let entries: Vec<String> = s
|
||||||
|
.whowas
|
||||||
|
.iter()
|
||||||
|
.filter(|e| e.nick.eq_ignore_ascii_case(&nick))
|
||||||
|
.map(|e| {
|
||||||
|
let mut fields = vec![
|
||||||
|
("nick", qstr(&e.nick)),
|
||||||
|
("ident", qstr(&e.ident)),
|
||||||
|
("host", qstr(&e.host)),
|
||||||
|
("realname", qstr(&e.realname)),
|
||||||
|
("signon", e.ts.to_string()),
|
||||||
|
];
|
||||||
|
if let Some(acct) = &e.account {
|
||||||
|
fields.push(("account", qstr(acct)));
|
||||||
|
}
|
||||||
|
obj(&fields)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(obj(&[("entries", format!("[{}]", entries.join(",")))]))
|
||||||
|
}
|
||||||
|
other => Err(RpcError::method_not_found(&format!("whowas.{other}"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -224,6 +224,24 @@ impl Server {
|
||||||
.unwrap_or(default)
|
.unwrap_or(default)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply a freshly-loaded config to the running server — the shared body of
|
||||||
|
/// REHASH and the `server.rehash` RPC. Reloads the typed fields **and**
|
||||||
|
/// `raw_config`, so modules reading via `conf*` see the new values too.
|
||||||
|
pub fn apply_config(&mut self, fresh: crate::config::Config) {
|
||||||
|
self.motd = fresh.motd;
|
||||||
|
self.opers = fresh.opers;
|
||||||
|
self.cloak_key = fresh.cloak_key;
|
||||||
|
self.censor = fresh.censor;
|
||||||
|
self.amu = fresh.amu;
|
||||||
|
self.resolve_hosts = fresh.resolve_hosts;
|
||||||
|
self.use_resolved_host = fresh.use_resolved_host;
|
||||||
|
self.dnsbl_zones = fresh.dnsbl_zones;
|
||||||
|
self.dnsbl_action = fresh.dnsbl_action;
|
||||||
|
self.dnsbl_reason = fresh.dnsbl_reason;
|
||||||
|
self.sasl_server = fresh.sasl_server;
|
||||||
|
self.raw_config = fresh.raw;
|
||||||
|
}
|
||||||
|
|
||||||
/// Remember an identity for WHOWAS (capped ring, newest first).
|
/// Remember an identity for WHOWAS (capped ring, newest first).
|
||||||
pub fn push_whowas(
|
pub fn push_whowas(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue