Q-line a FORBIDden nick so it can't be used, not just registered
All checks were successful
CI / check (push) Successful in 5m17s

This commit is contained in:
Jean Chevronnet 2026-07-20 22:49:21 +00:00
parent 7cb1c61afa
commit ab8ea65a42
No known key found for this signature in database
3 changed files with 52 additions and 5 deletions

View file

@ -1245,6 +1245,13 @@ impl Engine {
let duration = a.expires.map(|e| e.saturating_sub(now)).unwrap_or(0);
out.push(NetAction::AddLine { kind: a.kind.wire().to_string(), mask: a.mask, setter: a.setter, duration, reason: a.reason });
}
// Re-assert a Q-line for every forbidden nick, so a FORBID keeps the nick
// unusable across a relink (services X-lines don't survive the split).
for f in self.db.forbids() {
if matches!(f.kind, echo_api::ForbidKind::Nick) {
out.push(NetAction::AddLine { kind: echo_api::XlineKind::Qline.wire().to_string(), mask: f.mask, setter: f.setter, duration: 0, reason: f.reason });
}
}
// Re-assert the spam filters so they survive an ircd restart (m_filter has
// no s2s delete, so echo is the durable source of truth for them).
for f in self.db.filters() {

View file

@ -5372,6 +5372,37 @@
assert!(e.db.is_verified("bob"), "bob is now active after the vouch");
}
// FORBID NICK also Q-lines the nick so it can't be used, and the Q-line is
// removed on unforbid and re-asserted on a relink.
#[test]
fn forbid_nick_qlines_it() {
use echo_operserv::OperServ;
let path = std::env::temp_dir().join("echo-forbid-qline.jsonl");
let _ = std::fs::remove_file(&path);
let mut db = Db::open(&path, "42S");
db.scram_iterations = 4096;
db.register("alice", "sesame", None).unwrap();
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() });
let to_os = |e: &mut Engine, text: &str| e.handle(NetEvent::Privmsg { from: "000AAAAAB".into(), to: "42SAAAAAH".into(), text: text.into() });
let out = to_os(&mut e, "FORBID ADD NICK badname reserved");
assert!(out.iter().any(|a| matches!(a, NetAction::AddLine { kind, mask, .. } if kind == "Q" && mask == "badname")), "qline set: {out:?}");
let burst = e.startup_actions();
assert!(burst.iter().any(|a| matches!(a, NetAction::AddLine { kind, mask, .. } if kind == "Q" && mask == "badname")), "qline re-asserted on relink: {burst:?}");
let out = to_os(&mut e, "FORBID DEL NICK badname");
assert!(out.iter().any(|a| matches!(a, NetAction::DelLine { kind, mask } if kind == "Q" && mask == "badname")), "qline removed: {out:?}");
}
// ChanServ SET: description and founder transfer, founder-gated.
#[test]
fn chanserv_set() {