Add OperServ PROTECT to turn nick protection on/off per account or for all (bulk-enable after a migration)
All checks were successful
CI / check (push) Successful in 5m13s
All checks were successful
CI / check (push) Successful in 5m13s
This commit is contained in:
parent
ab8ea65a42
commit
ff4856a12e
3 changed files with 67 additions and 0 deletions
|
|
@ -11,6 +11,8 @@ mod spamfilter;
|
||||||
mod redact;
|
mod redact;
|
||||||
#[path = "forbid.rs"]
|
#[path = "forbid.rs"]
|
||||||
mod forbid;
|
mod forbid;
|
||||||
|
#[path = "protect.rs"]
|
||||||
|
mod protect;
|
||||||
mod notify;
|
mod notify;
|
||||||
#[path = "global.rs"]
|
#[path = "global.rs"]
|
||||||
mod global;
|
mod global;
|
||||||
|
|
@ -82,6 +84,7 @@ impl Service for OperServ {
|
||||||
Some(cmd) if cmd.eq_ignore_ascii_case("SPAMFILTER") => spamfilter::handle(me, from, args, ctx, db),
|
Some(cmd) if cmd.eq_ignore_ascii_case("SPAMFILTER") => spamfilter::handle(me, from, args, ctx, db),
|
||||||
Some(cmd) if cmd.eq_ignore_ascii_case("REDACT") => redact::handle(me, from, args, ctx),
|
Some(cmd) if cmd.eq_ignore_ascii_case("REDACT") => redact::handle(me, from, args, ctx),
|
||||||
Some(cmd) if cmd.eq_ignore_ascii_case("FORBID") => forbid::handle(me, from, args, ctx, db),
|
Some(cmd) if cmd.eq_ignore_ascii_case("FORBID") => forbid::handle(me, from, args, ctx, db),
|
||||||
|
Some(cmd) if cmd.eq_ignore_ascii_case("PROTECT") => protect::handle(me, from, args, ctx, db),
|
||||||
Some(cmd) if cmd.eq_ignore_ascii_case("NOTIFY") => notify::handle(me, from, args, ctx, db),
|
Some(cmd) if cmd.eq_ignore_ascii_case("NOTIFY") => notify::handle(me, from, args, ctx, db),
|
||||||
Some(cmd) if cmd.eq_ignore_ascii_case("GLOBAL") => global::handle(me, from, args, ctx),
|
Some(cmd) if cmd.eq_ignore_ascii_case("GLOBAL") => global::handle(me, from, args, ctx),
|
||||||
Some(cmd) if cmd.eq_ignore_ascii_case("KILL") => kill::handle(me, from, args, ctx, net),
|
Some(cmd) if cmd.eq_ignore_ascii_case("KILL") => kill::handle(me, from, args, ctx, net),
|
||||||
|
|
@ -118,6 +121,7 @@ const TOPICS: &[HelpEntry] = &[
|
||||||
HelpEntry { cmd: "SPAMFILTER", summary: "content spam filter (ircd m_filter)", detail: "Syntax: \x02SPAMFILTER ADD <action> [+expiry] <regex> <reason> | SPAMFILTER DEL <regex|number> | SPAMFILTER LIST [pattern]\x02\nMatches message content (a regular expression, e.g. \x02.*free.*bitcoin.*\x02) and acts on it. Actions: gline, zline, block, silent, kill, shun, warn, none. echo persists filters and re-applies them to the ircd at each link. Admin only." },
|
HelpEntry { cmd: "SPAMFILTER", summary: "content spam filter (ircd m_filter)", detail: "Syntax: \x02SPAMFILTER ADD <action> [+expiry] <regex> <reason> | SPAMFILTER DEL <regex|number> | SPAMFILTER LIST [pattern]\x02\nMatches message content (a regular expression, e.g. \x02.*free.*bitcoin.*\x02) and acts on it. Actions: gline, zline, block, silent, kill, shun, warn, none. echo persists filters and re-applies them to the ircd at each link. Admin only." },
|
||||||
HelpEntry { cmd: "REDACT", summary: "delete a message by its msgid", detail: "Syntax: \x02REDACT <#channel|nick> <msgid> [reason]\x02\nDeletes a specific message (by its IRCv3 \x02msgid\x02, which your client shows) from everyone who received it. draft/message-redaction." },
|
HelpEntry { cmd: "REDACT", summary: "delete a message by its msgid", detail: "Syntax: \x02REDACT <#channel|nick> <msgid> [reason]\x02\nDeletes a specific message (by its IRCv3 \x02msgid\x02, which your client shows) from everyone who received it. draft/message-redaction." },
|
||||||
HelpEntry { cmd: "FORBID", summary: "ban a nick/chan/email from registration", detail: "Syntax: \x02FORBID ADD <NICK|CHAN|EMAIL> <mask> <reason> | FORBID DEL <NICK|CHAN|EMAIL> <mask> | FORBID LIST\x02\nStops a nick, channel, or email pattern from being registered." },
|
HelpEntry { cmd: "FORBID", summary: "ban a nick/chan/email from registration", detail: "Syntax: \x02FORBID ADD <NICK|CHAN|EMAIL> <mask> <reason> | FORBID DEL <NICK|CHAN|EMAIL> <mask> | FORBID LIST\x02\nStops a nick, channel, or email pattern from being registered." },
|
||||||
|
HelpEntry { cmd: "PROTECT", summary: "turn nick protection on/off for accounts", detail: "Syntax: \x02PROTECT ALL | <account> [ON|OFF]\x02\nToggles the identify-or-be-renamed enforcement. \x02ALL\x02 enables it on every account that has it off (e.g. after a migration). Admin only." },
|
||||||
HelpEntry { cmd: "NOTIFY", summary: "watch masks and log their events", detail: "Syntax: \x02NOTIFY ADD +<expiry> <flags|*> <mask> <reason> | NOTIFY DEL <mask|number> | NOTIFY LIST [pattern] | NOTIFY VIEW [pattern] | NOTIFY CLEAR\x02\nWatches a \x02nick!user@host\x02 pattern, bare nick glob, or \x02#channel\x02; matching users have their flagged events announced to the staff feed. Flags: \x02c\x02 connect, \x02d\x02 disconnect, \x02o\x02 oper-up, \x02j\x02 join, \x02p\x02 part, \x02k\x02 kick, \x02m\x02 channel mode, \x02t\x02 topic, \x02n\x02 nick change, \x02u\x02 user mode, \x02s\x02 services command, \x02S\x02 services SET (\x02*\x02 for all). Expiry like \x02+30d\x02, or \x02+0\x02 for permanent. Admin only." },
|
HelpEntry { cmd: "NOTIFY", summary: "watch masks and log their events", detail: "Syntax: \x02NOTIFY ADD +<expiry> <flags|*> <mask> <reason> | NOTIFY DEL <mask|number> | NOTIFY LIST [pattern] | NOTIFY VIEW [pattern] | NOTIFY CLEAR\x02\nWatches a \x02nick!user@host\x02 pattern, bare nick glob, or \x02#channel\x02; matching users have their flagged events announced to the staff feed. Flags: \x02c\x02 connect, \x02d\x02 disconnect, \x02o\x02 oper-up, \x02j\x02 join, \x02p\x02 part, \x02k\x02 kick, \x02m\x02 channel mode, \x02t\x02 topic, \x02n\x02 nick change, \x02u\x02 user mode, \x02s\x02 services command, \x02S\x02 services SET (\x02*\x02 for all). Expiry like \x02+30d\x02, or \x02+0\x02 for permanent. Admin only." },
|
||||||
HelpEntry { cmd: "SQLINE", summary: "ban a nick pattern", detail: "Syntax: \x02SQLINE ADD [+expiry] <nick> <reason> | SQLINE DEL <mask|number> | SQLINE LIST [pattern]\x02\nBans matching nicknames." },
|
HelpEntry { cmd: "SQLINE", summary: "ban a nick pattern", detail: "Syntax: \x02SQLINE ADD [+expiry] <nick> <reason> | SQLINE DEL <mask|number> | SQLINE LIST [pattern]\x02\nBans matching nicknames." },
|
||||||
HelpEntry { cmd: "SNLINE", summary: "ban a realname pattern", detail: "Syntax: \x02SNLINE ADD [+expiry] <realname> <reason> | SNLINE DEL <mask|number> | SNLINE LIST [pattern]\x02\nBans matching real names." },
|
HelpEntry { cmd: "SNLINE", summary: "ban a realname pattern", detail: "Syntax: \x02SNLINE ADD [+expiry] <realname> <reason> | SNLINE DEL <mask|number> | SNLINE LIST [pattern]\x02\nBans matching real names." },
|
||||||
|
|
|
||||||
37
modules/operserv/src/protect.rs
Normal file
37
modules/operserv/src/protect.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
use echo_api::{Priv, Sender, ServiceCtx, Store};
|
||||||
|
|
||||||
|
// PROTECT ALL | <account> [ON|OFF]: turn NickServ nick-protection (the
|
||||||
|
// identify-or-be-renamed enforcement) on or off for accounts. ALL enables it on
|
||||||
|
// every account that currently has it off — useful after a migration that
|
||||||
|
// carried protection over as off. 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 — PROTECT needs the \x02admin\x02 privilege.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match args.get(1) {
|
||||||
|
Some(t) if t.eq_ignore_ascii_case("ALL") => {
|
||||||
|
let mut n = 0;
|
||||||
|
for a in db.accounts_matching("*") {
|
||||||
|
if !db.account_wants_protect(&a.name) && db.set_account_kill(&a.name, true).is_ok() {
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.notice(me, from.uid, format!("Enabled nick protection on \x02{n}\x02 account(s) that had it off."));
|
||||||
|
ctx.alert("OPER", format!("enabled nick protection on {n} accounts (PROTECT ALL)"));
|
||||||
|
}
|
||||||
|
Some(&account) => {
|
||||||
|
if !db.exists(account) {
|
||||||
|
ctx.notice(me, from.uid, format!("\x02{account}\x02 isn't registered."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let on = !matches!(args.get(2).map(|s| s.to_ascii_uppercase()).as_deref(), Some("OFF"));
|
||||||
|
if db.set_account_kill(account, on).is_ok() {
|
||||||
|
ctx.notice(me, from.uid, format!("Nick protection for \x02{account}\x02 is now \x02{}\x02.", if on { "on" } else { "off" }));
|
||||||
|
} else {
|
||||||
|
ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => ctx.notice(me, from.uid, "Syntax: PROTECT ALL | <account> [ON|OFF]"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5403,6 +5403,32 @@
|
||||||
assert!(out.iter().any(|a| matches!(a, NetAction::DelLine { kind, mask } if kind == "Q" && mask == "badname")), "qline removed: {out:?}");
|
assert!(out.iter().any(|a| matches!(a, NetAction::DelLine { kind, mask } if kind == "Q" && mask == "badname")), "qline removed: {out:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OperServ PROTECT ALL enables nick protection on accounts that had it off
|
||||||
|
// (e.g. carried over from a migration).
|
||||||
|
#[test]
|
||||||
|
fn operserv_protect_all_enables_protection() {
|
||||||
|
use echo_operserv::OperServ;
|
||||||
|
let path = std::env::temp_dir().join("echo-protectall.jsonl");
|
||||||
|
let _ = std::fs::remove_file(&path);
|
||||||
|
let mut db = Db::open(&path, "42S");
|
||||||
|
db.scram_iterations = 4096;
|
||||||
|
db.register("alice", "sesame", None).unwrap();
|
||||||
|
db.register("bob", "pw", None).unwrap();
|
||||||
|
db.set_account_kill("bob", false).unwrap(); // bob has protection OFF
|
||||||
|
assert!(!db.account_wants_protect("bob"));
|
||||||
|
let ns = NickServ { uid: "42SAAAAAA".into(), guest_nick: "Guest".into(), guest_seq: 0 };
|
||||||
|
let os = OperServ { uid: "42SAAAAAH".into() };
|
||||||
|
let mut e = Engine::new(vec![Box::new(ns), Box::new(os)], db);
|
||||||
|
e.set_sid("42S".into());
|
||||||
|
let mut opers = std::collections::HashMap::new();
|
||||||
|
opers.insert("alice".to_string(), Privs::default().with(echo_api::Priv::Admin));
|
||||||
|
e.set_opers(opers);
|
||||||
|
e.handle(NetEvent::UserConnect { uid: "000AAAAAB".into(), nick: "alice".into(), host: "h".into(), ip: "0.0.0.0".into() });
|
||||||
|
e.handle(NetEvent::Privmsg { from: "000AAAAAB".into(), to: "42SAAAAAA".into(), text: "IDENTIFY sesame".into() });
|
||||||
|
e.handle(NetEvent::Privmsg { from: "000AAAAAB".into(), to: "42SAAAAAH".into(), text: "PROTECT ALL".into() });
|
||||||
|
assert!(e.db.account_wants_protect("bob"), "bob is now protected after PROTECT ALL");
|
||||||
|
}
|
||||||
|
|
||||||
// ChanServ SET: description and founder transfer, founder-gated.
|
// ChanServ SET: description and founder transfer, founder-gated.
|
||||||
#[test]
|
#[test]
|
||||||
fn chanserv_set() {
|
fn chanserv_set() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue