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
|
|
@ -625,9 +625,9 @@ impl ChannelInfo {
|
|||
.is_some_and(|a| echo_api::level_caps(&a.level).op)
|
||||
}
|
||||
|
||||
/// The matching auto-kick entry for `hostmask` (nick!user@host), if any.
|
||||
pub fn akick_match(&self, hostmask: &str) -> Option<&ChanAkick> {
|
||||
self.akick.iter().find(|k| glob_match(&k.mask, hostmask))
|
||||
/// The auto-kick entry matching a live user (plain host mask or extban), if any.
|
||||
pub fn akick_match(&self, target: &echo_api::BanTarget) -> Option<&ChanAkick> {
|
||||
self.akick.iter().find(|k| echo_api::akick_matches(&k.mask, target))
|
||||
}
|
||||
|
||||
/// The mode string services keep applied: +r plus the lock.
|
||||
|
|
|
|||
|
|
@ -81,12 +81,18 @@
|
|||
let mut db = Db::open(&tmp("akick"), "N1");
|
||||
db.register_channel("#c", "founder").unwrap();
|
||||
db.akick_add("#c", "*!*@bad.host", "spam").unwrap();
|
||||
// Also an account extban (the evasion-proof kind) and a realname extban.
|
||||
db.akick_add("#c", "account:baddie", "gone").unwrap();
|
||||
db.akick_add("#c", "realname:*viagra*", "spam").unwrap();
|
||||
let info = db.channel("#c").unwrap();
|
||||
assert!(info.akick_match("evil!~e@bad.host").is_some());
|
||||
assert!(info.akick_match("good!~g@ok.host").is_none());
|
||||
let t = |nick, ident, host, gecos, account| echo_api::BanTarget { nick, ident, host, realhost: host, ip: "0.0.0.0", gecos, account };
|
||||
assert!(info.akick_match(&t("evil", "~e", "bad.host", "", None)).is_some(), "host mask");
|
||||
assert!(info.akick_match(&t("good", "~g", "ok.host", "", None)).is_none());
|
||||
assert!(info.akick_match(&t("x", "y", "ok.host", "", Some("baddie"))).is_some(), "account extban");
|
||||
assert!(info.akick_match(&t("x", "y", "ok.host", "cheap VIAGRA now", None)).is_some(), "realname extban");
|
||||
assert!(info.akick_match(&t("x", "y", "ok.host", "hello", None)).is_none());
|
||||
assert!(db.akick_del("#c", "*!*@bad.host").unwrap());
|
||||
assert!(!db.akick_del("#c", "*!*@bad.host").unwrap());
|
||||
assert!(db.channel("#c").unwrap().akick.is_empty());
|
||||
}
|
||||
|
||||
// The auto-join list adds, updates a key in place, removes case-insensitively,
|
||||
|
|
|
|||
|
|
@ -1078,6 +1078,10 @@ impl Engine {
|
|||
Vec::new()
|
||||
}
|
||||
}
|
||||
NetEvent::UserAttrs { uid, ident, realhost, gecos } => {
|
||||
self.network.set_user_attrs(&uid, ident, realhost, gecos);
|
||||
Vec::new()
|
||||
}
|
||||
NetEvent::UserConnect { uid, nick, host, ip } => {
|
||||
let arriving_nick = nick.clone();
|
||||
self.network.user_connect(uid.clone(), nick, host, ip.clone());
|
||||
|
|
@ -1218,13 +1222,17 @@ impl Engine {
|
|||
}
|
||||
// No access: an auto-kick match is banned and kicked, else greeted.
|
||||
None => {
|
||||
let nick = self.network.nick_of(&uid).unwrap_or("*").to_string();
|
||||
let host = self.network.host_of(&uid).unwrap_or("*").to_string();
|
||||
let hostmask = format!("{nick}!*@{host}");
|
||||
let akick = self.db.channel(&channel).and_then(|c| c.akick_match(&hostmask)).map(|k| {
|
||||
let reason = if k.reason.is_empty() { "You are banned from this channel.".to_string() } else { k.reason.clone() };
|
||||
(k.mask.clone(), reason)
|
||||
});
|
||||
// Match the akick list against the user's full identity —
|
||||
// host and the extbans (account/realname/…) the ircd gave us.
|
||||
let akick = {
|
||||
let target = self.network.ban_target(&uid);
|
||||
target.as_ref().and_then(|t| {
|
||||
self.db.channel(&channel).and_then(|c| c.akick_match(t)).map(|k| {
|
||||
let reason = if k.reason.is_empty() { "You are banned from this channel.".to_string() } else { k.reason.clone() };
|
||||
(k.mask.clone(), reason)
|
||||
})
|
||||
})
|
||||
};
|
||||
match akick {
|
||||
Some((mask, reason)) => {
|
||||
self.network.channel_part(&channel, &uid);
|
||||
|
|
|
|||
|
|
@ -80,8 +80,11 @@ fn incident_code(seq: u64) -> String {
|
|||
pub struct User {
|
||||
pub uid: String,
|
||||
pub nick: String,
|
||||
pub host: String,
|
||||
pub ident: String, // user@ — from the UID (was discarded); empty until UserAttrs
|
||||
pub host: String, // the displayed host
|
||||
pub realhost: String, // the real host, for host bans against it
|
||||
pub ip: String,
|
||||
pub gecos: String, // real name, for realname / realmask extbans
|
||||
}
|
||||
|
||||
// A channel's live membership, ops, and current key (+k), tracked from the burst.
|
||||
|
|
@ -129,7 +132,17 @@ impl Network {
|
|||
if !ip.is_empty() {
|
||||
*self.sessions.entry(ip.clone()).or_insert(0) += 1;
|
||||
}
|
||||
self.users.insert(uid.clone(), User { uid, nick, host, ip });
|
||||
self.users.insert(uid.clone(), User { uid, nick, ident: String::new(), host, realhost: String::new(), ip, gecos: String::new() });
|
||||
}
|
||||
|
||||
// Fill in the rest of a user's identity (ident, real host, real name), which
|
||||
// the UID carries but UserConnect doesn't — needed for extban matching.
|
||||
pub fn set_user_attrs(&mut self, uid: &str, ident: String, realhost: String, gecos: String) {
|
||||
if let Some(u) = self.users.get_mut(uid) {
|
||||
u.ident = ident;
|
||||
u.realhost = realhost;
|
||||
u.gecos = gecos;
|
||||
}
|
||||
}
|
||||
|
||||
// Mirror the engine's declarative config operators, so services can enumerate
|
||||
|
|
@ -202,6 +215,21 @@ impl Network {
|
|||
self.users.get(uid).map(|u| u.host.as_str())
|
||||
}
|
||||
|
||||
// A user's identity for extban / ban matching (nick, ident, hosts, ip, gecos,
|
||||
// account). `None` if we don't know the uid.
|
||||
pub fn ban_target<'a>(&'a self, uid: &str) -> Option<echo_api::BanTarget<'a>> {
|
||||
let u = self.users.get(uid)?;
|
||||
Some(echo_api::BanTarget {
|
||||
nick: &u.nick,
|
||||
ident: &u.ident,
|
||||
host: &u.host,
|
||||
realhost: &u.realhost,
|
||||
ip: &u.ip,
|
||||
gecos: &u.gecos,
|
||||
account: self.accounts.get(uid).map(String::as_str),
|
||||
})
|
||||
}
|
||||
|
||||
// Every known user whose uid carries `sid` as its prefix — i.e. those behind
|
||||
// a server, used to forget them all when it splits (SQUIT).
|
||||
pub fn uids_on_server(&self, sid: &str) -> Vec<String> {
|
||||
|
|
@ -483,6 +511,9 @@ impl NetView for Network {
|
|||
fn nick_of(&self, uid: &str) -> Option<&str> {
|
||||
Network::nick_of(self, uid)
|
||||
}
|
||||
fn ban_target(&self, uid: &str) -> Option<echo_api::BanTarget<'_>> {
|
||||
Network::ban_target(self, uid)
|
||||
}
|
||||
fn host_of(&self, uid: &str) -> Option<&str> {
|
||||
Network::host_of(self, uid)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue