hidemode: hide configured mode changes from members below a rank (per-recipient MODE)
This commit is contained in:
parent
06883ced9f
commit
8ebb106f97
4 changed files with 113 additions and 5 deletions
|
|
@ -136,6 +136,10 @@ amu_target = both
|
||||||
# --- autodrop (m_autodrop): silently drop a not-yet-registered client that sends
|
# --- autodrop (m_autodrop): silently drop a not-yet-registered client that sends
|
||||||
# any of these commands (HTTP scanners blurt GET/POST before NICK/USER):
|
# any of these commands (HTTP scanners blurt GET/POST before NICK/USER):
|
||||||
# autodrop_commands = GET POST HEAD CONNECT PUT DELETE OPTIONS TRACE PATCH
|
# autodrop_commands = GET POST HEAD CONNECT PUT DELETE OPTIONS TRACE PATCH
|
||||||
|
# --- hidemode (m_hidemode): hide changes to a mode from members below a rank
|
||||||
|
# (the setter, opers and links always see it). Repeatable,
|
||||||
|
# `hidemode = <modechar> <rank>` (owner|admin|op|halfop|voice). e.g. hide bans:
|
||||||
|
# hidemode = b op
|
||||||
# --- hidelist (m_hidelist): list modes (+b/+e/+I/…) are viewable by members by
|
# --- hidelist (m_hidelist): list modes (+b/+e/+I/…) are viewable by members by
|
||||||
# default; this restricts a given list to a minimum rank. Repeatable,
|
# default; this restricts a given list to a minimum rank. Repeatable,
|
||||||
# `hidelist = <modechar> <rank>` (rank: owner|admin|op|halfop|voice). Opers see
|
# `hidelist = <modechar> <rank>` (rank: owner|admin|op|halfop|voice). Opers see
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,8 @@ pub fn apply_mode(s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
||||||
let mut applied = String::new();
|
let mut applied = String::new();
|
||||||
let mut last = ' ';
|
let mut last = ' ';
|
||||||
let mut echoed: Vec<String> = Vec::new();
|
let mut echoed: Vec<String> = Vec::new();
|
||||||
|
// (sign, letter, displayed-param) per applied change — for hidemode filtering
|
||||||
|
let mut changes: Vec<(char, char, Option<String>)> = Vec::new();
|
||||||
for c in modestring.chars() {
|
for c in modestring.chars() {
|
||||||
if c == '+' || c == '-' {
|
if c == '+' || c == '-' {
|
||||||
sign = c;
|
sign = c;
|
||||||
|
|
@ -138,6 +140,7 @@ pub fn apply_mode(s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
||||||
};
|
};
|
||||||
if let Applied::Yes(echo) = handler.apply(s, target, &key, uid, adding, param.as_deref()) {
|
if let Applied::Yes(echo) = handler.apply(s, target, &key, uid, adding, param.as_deref()) {
|
||||||
emit(&mut applied, &mut last, sign, c);
|
emit(&mut applied, &mut last, sign, c);
|
||||||
|
changes.push((sign, c, echo.clone()));
|
||||||
if let Some(p) = echo {
|
if let Some(p) = echo {
|
||||||
echoed.push(p);
|
echoed.push(p);
|
||||||
}
|
}
|
||||||
|
|
@ -150,11 +153,21 @@ pub fn apply_mode(s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
||||||
} else {
|
} else {
|
||||||
format!(" {}", echoed.join(" "))
|
format!(" {}", echoed.join(" "))
|
||||||
};
|
};
|
||||||
|
// hidemode: if any changed mode is configured hidden, deliver per-recipient
|
||||||
|
// so members below the required rank don't see it; the setter, opers and
|
||||||
|
// linked servers always get the full line.
|
||||||
|
if changes
|
||||||
|
.iter()
|
||||||
|
.any(|(_, c, _)| crate::modules::hidemode::hidden_rank(s, *c).is_some())
|
||||||
|
{
|
||||||
|
crate::modules::hidemode::broadcast(s, &key, target, uid, &prefix, &changes);
|
||||||
|
} else {
|
||||||
s.to_channel(
|
s.to_channel(
|
||||||
&key,
|
&key,
|
||||||
&format!(":{prefix} MODE {target} {applied}{pstr}"),
|
&format!(":{prefix} MODE {target} {applied}{pstr}"),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
s.propagate_from_user(uid, &format!("MODE {target} {applied}{pstr}"));
|
s.propagate_from_user(uid, &format!("MODE {target} {applied}{pstr}"));
|
||||||
// links
|
// links
|
||||||
}
|
}
|
||||||
|
|
|
||||||
90
src/modules/hidemode.rs
Normal file
90
src/modules/hidemode.rs
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
//! hidemode — hide specific mode changes from channel members below a rank, so
|
||||||
|
//! ordinary users don't see e.g. bans being set/removed. Config, repeatable:
|
||||||
|
//! `hidemode = <modechar> <rank>` (rank: owner|admin|op|halfop|voice). The setter,
|
||||||
|
//! opers and linked servers always see the full change. Reference: InspIRCd's
|
||||||
|
//! `m_hidemode`. Original native Rust.
|
||||||
|
|
||||||
|
use crate::channels::{RANK_ADMIN, RANK_HALFOP, RANK_OP, RANK_OWNER, RANK_VOICE};
|
||||||
|
use crate::server::Server;
|
||||||
|
use crate::Uid;
|
||||||
|
|
||||||
|
fn rank_value(name: &str) -> u8 {
|
||||||
|
match name.to_ascii_lowercase().as_str() {
|
||||||
|
"owner" | "founder" | "q" => RANK_OWNER,
|
||||||
|
"admin" | "protect" | "a" => RANK_ADMIN,
|
||||||
|
"op" | "o" => RANK_OP,
|
||||||
|
"halfop" | "h" => RANK_HALFOP,
|
||||||
|
"voice" | "v" => RANK_VOICE,
|
||||||
|
_ => RANK_OP,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The minimum rank needed to *see* changes to `modechar`, if it's configured
|
||||||
|
/// hidden. `None` ⇒ the mode is visible to everyone (the common case).
|
||||||
|
pub fn hidden_rank(s: &Server, modechar: char) -> Option<u8> {
|
||||||
|
for line in s.conf_all("hidemode") {
|
||||||
|
let mut it = line.split_whitespace();
|
||||||
|
if let (Some(mc), Some(rank)) = (it.next(), it.next()) {
|
||||||
|
if mc.chars().next() == Some(modechar) {
|
||||||
|
return Some(rank_value(rank));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render a subset of changes back into a `<modes> <params…>` pair.
|
||||||
|
fn render(changes: &[&(char, char, Option<String>)]) -> (String, Vec<String>) {
|
||||||
|
let mut modes = String::new();
|
||||||
|
let mut last = ' ';
|
||||||
|
let mut params = Vec::new();
|
||||||
|
for (sign, letter, param) in changes {
|
||||||
|
if *sign != last {
|
||||||
|
modes.push(*sign);
|
||||||
|
last = *sign;
|
||||||
|
}
|
||||||
|
modes.push(*letter);
|
||||||
|
if let Some(p) = param {
|
||||||
|
params.push(p.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(modes, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deliver a MODE change per-recipient, dropping any hidden mode from the line sent
|
||||||
|
/// to members below its required rank. Called from `apply_mode` only when at least
|
||||||
|
/// one changed mode is hidden.
|
||||||
|
pub fn broadcast(
|
||||||
|
s: &Server,
|
||||||
|
key: &str,
|
||||||
|
target: &str,
|
||||||
|
setter: Uid,
|
||||||
|
prefix: &str,
|
||||||
|
changes: &[(char, char, Option<String>)],
|
||||||
|
) {
|
||||||
|
let members: Vec<Uid> = s
|
||||||
|
.channels
|
||||||
|
.get(key)
|
||||||
|
.map(|c| c.members.keys().copied().collect())
|
||||||
|
.unwrap_or_default();
|
||||||
|
for m in members {
|
||||||
|
let privileged = m == setter || s.is_oper(m);
|
||||||
|
let visible: Vec<&(char, char, Option<String>)> = changes
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, c, _)| match hidden_rank(s, *c) {
|
||||||
|
None => true,
|
||||||
|
Some(req) => privileged || s.rank(m, key) >= req,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if visible.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (modes, params) = render(&visible);
|
||||||
|
let pstr = if params.is_empty() {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
format!(" {}", params.join(" "))
|
||||||
|
};
|
||||||
|
s.send(m, format!(":{prefix} MODE {target} {modes}{pstr}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -34,6 +34,7 @@ pub mod geoip;
|
||||||
pub mod globops;
|
pub mod globops;
|
||||||
pub mod hashident;
|
pub mod hashident;
|
||||||
pub mod hidelist;
|
pub mod hidelist;
|
||||||
|
pub mod hidemode;
|
||||||
pub mod hidewhois;
|
pub mod hidewhois;
|
||||||
pub mod irccloudtags;
|
pub mod irccloudtags;
|
||||||
pub mod jsonlog;
|
pub mod jsonlog;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue