OperServ: LOGSEARCH — search the action log

LOGSEARCH [pattern] searches the incident log newest-first: match a bare
id (as stamped into a kick reason), or any word from the summary; no
argument lists the most recent. Auspex-gated (read-only investigation).

To make it comprehensive, the command audit trail now records every
notable action to the same incident log (not just to the audit channel,
and regardless of whether one is configured) — so registrations, drops,
akills, suspensions, vhosts, notes, and the like are all findable
alongside the kicks and kills stamped at the removal choke-point.
This commit is contained in:
Jean Chevronnet 2026-07-14 02:16:59 +00:00
parent dc2331f439
commit a7aacf347e
No known key found for this signature in database
3 changed files with 98 additions and 25 deletions

View file

@ -33,6 +33,8 @@ mod session;
mod jupe;
#[path = "chankill.rs"]
mod chankill;
#[path = "logsearch.rs"]
mod logsearch;
pub struct OperServ {
pub uid: String,
@ -76,6 +78,7 @@ impl Service for OperServ {
Some(cmd) if cmd.eq_ignore_ascii_case("EXCEPTION") => session::handle_exception(me, from, args, ctx, db),
Some(cmd) if cmd.eq_ignore_ascii_case("JUPE") => jupe::handle(me, from, args, ctx, db),
Some(cmd) if cmd.eq_ignore_ascii_case("CHANKILL") => chankill::handle(me, from, args, ctx, net, db),
Some(cmd) if cmd.eq_ignore_ascii_case("LOGSEARCH") => logsearch::handle(me, from, args, ctx, net),
Some(cmd) if cmd.eq_ignore_ascii_case("HELP") => help(me, from, ctx),
None => help(me, from, ctx),
Some(other) => ctx.notice(me, from.uid, format!("I don't know \x02{other}\x02. Try \x02HELP\x02.")),
@ -84,5 +87,5 @@ impl Service for OperServ {
}
fn help(me: &str, from: &Sender, ctx: &mut ServiceCtx) {
ctx.notice(me, from.uid, "OperServ holds network operator tools (Priv::Admin): \x02AKILL\x02 ADD|DEL|LIST (user@host bans), \x02SQLINE\x02 ADD|DEL|LIST (nick bans), \x02SNLINE\x02 ADD|DEL|LIST (realname bans), \x02SHUN\x02 ADD|DEL|LIST (silence a host), \x02CHANKILL\x02 <#chan> (clear a channel), \x02GLOBAL\x02 <message> (announce to everyone), \x02KILL\x02 <nick> [reason] (disconnect a user), \x02KICK\x02 <#chan> <nick> [reason], \x02MODE\x02 <#chan> <modes> [params], \x02IGNORE\x02 ADD|DEL|LIST (silence a user from services), \x02STATS\x02 (enforcement overview), \x02SVSNICK\x02 <nick> <newnick>, \x02SVSJOIN\x02 <nick> <#chan> [key], \x02INFO\x02 ADD|DEL <target> (staff notes), \x02NEWS\x02 ADD|DEL|LIST <LOGON|OPER> (announcements), \x02OPER\x02 ADD|DEL|LIST (runtime operators), \x02SESSION\x02 LIST|VIEW + \x02EXCEPTION\x02 ADD|DEL|LIST (per-IP session limits), \x02JUPE\x02 <server> | DEL | LIST.");
ctx.notice(me, from.uid, "OperServ holds network operator tools (Priv::Admin): \x02AKILL\x02 ADD|DEL|LIST (user@host bans), \x02SQLINE\x02 ADD|DEL|LIST (nick bans), \x02SNLINE\x02 ADD|DEL|LIST (realname bans), \x02SHUN\x02 ADD|DEL|LIST (silence a host), \x02CHANKILL\x02 <#chan> (clear a channel), \x02GLOBAL\x02 <message> (announce to everyone), \x02KILL\x02 <nick> [reason] (disconnect a user), \x02KICK\x02 <#chan> <nick> [reason], \x02MODE\x02 <#chan> <modes> [params], \x02IGNORE\x02 ADD|DEL|LIST (silence a user from services), \x02STATS\x02 (enforcement overview), \x02SVSNICK\x02 <nick> <newnick>, \x02SVSJOIN\x02 <nick> <#chan> [key], \x02INFO\x02 ADD|DEL <target> (staff notes), \x02NEWS\x02 ADD|DEL|LIST <LOGON|OPER> (announcements), \x02OPER\x02 ADD|DEL|LIST (runtime operators), \x02SESSION\x02 LIST|VIEW + \x02EXCEPTION\x02 ADD|DEL|LIST (per-IP session limits), \x02JUPE\x02 <server> | DEL | LIST, \x02LOGSEARCH\x02 [pattern] (search the action log).");
}

23
operserv/src/logsearch.rs Normal file
View file

@ -0,0 +1,23 @@
use fedserv_api::{human_time, NetView, Priv, Sender, ServiceCtx};
// LOGSEARCH [pattern]: search the recent action log — every kick, kill, ban,
// registration/drop, akill, suspension, vhost, note, and so on. A bare id (as
// stamped into a kick reason, e.g. from `[#3F]`) or any word from the summary
// matches; no argument lists the most recent. Read-only, so any operator who can
// see hidden info (Priv::Auspex) may run it.
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView) {
if !from.privs.has(Priv::Auspex) {
ctx.notice(me, from.uid, "Access denied — LOGSEARCH needs the \x02auspex\x02 privilege.");
return;
}
let pattern = args[1..].join(" ");
let hits = net.search_incidents(&pattern, 20);
if hits.is_empty() {
ctx.notice(me, from.uid, "No matching log entries.");
return;
}
for h in &hits {
ctx.notice(me, from.uid, format!("[\x02#{}\x02] {}{}", h.id, human_time(h.ts), h.summary));
}
ctx.notice(me, from.uid, format!("End of results ({} shown, newest first — refine the search to narrow).", hits.len()));
}