OperServ: DEFCON network defence levels

DEFCON [1-5] reads or sets a network-wide defence posture, each level
adding a restriction the engine and services enforce:
- 4: channel registrations frozen (ChanServ REGISTER)
- 3: all registrations frozen (the pre_register_check choke-point, covering
     NickServ and the IRCv3 relay, plus channels)
- 2: sessions additionally capped to one per host
- 1: full lockdown — new connections are turned away on connect

Setting a level announces it to the whole network. Node-local level on the
db so services can read the derived freezes; admin-only.
This commit is contained in:
Jean Chevronnet 2026-07-14 02:35:09 +00:00
parent 85d01b3ebf
commit 4c899d80b0
No known key found for this signature in database
6 changed files with 165 additions and 4 deletions

40
operserv/src/defcon.rs Normal file
View file

@ -0,0 +1,40 @@
use fedserv_api::{Priv, Sender, ServiceCtx, Store};
// DEFCON [1-5]: read or set the network defence level. 5 is normal; each lower
// level adds a restriction — 4 freezes channel registrations, 3 freezes all
// registrations, 2 also caps everyone to one session, 1 is a full lockdown that
// turns away new connections. Changing it announces the level to the network.
// Admin-only.
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
if !from.privs.has(Priv::Admin) {
ctx.notice(me, from.uid, "Access denied — DEFCON needs the \x02admin\x02 privilege.");
return;
}
match args.get(1) {
None => ctx.notice(me, from.uid, format!("The network is at \x02DEFCON {}\x02{}.", db.defcon(), describe(db.defcon()))),
Some(&arg) => match arg.parse::<u8>() {
Ok(level) if (1..=5).contains(&level) => {
db.set_defcon(level);
ctx.notice(me, from.uid, format!("DEFCON set to \x02{level}\x02{}.", describe(level)));
// Let everyone know the posture changed.
let msg = if level == 5 {
"The network is back to normal operations (DEFCON 5).".to_string()
} else {
format!("Network defence level is now \x02DEFCON {level}\x02: {}.", describe(level))
};
ctx.global(me, msg);
}
_ => ctx.notice(me, from.uid, "Syntax: DEFCON [1-5] (5 = normal, 1 = lockdown)"),
},
}
}
fn describe(level: u8) -> &'static str {
match level {
5 => "normal operations",
4 => "channel registrations frozen",
3 => "all registrations frozen",
2 => "registrations frozen and sessions capped to one per host",
_ => "full lockdown, no new connections",
}
}

View file

@ -35,6 +35,8 @@ mod jupe;
mod chankill;
#[path = "logsearch.rs"]
mod logsearch;
#[path = "defcon.rs"]
mod defcon;
pub struct OperServ {
pub uid: String,
@ -80,6 +82,7 @@ impl Service for OperServ {
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("DEFCON") => defcon::handle(me, from, args, ctx, db),
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.")),
@ -88,5 +91,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), \x02CBAN\x02 ADD|DEL|LIST (ban a channel name), \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).");
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), \x02CBAN\x02 ADD|DEL|LIST (ban a channel name), \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), \x02DEFCON\x02 [1-5] (network defence level).");
}