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_idle = yes # hide 317
|
||||
# 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
|
||||
# question before their private messages are delivered (opers & logged-in
|
||||
# accounts are exempt). Cheap anti-spam-bot gate.
|
||||
|
|
|
|||
|
|
@ -560,6 +560,21 @@ impl Server {
|
|||
// (both honour the g: security-group extban)
|
||||
if self.ban_list_hit(uid, &ch.bans) && !self.ban_list_hit(uid, &ch.excepts) {
|
||||
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(
|
||||
uid,
|
||||
ERR_BANNEDFROMCHAN,
|
||||
|
|
@ -925,7 +940,8 @@ impl Server {
|
|||
_ => false,
|
||||
}
|
||||
} 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 antimixedutf8;
|
||||
pub mod antirandom;
|
||||
pub mod banredirect;
|
||||
pub mod blockamsg;
|
||||
pub mod channames;
|
||||
pub mod channelban;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue