akick: accept a matching extban only the ircd knows (ircd-enforced +b), reject acting extbans with a clear reason
This commit is contained in:
parent
c990eb1e1e
commit
77d09c4bdc
5 changed files with 75 additions and 21 deletions
|
|
@ -1136,6 +1136,18 @@ impl<'a> AkickMask<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Whether echo leans on the ircd to enforce an akick with this `mask` (by setting
|
||||
/// a channel `+b`) because it can't evaluate the mask itself: a passive extban, or
|
||||
/// a matching extban the ircd offers but echo's static table doesn't know. Host
|
||||
/// masks and echo's own matchable extbans return false — echo kicks those on join.
|
||||
pub fn ircd_enforced(mask: &str) -> bool {
|
||||
match AkickMask::parse(mask) {
|
||||
AkickMask::Host(_) => false,
|
||||
AkickMask::Ext(eb, _) => !eb.matchable(),
|
||||
AkickMask::Unknown(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether an AKICK `mask` matches a live user `t` — a plain hostmask (globbed
|
||||
/// against the displayed-host, real-host and IP forms, as InspIRCd's CheckBan
|
||||
/// does) or a matching-extban echo has the data for. Passive/unknown never match.
|
||||
|
|
@ -1560,6 +1572,11 @@ pub trait Store {
|
|||
fn extban_offered(&self, _name: &str) -> bool {
|
||||
true
|
||||
}
|
||||
// Resolve an extban token (name or single letter) the ircd advertised but echo's
|
||||
// static table doesn't know, to its live-registry entry. None if not offered.
|
||||
fn extban_lookup(&self, _token: &str) -> Option<ExtbanCap> {
|
||||
None
|
||||
}
|
||||
fn exists(&self, name: &str) -> bool;
|
||||
fn account(&self, name: &str) -> Option<AccountView>;
|
||||
// Canonical account name for a nick (following a grouping), if registered.
|
||||
|
|
@ -2035,6 +2052,13 @@ mod tests {
|
|||
let anon = BanTarget { account: None, ..t };
|
||||
assert!(akick_matches("unauthed:*!*@h.com", &anon), "not logged in + host matches");
|
||||
assert!(!akick_matches("account:bad", &anon), "no account -> account extban can't match");
|
||||
|
||||
// ircd_enforced: host masks and echo's matchable extbans are echo's job;
|
||||
// passive extbans and ones echo's table doesn't know are the ircd's (a +b).
|
||||
assert!(!ircd_enforced("*!*@host"), "host mask: echo kicks");
|
||||
assert!(!ircd_enforced("account:bad"), "matchable extban: echo kicks");
|
||||
assert!(ircd_enforced("country:US"), "passive extban: ircd enforces");
|
||||
assert!(ircd_enforced("mute:*!*@x"), "extban echo's table lacks: ircd enforces");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -28,22 +28,38 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
ctx.notice(me, from.uid, "Syntax: AKICK <#channel> ADD <mask> [reason]");
|
||||
return;
|
||||
};
|
||||
// Reject something that's neither a host mask nor a known extban, and
|
||||
// any extban the network's `[extban] enabled` config has turned off.
|
||||
// Validate the mask: a host mask always passes; an extban must be one
|
||||
// this ircd offers, be a matching (not acting) kind, and be allowed by
|
||||
// the `[extban] enabled` config. Extbans echo's own table doesn't know
|
||||
// are resolved against the ircd's live set so a new matching extban
|
||||
// still works (the ircd enforces it).
|
||||
match echo_api::AkickMask::parse(mask) {
|
||||
echo_api::AkickMask::Unknown(name) => {
|
||||
ctx.notice(me, from.uid, format!("\x02{name}\x02 isn't a host mask or a known extban."));
|
||||
return;
|
||||
}
|
||||
echo_api::AkickMask::Ext(eb, _) if !db.extban_offered(eb.name) => {
|
||||
echo_api::AkickMask::Host(_) => {}
|
||||
echo_api::AkickMask::Ext(eb, _) => {
|
||||
if !db.extban_offered(eb.name) {
|
||||
ctx.notice(me, from.uid, format!("This network's ircd doesn't offer the \x02{}\x02 extban.", eb.name));
|
||||
return;
|
||||
}
|
||||
echo_api::AkickMask::Ext(eb, _) if !db.extban_enabled(eb.name) => {
|
||||
if !db.extban_enabled(eb.name) {
|
||||
ctx.notice(me, from.uid, format!("The \x02{}\x02 extban isn't enabled on this network.", eb.name));
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
echo_api::AkickMask::Unknown(token) => match db.extban_lookup(token) {
|
||||
None => {
|
||||
ctx.notice(me, from.uid, format!("\x02{token}\x02 isn't a host mask or an extban this network offers."));
|
||||
return;
|
||||
}
|
||||
Some(cap) if cap.acting => {
|
||||
ctx.notice(me, from.uid, format!("\x02{}\x02 is an acting extban; auto-kick needs a matching extban or a host mask.", cap.name));
|
||||
return;
|
||||
}
|
||||
Some(cap) if !db.extban_enabled(&cap.name) => {
|
||||
ctx.notice(me, from.uid, format!("The \x02{}\x02 extban isn't enabled on this network.", cap.name));
|
||||
return;
|
||||
}
|
||||
Some(_) => {}
|
||||
},
|
||||
}
|
||||
if !super::require_op(me, from, chan, ctx, db) {
|
||||
return;
|
||||
|
|
@ -51,10 +67,10 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
let reason = if args.len() > 4 { args[4..].join(" ") } else { "Auto-kicked".to_string() };
|
||||
match db.akick_add(chan, mask, &reason) {
|
||||
Ok(()) => {
|
||||
// Extbans echo can't evaluate itself (country, asn, …) are pushed
|
||||
// to the channel as a ban so the ircd enforces them; the ones echo
|
||||
// matches are applied on join / ENFORCE and need no standing mode.
|
||||
if matches!(echo_api::AkickMask::parse(mask), echo_api::AkickMask::Ext(eb, _) if !eb.matchable()) {
|
||||
// Extbans echo can't evaluate itself are pushed to the channel as
|
||||
// a ban so the ircd enforces them; the ones echo matches are applied
|
||||
// on join / ENFORCE and need no standing mode.
|
||||
if echo_api::ircd_enforced(mask) {
|
||||
ctx.channel_mode(me, chan, &format!("+b {mask}"));
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Added \x02{mask}\x02 to \x02{chan}\x02's auto-kick list."));
|
||||
|
|
@ -73,7 +89,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
match db.akick_del(chan, mask) {
|
||||
Ok(true) => {
|
||||
// Lift the standing ban we set for an ircd-enforced extban.
|
||||
if matches!(echo_api::AkickMask::parse(mask), echo_api::AkickMask::Ext(eb, _) if !eb.matchable()) {
|
||||
if echo_api::ircd_enforced(mask) {
|
||||
ctx.channel_mode(me, chan, &format!("-b {mask}"));
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Removed \x02{mask}\x02 from \x02{chan}\x02's auto-kick list."));
|
||||
|
|
@ -90,7 +106,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
let masks: Vec<String> = db.channel(chan).map_or_else(Vec::new, |info| info.akick.iter().map(|k| k.mask.clone()).collect());
|
||||
for mask in &masks {
|
||||
let _ = db.akick_del(chan, mask);
|
||||
if matches!(echo_api::AkickMask::parse(mask), echo_api::AkickMask::Ext(eb, _) if !eb.matchable()) {
|
||||
if echo_api::ircd_enforced(mask) {
|
||||
ctx.channel_mode(me, chan, &format!("-b {mask}"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
ctx.channel_mode(me, chan, &info.lock_modes());
|
||||
// Re-assert the bans for extbans the ircd enforces (echo can't match them itself).
|
||||
for k in &info.akick {
|
||||
if matches!(echo_api::AkickMask::parse(&k.mask), echo_api::AkickMask::Ext(eb, _) if !eb.matchable()) {
|
||||
if echo_api::ircd_enforced(&k.mask) {
|
||||
ctx.channel_mode(me, chan, &format!("+b {}", k.mask));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,6 +187,17 @@ impl Db {
|
|||
self.live_extbans.as_ref().is_none_or(|set| set.iter().any(|e| e.name.eq_ignore_ascii_case(name)))
|
||||
}
|
||||
|
||||
/// Resolve an extban token (name, case-insensitive; or single letter, case-
|
||||
/// sensitive) against the ircd's live set. For extbans echo's static table lacks.
|
||||
pub fn extban_lookup(&self, token: &str) -> Option<echo_api::ExtbanCap> {
|
||||
let live = self.live_extbans.as_ref()?;
|
||||
let mut chars = token.chars();
|
||||
match (chars.next(), chars.next()) {
|
||||
(Some(c), None) => live.iter().find(|e| e.letter == Some(c)).cloned(),
|
||||
_ => live.iter().find(|e| e.name.eq_ignore_ascii_case(token)).cloned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Display name used in email templates.
|
||||
pub fn email_brand(&self) -> &str {
|
||||
&self.email_brand
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@ impl Store for Db {
|
|||
fn extban_offered(&self, name: &str) -> bool {
|
||||
Db::extban_offered(self, name)
|
||||
}
|
||||
fn extban_lookup(&self, token: &str) -> Option<echo_api::ExtbanCap> {
|
||||
Db::extban_lookup(self, token)
|
||||
}
|
||||
fn email_enabled(&self) -> bool {
|
||||
Db::email_enabled(self)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue