banredirect: +b mask$#chan bounces a banned user to another channel (loop-guarded)
This commit is contained in:
parent
349200d695
commit
1a53c5ae5a
4 changed files with 53 additions and 1 deletions
|
|
@ -117,6 +117,10 @@ amu_target = both
|
||||||
# hidewhois_hide_server = yes # hide 312
|
# hidewhois_hide_server = yes # hide 312
|
||||||
# hidewhois_hide_idle = yes # hide 317
|
# hidewhois_hide_idle = yes # hide 317
|
||||||
# hidewhois_hide_secure = yes # hide 671
|
# hidewhois_hide_secure = yes # hide 671
|
||||||
|
# --- banredirect (m_banredirect): no config needed — it extends ban syntax. A
|
||||||
|
# ban `+b <mask>$<#channel>` bounces a matching user into #channel instead of
|
||||||
|
# refusing them, e.g. /MODE #main +b *!*@*.spammer.net$#quarantine
|
||||||
|
# The redirect fires at most once (never loops).
|
||||||
# --- solvemsg (m_solvemsg): an un-vouched user must answer one arithmetic
|
# --- solvemsg (m_solvemsg): an un-vouched user must answer one arithmetic
|
||||||
# question before their private messages are delivered (opers & logged-in
|
# question before their private messages are delivered (opers & logged-in
|
||||||
# accounts are exempt). Cheap anti-spam-bot gate.
|
# accounts are exempt). Cheap anti-spam-bot gate.
|
||||||
|
|
|
||||||
|
|
@ -560,6 +560,21 @@ impl Server {
|
||||||
// (both honour the g: security-group extban)
|
// (both honour the g: security-group extban)
|
||||||
if self.ban_list_hit(uid, &ch.bans) && !self.ban_list_hit(uid, &ch.excepts) {
|
if self.ban_list_hit(uid, &ch.bans) && !self.ban_list_hit(uid, &ch.excepts) {
|
||||||
if !is_oper {
|
if !is_oper {
|
||||||
|
// banredirect: `+b mask$#chan` bounces the user into #chan (once)
|
||||||
|
if let Some(t) = crate::modules::banredirect::redirect_target(self, uid, &key) {
|
||||||
|
let tl = t.to_ascii_lowercase();
|
||||||
|
if !self.in_redirect && tl != key && !self.is_member(uid, &tl) {
|
||||||
|
self.numeric(
|
||||||
|
uid,
|
||||||
|
ERR_LINKCHANNEL,
|
||||||
|
&format!("{name} {t} :Cannot join channel (+b), redirecting"),
|
||||||
|
);
|
||||||
|
self.in_redirect = true;
|
||||||
|
self.join(uid, &t, None);
|
||||||
|
self.in_redirect = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
self.numeric(
|
self.numeric(
|
||||||
uid,
|
uid,
|
||||||
ERR_BANNEDFROMCHAN,
|
ERR_BANNEDFROMCHAN,
|
||||||
|
|
@ -925,7 +940,8 @@ impl Server {
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
glob_match(&b.mask, &who)
|
// strip any `$#chan` banredirect suffix before matching the mask
|
||||||
|
glob_match(crate::modules::banredirect::mask_part(&b.mask), &who)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
src/modules/banredirect.rs
Normal file
31
src/modules/banredirect.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
//! banredirect — a ban of the form `+b <mask>$<#channel>` bounces a matching,
|
||||||
|
//! banned user into `#channel` instead of refusing them outright. The redirect
|
||||||
|
//! fires at most once, guarded by `Server.in_redirect` (shared with the `+L`
|
||||||
|
//! full-channel redirect), so it can never loop. Reference: InspIRCd's
|
||||||
|
//! `m_banredirect`. Original native Rust.
|
||||||
|
|
||||||
|
use crate::channels::glob_match;
|
||||||
|
use crate::server::Server;
|
||||||
|
use crate::Uid;
|
||||||
|
|
||||||
|
/// The mask part of a ban, without any `$#chan` redirect suffix — used both for
|
||||||
|
/// matching (a redirect ban must still *block*) and for reading the target.
|
||||||
|
pub fn mask_part(mask: &str) -> &str {
|
||||||
|
mask.split('$').next().unwrap_or(mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If `uid` is caught by a redirect ban (`mask$#chan`) in `key` with no matching
|
||||||
|
/// exception, return the target channel. The caller has already established the
|
||||||
|
/// user is banned; this extracts the `$#chan` destination.
|
||||||
|
pub fn redirect_target(s: &Server, uid: Uid, key: &str) -> Option<String> {
|
||||||
|
let ch = s.channels.get(key)?;
|
||||||
|
let who = s.users.get(&uid)?.prefix();
|
||||||
|
// a matching +e exception cancels the ban, hence the redirect
|
||||||
|
if ch.excepts.iter().any(|e| glob_match(mask_part(&e.mask), &who)) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
ch.bans.iter().find_map(|b| {
|
||||||
|
let (mask, redir) = b.mask.split_once('$')?;
|
||||||
|
(redir.starts_with('#') && glob_match(mask, &who)).then(|| redir.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
pub mod account_registration;
|
pub mod account_registration;
|
||||||
pub mod antimixedutf8;
|
pub mod antimixedutf8;
|
||||||
pub mod antirandom;
|
pub mod antirandom;
|
||||||
|
pub mod banredirect;
|
||||||
pub mod blockamsg;
|
pub mod blockamsg;
|
||||||
pub mod channames;
|
pub mod channames;
|
||||||
pub mod channelban;
|
pub mod channelban;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue