xline: broadcast the XLINE notice on remove and expire too, so it covers every x-line's whole lifecycle
This commit is contained in:
parent
6301b64509
commit
3ad67c6b52
5 changed files with 69 additions and 30 deletions
|
|
@ -726,20 +726,16 @@ fn do_xline(s: &mut Server, uid: Uid, params: &[String], kind: XKind) -> CmdResu
|
||||||
.map(|u| u.nick.clone())
|
.map(|u| u.nick.clone())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
if params.len() < 2 {
|
if params.len() < 2 {
|
||||||
let word = if s.remove_xline(kind, &mask) {
|
// A successful removal is announced by remove_xline (snomask +x), same as the
|
||||||
|
// add; only tell the oper directly when there was nothing to remove.
|
||||||
|
if s.remove_xline(kind, &mask, &nick) {
|
||||||
s.propagate_delline(kind.tag(), &mask);
|
s.propagate_delline(kind.tag(), &mask);
|
||||||
"removed"
|
|
||||||
} else {
|
} else {
|
||||||
"not found"
|
s.send(
|
||||||
};
|
uid,
|
||||||
s.send(
|
format!(":{} NOTICE {nick} :{}-line not found: {mask}", s.name, kind.tag()),
|
||||||
uid,
|
);
|
||||||
format!(
|
}
|
||||||
":{} NOTICE {nick} :{}-line {word}: {mask}",
|
|
||||||
s.name,
|
|
||||||
kind.tag()
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return CmdResult::Ok;
|
return CmdResult::Ok;
|
||||||
}
|
}
|
||||||
// <mask> <duration> :<reason>; tolerate the durationless form by taking an
|
// <mask> <duration> :<reason>; tolerate the durationless form by taking an
|
||||||
|
|
@ -879,16 +875,14 @@ impl Command for Rline {
|
||||||
.map(|u| u.nick.clone())
|
.map(|u| u.nick.clone())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
if params.len() < 2 {
|
if params.len() < 2 {
|
||||||
let word = if s.remove_xline(XKind::Rline, &pattern) {
|
if s.remove_xline(XKind::Rline, &pattern, &nick) {
|
||||||
s.propagate_delline("R", &pattern);
|
s.propagate_delline("R", &pattern);
|
||||||
"removed"
|
|
||||||
} else {
|
} else {
|
||||||
"not found"
|
s.send(
|
||||||
};
|
uid,
|
||||||
s.send(
|
format!(":{} NOTICE {nick} :R-line not found: {pattern}", s.name),
|
||||||
uid,
|
);
|
||||||
format!(":{} NOTICE {nick} :R-line {word}: {pattern}", s.name),
|
}
|
||||||
);
|
|
||||||
return CmdResult::Ok;
|
return CmdResult::Ok;
|
||||||
}
|
}
|
||||||
if let Err(e) = crate::regex::Regex::new(&pattern) {
|
if let Err(e) = crate::regex::Regex::new(&pattern) {
|
||||||
|
|
|
||||||
|
|
@ -659,7 +659,8 @@ impl Server {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if msg.params.len() == 1 {
|
if msg.params.len() == 1 {
|
||||||
self.remove_xline(crate::xline::XKind::Svshold, &nick);
|
let setter = self.link_setter(msg);
|
||||||
|
self.remove_xline(crate::xline::XKind::Svshold, &nick, &setter);
|
||||||
} else if msg.params.len() >= 3 {
|
} else if msg.params.len() >= 3 {
|
||||||
let Some(dur) = crate::xline::parse_duration(&msg.params[1]) else {
|
let Some(dur) = crate::xline::parse_duration(&msg.params[1]) else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -1448,7 +1449,8 @@ impl Server {
|
||||||
let Some(kind) = crate::xline::XKind::from_tag(&msg.params[0]) else {
|
let Some(kind) = crate::xline::XKind::from_tag(&msg.params[0]) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if self.remove_xline(kind, &msg.params[1]) {
|
let remover = self.link_setter(msg);
|
||||||
|
if self.remove_xline(kind, &msg.params[1], &remover) {
|
||||||
self.propagate(&msg.to_wire(), Some(via));
|
self.propagate(&msg.to_wire(), Some(via));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,8 @@ pub fn handle(s: &mut Server, action: &str, params: &str) -> Result<String, RpcE
|
||||||
let mask = json::get_str(params, "mask")
|
let mask = json::get_str(params, "mask")
|
||||||
.or_else(|| json::get_str(params, "name"))
|
.or_else(|| json::get_str(params, "name"))
|
||||||
.ok_or_else(|| RpcError::invalid_params("missing 'mask'"))?;
|
.ok_or_else(|| RpcError::invalid_params("missing 'mask'"))?;
|
||||||
let removed = s.remove_xline(kind, &mask);
|
let setter = json::get_str(params, "setter").unwrap_or_else(|| "RPC".into());
|
||||||
|
let removed = s.remove_xline(kind, &mask, &setter);
|
||||||
if removed {
|
if removed {
|
||||||
Ok(obj(&[("result", "true".into())]))
|
Ok(obj(&[("result", "true".into())]))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1618,6 +1618,36 @@ mod tests {
|
||||||
assert_eq!(super::long_date(86400 * 31), "Sun 01 Feb 1970 00:00:00");
|
assert_eq!(super::long_date(86400 * 31), "Sun 01 Feb 1970 00:00:00");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn xline_add_remove_expire_all_notify() {
|
||||||
|
let mut s = srv();
|
||||||
|
s.name = "irc.test".to_string();
|
||||||
|
s.conf_path = std::env::temp_dir().join("echo-xline-notify-test").display().to_string();
|
||||||
|
let orx = add_user(&mut s, 1, "op");
|
||||||
|
if let Some(u) = s.users.get_mut(&1) {
|
||||||
|
u.flags.oper = true;
|
||||||
|
u.flags.snomask = true;
|
||||||
|
u.flags.snomask_cats = "x".to_string();
|
||||||
|
}
|
||||||
|
// permanent G-line, then removed
|
||||||
|
s.add_xline(crate::xline::XKind::Gline, "*@bad.example", 0, "op", "spam");
|
||||||
|
assert!(s.remove_xline(crate::xline::XKind::Gline, "*@bad.example", "op"));
|
||||||
|
assert!(!s.remove_xline(crate::xline::XKind::Gline, "*@bad.example", "op")); // gone: no re-announce
|
||||||
|
// a timed Z-line whose expiry is forced into the past, then purged
|
||||||
|
s.add_xline(crate::xline::XKind::Zline, "192.0.2.5", 3600, "op", "temp");
|
||||||
|
for x in s.xlines.iter_mut() {
|
||||||
|
if x.mask == "192.0.2.5" {
|
||||||
|
x.expires = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.purge_xlines();
|
||||||
|
let joined: String =
|
||||||
|
std::iter::from_fn(|| orx.try_recv().ok()).collect::<Vec<_>>().join("\n");
|
||||||
|
assert!(joined.contains("XLINE: op added a permanent G-line on *@bad.example: spam"), "add: {joined}");
|
||||||
|
assert!(joined.contains("XLINE: op removed a G-line on *@bad.example"), "remove: {joined}");
|
||||||
|
assert!(joined.contains("XLINE: Z-line on 192.0.2.5 expired"), "expire: {joined}");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dnsbl_hit_emits_expected_snotices() {
|
fn dnsbl_hit_emits_expected_snotices() {
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
|
|
|
||||||
26
src/xline.rs
26
src/xline.rs
|
|
@ -294,8 +294,9 @@ impl Server {
|
||||||
self.enforce_xlines();
|
self.enforce_xlines();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove an x-line by kind + mask; returns whether one was found.
|
/// Remove an x-line by kind + mask; announces it (snomask +x, like the add) and
|
||||||
pub fn remove_xline(&mut self, kind: XKind, mask: &str) -> bool {
|
/// returns whether one was found. `remover` is who took it off.
|
||||||
|
pub fn remove_xline(&mut self, kind: XKind, mask: &str, remover: &str) -> bool {
|
||||||
let before = self.xlines.len();
|
let before = self.xlines.len();
|
||||||
// case-insensitive: nick/host/channel masks match case-insensitively when
|
// case-insensitive: nick/host/channel masks match case-insensitively when
|
||||||
// enforced, so removal must too (e.g. remove `CBAN #foo` for a `#Foo` ban).
|
// enforced, so removal must too (e.g. remove `CBAN #foo` for a `#Foo` ban).
|
||||||
|
|
@ -303,6 +304,7 @@ impl Server {
|
||||||
.retain(|x| !(x.kind == kind && x.mask.eq_ignore_ascii_case(mask)));
|
.retain(|x| !(x.kind == kind && x.mask.eq_ignore_ascii_case(mask)));
|
||||||
let removed = self.xlines.len() < before;
|
let removed = self.xlines.len() < before;
|
||||||
if removed {
|
if removed {
|
||||||
|
self.snotice_c('x', &format!("XLINE: {remover} removed a {}-line on {mask}", kind.tag()));
|
||||||
self.save_xlines();
|
self.save_xlines();
|
||||||
}
|
}
|
||||||
removed
|
removed
|
||||||
|
|
@ -335,14 +337,24 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drop expired x-lines (called on the background tick).
|
/// Drop expired x-lines (called on the background tick), announcing each one
|
||||||
|
/// (snomask +x) so the XLINE notices cover a ban's whole life: add → expire.
|
||||||
pub fn purge_xlines(&mut self) {
|
pub fn purge_xlines(&mut self) {
|
||||||
let n = now();
|
let n = now();
|
||||||
let before = self.xlines.len();
|
let expired: Vec<(XKind, String)> = self
|
||||||
self.xlines.retain(|x| x.expires == 0 || x.expires > n);
|
.xlines
|
||||||
if self.xlines.len() != before {
|
.iter()
|
||||||
self.save_xlines(); // an expiry changed the set — persist it
|
.filter(|x| x.expires != 0 && x.expires <= n)
|
||||||
|
.map(|x| (x.kind, x.mask.clone()))
|
||||||
|
.collect();
|
||||||
|
if expired.is_empty() {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
self.xlines.retain(|x| x.expires == 0 || x.expires > n);
|
||||||
|
for (kind, mask) in &expired {
|
||||||
|
self.snotice_c('x', &format!("XLINE: {}-line on {mask} expired", kind.tag()));
|
||||||
|
}
|
||||||
|
self.save_xlines(); // an expiry changed the set — persist it
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Path of the on-disk x-line db (beside the config file).
|
/// Path of the on-disk x-line db (beside the config file).
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue