akick: match extbans (account/realname/realmask/unauthed) using the ident, real host and real name the UID carries but was discarding
All checks were successful
CI / check (push) Successful in 4m0s
All checks were successful
CI / check (push) Successful in 4m0s
This commit is contained in:
parent
bc4fada354
commit
426fc694a2
8 changed files with 202 additions and 26 deletions
|
|
@ -2,7 +2,9 @@ use echo_api::Store;
|
|||
use echo_api::{Sender, ServiceCtx};
|
||||
|
||||
// AKICK <#channel> ADD <mask> [reason] | DEL <mask> | LIST
|
||||
// Masks are nick!user@host globs; matching users are banned and kicked on join.
|
||||
// Masks are nick!user@host globs or an extban echo can match: account:<glob>,
|
||||
// realname:<glob>, realmask:<host>+<realname>, or unauthed:<host>. Matching users
|
||||
// are banned and kicked on join.
|
||||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
let Some(&chan) = args.get(1) else {
|
||||
ctx.notice(me, from.uid, "Syntax: AKICK <#channel> ADD <mask> [reason] | DEL <mask> | LIST");
|
||||
|
|
@ -24,6 +26,12 @@ 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 extbans echo has no per-user data to match (country, class, …),
|
||||
// so we never store an akick that can't be enforced.
|
||||
if let echo_api::AkickMask::Other(name) = echo_api::AkickMask::parse(mask) {
|
||||
ctx.notice(me, from.uid, format!("Can't auto-kick by the \x02{name}\x02 extban. Use a host mask or \x02account:\x02 / \x02realname:\x02 / \x02realmask:\x02 / \x02unauthed:\x02."));
|
||||
return;
|
||||
}
|
||||
if !super::require_op(me, from, chan, ctx, db) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,9 +21,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
match net.account_of(&uid).and_then(|a| info.join_mode(a)) {
|
||||
Some(m) => ctx.channel_mode(me, chan, &status_mode(m, &uid)),
|
||||
None => {
|
||||
let nick = net.nick_of(&uid).unwrap_or("*");
|
||||
let host = net.host_of(&uid).unwrap_or("*");
|
||||
if let Some(k) = info.akick_match(&format!("{nick}!*@{host}")) {
|
||||
if let Some(k) = net.ban_target(&uid).and_then(|t| info.akick_match(&t)) {
|
||||
ctx.channel_mode(me, chan, &format!("+b {}", k.mask));
|
||||
let reason = if k.reason.is_empty() { "You are banned from this channel." } else { &k.reason };
|
||||
ctx.kick(me, chan, &uid, reason);
|
||||
|
|
|
|||
|
|
@ -106,12 +106,20 @@ impl Protocol for InspIrcd {
|
|||
// <ip> <signon> … — take the displayed host for ban masks and the ip
|
||||
// (index 7) for session limiting.
|
||||
"UID" => {
|
||||
// insp4: uuid nickts nick realhost disphost realuser dispuser ip signonts +modes[ params] :gecos
|
||||
let a: Vec<&str> = tokens.collect();
|
||||
match (a.first(), a.get(2)) {
|
||||
(Some(uid), Some(nick)) => {
|
||||
let realhost = a.get(3).unwrap_or(&"").to_string();
|
||||
let host = a.get(4).unwrap_or(&"").to_string();
|
||||
let ident = a.get(6).unwrap_or(&"").to_string();
|
||||
let ip = a.get(7).unwrap_or(&"").to_string();
|
||||
vec![NetEvent::UserConnect { uid: uid.to_string(), nick: nick.to_string(), host, ip }]
|
||||
// gecos is the trailing param (has spaces) — take it whole.
|
||||
let gecos = rest.split_once(" :").map_or("", |(_, g)| g).to_string();
|
||||
vec![
|
||||
NetEvent::UserConnect { uid: uid.to_string(), nick: nick.to_string(), host, ip },
|
||||
NetEvent::UserAttrs { uid: uid.to_string(), ident, realhost, gecos },
|
||||
]
|
||||
}
|
||||
_ => vec![],
|
||||
}
|
||||
|
|
@ -558,12 +566,17 @@ mod tests {
|
|||
assert!(matches!(ev.as_slice(), [NetEvent::ServerSplit { server }] if server == "0IR"), "{ev:?}");
|
||||
}
|
||||
|
||||
// A UID burst introduces the user under their current nick.
|
||||
// A UID burst introduces the user and, alongside, the identity fields the
|
||||
// UID carries that UserConnect omits (ident, real host, gecos).
|
||||
#[test]
|
||||
fn parses_uid_burst() {
|
||||
let ev = proto().parse(":0IR UID 0IRAAAAAB 1783833000 alice host host user user 0.0.0.0 1783833000 +i :real");
|
||||
let ev = proto().parse(":0IR UID 0IRAAAAAB 1783833000 alice real.host disp.host ruser duser 1.2.3.4 1783833000 +i :Real Name");
|
||||
assert!(
|
||||
matches!(ev.as_slice(), [NetEvent::UserConnect { uid, nick, .. }] if uid == "0IRAAAAAB" && nick == "alice"),
|
||||
matches!(ev.as_slice(), [
|
||||
NetEvent::UserConnect { uid, nick, host, ip },
|
||||
NetEvent::UserAttrs { ident, realhost, gecos, .. },
|
||||
] if uid == "0IRAAAAAB" && nick == "alice" && host == "disp.host" && ip == "1.2.3.4"
|
||||
&& ident == "duser" && realhost == "real.host" && gecos == "Real Name"),
|
||||
"{ev:?}"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue