extban: learn the ircd live set from CAPAB EXTBANS and validate AKICK/MODE against what it actually offers
This commit is contained in:
parent
da94142bad
commit
c990eb1e1e
8 changed files with 95 additions and 1 deletions
|
|
@ -35,6 +35,10 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
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) => {
|
||||
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) => {
|
||||
ctx.notice(me, from.uid, format!("The \x02{}\x02 extban isn't enabled on this network.", eb.name));
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
for mask in ban_masks(&args[2..]) {
|
||||
let core = mask.strip_prefix('!').unwrap_or(mask); // extbans may be inverted
|
||||
if let echo_api::AkickMask::Ext(eb, _) = echo_api::AkickMask::parse(core) {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -311,6 +311,20 @@ impl Protocol for InspIrcd {
|
|||
_ => vec![NetEvent::Unknown { line: line.to_string() }],
|
||||
}
|
||||
}
|
||||
// CAPAB EXTBANS :matching:account=R acting:mute=m … — the ircd's live
|
||||
// extban set. Other CAPAB subcommands (MODULES, CHANMODES, …) are noise.
|
||||
"CAPAB" => {
|
||||
if tokens.next().is_some_and(|s| s.eq_ignore_ascii_case("EXTBANS")) {
|
||||
let entries: Vec<_> = trailing(rest).split_whitespace().filter_map(parse_extban_cap).collect();
|
||||
if entries.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
vec![NetEvent::ExtbanRegistry { entries }]
|
||||
}
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
_ => vec![NetEvent::Unknown { line: line.to_string() }],
|
||||
}
|
||||
}
|
||||
|
|
@ -525,6 +539,22 @@ fn scan_status(modes: &str, params: &[&str], want: char) -> Vec<(bool, String)>
|
|||
}
|
||||
|
||||
// The IRC "trailing" argument: everything after the first " :", else the last word.
|
||||
// Parse one `CAPAB EXTBANS` token — "<matching|acting>:<name>[=<letter>]" — into
|
||||
// an ExtbanCap. Anything not shaped like that is skipped.
|
||||
fn parse_extban_cap(token: &str) -> Option<echo_api::ExtbanCap> {
|
||||
let (ty, spec) = token.split_once(':')?;
|
||||
let acting = match ty {
|
||||
"acting" => true,
|
||||
"matching" => false,
|
||||
_ => return None,
|
||||
};
|
||||
let (name, letter) = match spec.split_once('=') {
|
||||
Some((n, l)) => (n, l.chars().next()),
|
||||
None => (spec, None),
|
||||
};
|
||||
(!name.is_empty()).then(|| echo_api::ExtbanCap { name: name.to_string(), letter, acting })
|
||||
}
|
||||
|
||||
fn trailing(rest: &str) -> String {
|
||||
match rest.find(" :") {
|
||||
Some(i) => rest[i + 2..].to_string(),
|
||||
|
|
@ -576,6 +606,20 @@ mod tests {
|
|||
assert!(p.parse(":42S METADATA 0IRAAAAAB ssl_cert :VTrSe aabbccdd").is_empty(), "our own echo is ignored");
|
||||
}
|
||||
|
||||
// CAPAB EXTBANS surfaces the ircd's live extban set: name, optional letter,
|
||||
// and matching-vs-acting. Malformed tokens are skipped; other CAPAB lines don't.
|
||||
#[test]
|
||||
fn parses_capab_extbans() {
|
||||
let mut p = proto();
|
||||
let ev = p.parse("CAPAB EXTBANS :matching:account=R acting:mute=m matching:noletter garbage");
|
||||
let [NetEvent::ExtbanRegistry { entries }] = ev.as_slice() else { panic!("{ev:?}") };
|
||||
assert_eq!(entries.len(), 3, "garbage token skipped");
|
||||
assert_eq!(entries[0], echo_api::ExtbanCap { name: "account".into(), letter: Some('R'), acting: false });
|
||||
assert_eq!(entries[1], echo_api::ExtbanCap { name: "mute".into(), letter: Some('m'), acting: true });
|
||||
assert_eq!(entries[2], echo_api::ExtbanCap { name: "noletter".into(), letter: None, acting: false });
|
||||
assert!(p.parse("CAPAB CHANMODES :ban=b").is_empty(), "other CAPAB subcommands ignored");
|
||||
}
|
||||
|
||||
// Both SERVER forms surface a ServerInfo (SID -> name) for the `server` extban:
|
||||
// the uplink handshake (name password sid) and a sourced downstream link (name sid).
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue