TBAN timed channel bans (auto-lift on the tick)
This commit is contained in:
parent
835c1cf099
commit
cab53016e8
5 changed files with 92 additions and 1 deletions
|
|
@ -103,6 +103,7 @@ pub struct Ban {
|
||||||
pub mask: String,
|
pub mask: String,
|
||||||
pub setter: String,
|
pub setter: String,
|
||||||
pub ts: u64,
|
pub ts: u64,
|
||||||
|
pub expires: Option<u64>, // TBAN: unix ts to auto-lift at (None = permanent)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// +f message flood: `[*]lines:secs` — kick past `lines` msgs in `secs` (and set
|
/// +f message flood: `[*]lines:secs` — kick past `lines` msgs in `secs` (and set
|
||||||
|
|
@ -348,6 +349,26 @@ impl Server {
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lift any expired TBAN timed bans, announcing `MODE -b` to each channel.
|
||||||
|
/// Called from the background tick.
|
||||||
|
pub fn purge_tbans(&mut self) {
|
||||||
|
let now = now();
|
||||||
|
let mut expired: Vec<(String, String, String)> = Vec::new(); // (key, name, mask)
|
||||||
|
for (key, ch) in &self.channels {
|
||||||
|
for b in &ch.bans {
|
||||||
|
if b.expires.is_some_and(|e| e <= now) {
|
||||||
|
expired.push((key.clone(), ch.name.clone(), b.mask.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (key, name, mask) in expired {
|
||||||
|
if let Some(ch) = self.channels.get_mut(&key) {
|
||||||
|
ch.bans.retain(|b| b.mask != mask);
|
||||||
|
}
|
||||||
|
self.to_channel(&key, &format!(":{} MODE {name} -b {mask}", self.name), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Force `uid` out of `chan` (SAPART / SVSPART enforcement): announce the PART
|
/// Force `uid` out of `chan` (SAPART / SVSPART enforcement): announce the PART
|
||||||
/// to the channel and to links, drop the membership, reap the channel if empty.
|
/// to the channel and to links, drop the membership, reap the channel if empty.
|
||||||
/// No-op if the user isn't a member.
|
/// No-op if the user isn't a member.
|
||||||
|
|
@ -758,6 +779,7 @@ impl Server {
|
||||||
mask,
|
mask,
|
||||||
setter: self.name.clone(),
|
setter: self.name.clone(),
|
||||||
ts: now(),
|
ts: now(),
|
||||||
|
expires: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
//! core_channel — channel membership commands: JOIN, PART, KICK, TOPIC, NAMES.
|
//! core_channel — channel membership commands: JOIN, PART, KICK, TOPIC, NAMES.
|
||||||
|
|
||||||
use crate::channels::{Topic, RANK_HALFOP};
|
use crate::channels::{normalize_ban_mask, Ban, Topic, RANK_HALFOP};
|
||||||
use crate::command::{CmdResult, Command};
|
use crate::command::{CmdResult, Command};
|
||||||
use crate::module::Hook;
|
use crate::module::Hook;
|
||||||
use crate::numeric::*;
|
use crate::numeric::*;
|
||||||
use crate::server::{now, Server};
|
use crate::server::{now, Server};
|
||||||
|
use crate::xline::parse_duration;
|
||||||
use crate::Uid;
|
use crate::Uid;
|
||||||
|
|
||||||
pub fn commands() -> Vec<Box<dyn Command>> {
|
pub fn commands() -> Vec<Box<dyn Command>> {
|
||||||
|
|
@ -19,9 +20,74 @@ pub fn commands() -> Vec<Box<dyn Command>> {
|
||||||
Box::new(Knock),
|
Box::new(Knock),
|
||||||
Box::new(Cycle),
|
Box::new(Cycle),
|
||||||
Box::new(Remove),
|
Box::new(Remove),
|
||||||
|
Box::new(Tban),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TBAN — set a +b ban that lifts itself after a duration (InspIRCd `m_timedbans`).
|
||||||
|
/// `TBAN <#chan> <duration> <mask>`; needs half-op or above. The background tick
|
||||||
|
/// removes it and announces `MODE -b` when it expires.
|
||||||
|
struct Tban;
|
||||||
|
impl Command for Tban {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"TBAN"
|
||||||
|
}
|
||||||
|
fn min_params(&self) -> usize {
|
||||||
|
3
|
||||||
|
}
|
||||||
|
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
||||||
|
let chan = ¶ms[0];
|
||||||
|
let key = chan.to_ascii_lowercase();
|
||||||
|
if !s.channels.contains_key(&key) {
|
||||||
|
s.numeric(uid, ERR_NOSUCHCHANNEL, &format!("{chan} :No such channel"));
|
||||||
|
return CmdResult::Fail;
|
||||||
|
}
|
||||||
|
if s.rank(uid, &key) < RANK_HALFOP {
|
||||||
|
s.numeric(
|
||||||
|
uid,
|
||||||
|
ERR_CHANOPRIVSNEEDED,
|
||||||
|
&format!("{chan} :You're not a channel operator"),
|
||||||
|
);
|
||||||
|
return CmdResult::Fail;
|
||||||
|
}
|
||||||
|
let Some(dur) = parse_duration(¶ms[1]).filter(|&d| d > 0) else {
|
||||||
|
s.fail(
|
||||||
|
uid,
|
||||||
|
"TBAN",
|
||||||
|
"INVALID_DURATION",
|
||||||
|
"TBAN needs a positive duration",
|
||||||
|
);
|
||||||
|
return CmdResult::Fail;
|
||||||
|
};
|
||||||
|
let mask = normalize_ban_mask(¶ms[2]);
|
||||||
|
let (nick, prefix) = {
|
||||||
|
let u = &s.users[&uid];
|
||||||
|
(u.nick.clone(), u.prefix())
|
||||||
|
};
|
||||||
|
if s.channels[&key].bans.iter().any(|b| b.mask == mask) {
|
||||||
|
s.send(
|
||||||
|
uid,
|
||||||
|
format!(
|
||||||
|
":{} NOTICE {nick} :{mask} is already banned on {chan}",
|
||||||
|
s.name
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return CmdResult::Fail;
|
||||||
|
}
|
||||||
|
if let Some(c) = s.channels.get_mut(&key) {
|
||||||
|
c.bans.push(Ban {
|
||||||
|
mask: mask.clone(),
|
||||||
|
setter: nick,
|
||||||
|
ts: now(),
|
||||||
|
expires: Some(now() + dur),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
s.to_channel(&key, &format!(":{prefix} MODE {chan} +b {mask}"), None);
|
||||||
|
s.propagate_from_user(uid, &format!("MODE {chan} +b {mask}"));
|
||||||
|
CmdResult::Ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// KNOCK — ask for an invite to an invite-only channel.
|
/// KNOCK — ask for an invite to an invite-only channel.
|
||||||
struct Knock;
|
struct Knock;
|
||||||
impl Command for Knock {
|
impl Command for Knock {
|
||||||
|
|
|
||||||
|
|
@ -353,6 +353,7 @@ impl Ircd {
|
||||||
fn on_tick(&mut self) {
|
fn on_tick(&mut self) {
|
||||||
self.server.ping_links(); // keepalive on every server link
|
self.server.ping_links(); // keepalive on every server link
|
||||||
self.server.purge_xlines(); // drop expired server bans
|
self.server.purge_xlines(); // drop expired server bans
|
||||||
|
self.server.purge_tbans(); // lift expired timed channel bans (TBAN)
|
||||||
let now = crate::server::now();
|
let now = crate::server::now();
|
||||||
let (to_ping, to_quit) = self.server.idle_check(now);
|
let (to_ping, to_quit) = self.server.idle_check(now);
|
||||||
for uid in to_ping {
|
for uid in to_ping {
|
||||||
|
|
|
||||||
|
|
@ -1131,6 +1131,7 @@ impl Server {
|
||||||
mask,
|
mask,
|
||||||
setter,
|
setter,
|
||||||
ts: now(),
|
ts: now(),
|
||||||
|
expires: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -579,6 +579,7 @@ impl ChanMode for ListMode {
|
||||||
mask: mask.clone(),
|
mask: mask.clone(),
|
||||||
setter,
|
setter,
|
||||||
ts: now(),
|
ts: now(),
|
||||||
|
expires: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Applied::Yes(Some(mask))
|
Applied::Yes(Some(mask))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue