diff --git a/modules/hostserv/src/approve.rs b/modules/hostserv/src/approve.rs index a326840..18411f6 100644 --- a/modules/hostserv/src/approve.rs +++ b/modules/hostserv/src/approve.rs @@ -34,6 +34,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: return; } }; + // The forbidden list may have grown since the request was filed; a user's + // request must still obey it (an operator's own SET is a deliberate override). + if db.vhost_is_forbidden(&host) { + ctx.notice(me, from.uid, format!("Can't activate: \x02{host}\x02 is on the forbidden list.")); + return; + } match db.set_vhost(account, &host, from.nick, None) { Ok(()) => { for uid in net.uids_logged_into(account) { diff --git a/src/engine/tests.rs b/src/engine/tests.rs index fe7d4b5..322c3d5 100644 --- a/src/engine/tests.rs +++ b/src/engine/tests.rs @@ -2324,6 +2324,13 @@ assert!(says(&hs(&mut e, "000AAAAAB", "WAITING"), "No vhost requests"), "no request left after reject"); // A non-operator can't approve. assert!(says(&hs(&mut e, "000AAAAAV", "WAITING"), "Access denied"), "oper-gated"); + + // A host forbidden AFTER its request is filed must not slip through ACTIVATE. + hs(&mut e, "000AAAAAB", "REQUEST evilcorp.example"); + hs(&mut e, "000AAAAAB", "FORBID (?i)evilcorp"); + let out = hs(&mut e, "000AAAAAB", "ACTIVATE boss"); + assert!(says(&out, "forbidden list"), "activate refused for a now-forbidden host: {out:?}"); + assert!(!out.iter().any(|a| matches!(a, NetAction::SetHost { .. })), "no vhost applied when forbidden: {out:?}"); } // StatServ reports per-channel activity (#channel) and the global registry