oper override: bypass +i/+k/+b/+z/+R/+l/+J on JOIN with a snotice
This commit is contained in:
parent
f022ea5a42
commit
ef219bc98f
1 changed files with 78 additions and 44 deletions
|
|
@ -411,8 +411,12 @@ impl Server {
|
||||||
{
|
{
|
||||||
return; // unknown user, or already joined
|
return; // unknown user, or already joined
|
||||||
}
|
}
|
||||||
|
// IRC operators override the join restrictions below (m_override); each
|
||||||
|
// bypass sets `overrode`, snoticed once the join succeeds (accountability).
|
||||||
|
let is_oper = self.users.get(&uid).map(|u| u.flags.oper).unwrap_or(false);
|
||||||
|
let mut overrode = false;
|
||||||
// CBAN — a forbidden channel name (opers bypass)
|
// CBAN — a forbidden channel name (opers bypass)
|
||||||
if !self.users.get(&uid).map(|u| u.flags.oper).unwrap_or(false) {
|
if !is_oper {
|
||||||
if let Some(reason) = self.matched_cban(&key) {
|
if let Some(reason) = self.matched_cban(&key) {
|
||||||
self.numeric(
|
self.numeric(
|
||||||
uid,
|
uid,
|
||||||
|
|
@ -422,10 +426,11 @@ impl Server {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// an existing channel can refuse the join (+k / +b / +i / +l)
|
// an existing channel can refuse the join (+k / +b / +i / +z / +R / +J)
|
||||||
if let Some(ch) = self.channels.get(&key) {
|
if let Some(ch) = self.channels.get(&key) {
|
||||||
if let Some(k) = &ch.modes.key {
|
if let Some(k) = &ch.modes.key {
|
||||||
if key_arg != Some(k.as_str()) {
|
if key_arg != Some(k.as_str()) {
|
||||||
|
if !is_oper {
|
||||||
self.numeric(
|
self.numeric(
|
||||||
uid,
|
uid,
|
||||||
ERR_BADCHANNELKEY,
|
ERR_BADCHANNELKEY,
|
||||||
|
|
@ -433,12 +438,15 @@ impl Server {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
overrode = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// +b — bans block even an invited user, unless a +e exception matches
|
// +b — bans block even an invited user, unless a +e exception matches
|
||||||
let mask = self.users.get(&uid).map(|u| u.prefix()).unwrap_or_default();
|
let mask = self.users.get(&uid).map(|u| u.prefix()).unwrap_or_default();
|
||||||
if ch.bans.iter().any(|b| glob_match(&b.mask, &mask))
|
if ch.bans.iter().any(|b| glob_match(&b.mask, &mask))
|
||||||
&& !ch.excepts.iter().any(|e| glob_match(&e.mask, &mask))
|
&& !ch.excepts.iter().any(|e| glob_match(&e.mask, &mask))
|
||||||
{
|
{
|
||||||
|
if !is_oper {
|
||||||
self.numeric(
|
self.numeric(
|
||||||
uid,
|
uid,
|
||||||
ERR_BANNEDFROMCHAN,
|
ERR_BANNEDFROMCHAN,
|
||||||
|
|
@ -446,11 +454,14 @@ impl Server {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
overrode = true;
|
||||||
|
}
|
||||||
// +i — unless invited or matched by a +I invite exception
|
// +i — unless invited or matched by a +I invite exception
|
||||||
if ch.modes.invite_only
|
if ch.modes.invite_only
|
||||||
&& !ch.invites.contains(&uid)
|
&& !ch.invites.contains(&uid)
|
||||||
&& !ch.invex.iter().any(|e| glob_match(&e.mask, &mask))
|
&& !ch.invex.iter().any(|e| glob_match(&e.mask, &mask))
|
||||||
{
|
{
|
||||||
|
if !is_oper {
|
||||||
self.numeric(
|
self.numeric(
|
||||||
uid,
|
uid,
|
||||||
ERR_INVITEONLYCHAN,
|
ERR_INVITEONLYCHAN,
|
||||||
|
|
@ -458,8 +469,11 @@ impl Server {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
overrode = true;
|
||||||
|
}
|
||||||
// +z — TLS-connected users only
|
// +z — TLS-connected users only
|
||||||
if ch.modes.secure_only && !self.users.get(&uid).map(|u| u.secure).unwrap_or(false) {
|
if ch.modes.secure_only && !self.users.get(&uid).map(|u| u.secure).unwrap_or(false) {
|
||||||
|
if !is_oper {
|
||||||
self.numeric(
|
self.numeric(
|
||||||
uid,
|
uid,
|
||||||
ERR_SECUREONLYCHAN,
|
ERR_SECUREONLYCHAN,
|
||||||
|
|
@ -467,8 +481,10 @@ impl Server {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// +O — IRC operators only
|
overrode = true;
|
||||||
if ch.modes.oper_only && !self.users.get(&uid).map(|u| u.flags.oper).unwrap_or(false) {
|
}
|
||||||
|
// +O — IRC operators only (opers are allowed by definition)
|
||||||
|
if ch.modes.oper_only && !is_oper {
|
||||||
self.numeric(
|
self.numeric(
|
||||||
uid,
|
uid,
|
||||||
ERR_CANTJOINOPERSONLY,
|
ERR_CANTJOINOPERSONLY,
|
||||||
|
|
@ -484,6 +500,7 @@ impl Server {
|
||||||
.map(|u| u.account.is_none())
|
.map(|u| u.account.is_none())
|
||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
{
|
{
|
||||||
|
if !is_oper {
|
||||||
self.numeric(
|
self.numeric(
|
||||||
uid,
|
uid,
|
||||||
ERR_NEEDREGGEDNICK,
|
ERR_NEEDREGGEDNICK,
|
||||||
|
|
@ -491,25 +508,34 @@ impl Server {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
overrode = true;
|
||||||
|
}
|
||||||
// +J <secs> — can't rejoin within N seconds of being kicked
|
// +J <secs> — can't rejoin within N seconds of being kicked
|
||||||
if let Some(secs) = ch.modes.kicknorejoin {
|
if let Some(secs) = ch.modes.kicknorejoin {
|
||||||
if let Some(&kt) = ch.recent_kicks.get(&uid) {
|
if let Some(&kt) = ch.recent_kicks.get(&uid) {
|
||||||
if now().saturating_sub(kt) < secs as u64 {
|
if now().saturating_sub(kt) < secs as u64 {
|
||||||
|
if !is_oper {
|
||||||
self.numeric(
|
self.numeric(
|
||||||
uid,
|
uid,
|
||||||
ERR_DELAYREJOIN,
|
ERR_DELAYREJOIN,
|
||||||
&format!("{name} :You must wait {secs}s after a kick to rejoin (+J)"),
|
&format!(
|
||||||
|
"{name} :You must wait {secs}s after a kick to rejoin (+J)"
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
overrode = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// +l full — with +L redirect, bounce the user to the target instead
|
}
|
||||||
|
// +l full — with +L redirect, bounce the user to the target instead (opers override)
|
||||||
if let Some(ch) = self.channels.get(&key) {
|
if let Some(ch) = self.channels.get(&key) {
|
||||||
let full = ch.modes.limit.is_some_and(|l| ch.members.len() as u32 >= l);
|
let full = ch.modes.limit.is_some_and(|l| ch.members.len() as u32 >= l);
|
||||||
let redirect = ch.modes.redirect.clone();
|
let redirect = ch.modes.redirect.clone();
|
||||||
if full {
|
if full && is_oper {
|
||||||
|
overrode = true;
|
||||||
|
} else if full {
|
||||||
match redirect {
|
match redirect {
|
||||||
Some(t)
|
Some(t)
|
||||||
if !self.in_redirect
|
if !self.in_redirect
|
||||||
|
|
@ -537,8 +563,8 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// +j join flood — once tripped, the channel locks new joins out for 60s
|
// +j join flood — once tripped, the channel locks new joins out for 60s (opers exempt)
|
||||||
if self.channels.contains_key(&key) && self.joinflood_check(&key) {
|
if !is_oper && self.channels.contains_key(&key) && self.joinflood_check(&key) {
|
||||||
self.numeric(
|
self.numeric(
|
||||||
uid,
|
uid,
|
||||||
ERR_UNAVAILRESOURCE,
|
ERR_UNAVAILRESOURCE,
|
||||||
|
|
@ -546,6 +572,14 @@ impl Server {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if overrode {
|
||||||
|
let nick = self
|
||||||
|
.users
|
||||||
|
.get(&uid)
|
||||||
|
.map(|u| u.nick.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
self.snotice(&format!("{nick} used oper override to join {name}"));
|
||||||
|
}
|
||||||
let is_new = !self.channels.contains_key(&key);
|
let is_new = !self.channels.contains_key(&key);
|
||||||
let ch = self
|
let ch = self
|
||||||
.channels
|
.channels
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue