hidelist: restrict list-mode viewing by rank; allow members to view lists by default (parity)
This commit is contained in:
parent
9d84a66437
commit
ab4d87488b
5 changed files with 73 additions and 7 deletions
|
|
@ -120,6 +120,11 @@ amu_target = both
|
|||
# --- chanlog (m_chanlog): mirror the oper server-notice stream into a channel so
|
||||
# staff can watch it in a normal window. Set the channel (create/keep it opped):
|
||||
# chanlog = #snotices
|
||||
# --- hidelist (m_hidelist): list modes (+b/+e/+I/…) are viewable by members by
|
||||
# default; this restricts a given list to a minimum rank. Repeatable,
|
||||
# `hidelist = <modechar> <rank>` (rank: owner|admin|op|halfop|voice). Opers see
|
||||
# everything. e.g. only ops may view the ban list:
|
||||
# hidelist = b op
|
||||
# --- autoop (m_autoop): no config needed — it's the channel list mode +w. Grant a
|
||||
# status prefix to matching users on join, `+w <prefix>:<hostmask>`, e.g.
|
||||
# /MODE #chan +w o:*!*@trusted.host (auto-op)
|
||||
|
|
|
|||
|
|
@ -88,9 +88,19 @@ pub fn apply_mode(s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
|||
s.numeric(uid, RPL_CREATIONTIME, &format!("{target} {created}"));
|
||||
return CmdResult::Ok;
|
||||
}
|
||||
// setting modes needs at least half-op; each handler then enforces its
|
||||
// own finer rule (prefixes need enough rank, +z needs all-secure, …)
|
||||
if s.rank(uid, &key) < RANK_HALFOP {
|
||||
// dispatch each mode letter to its handler
|
||||
let modestring = params[1].clone();
|
||||
let args = ¶ms[2..];
|
||||
// a *pure list query* (only list-mode letters, no arguments, e.g. `MODE #c +b`)
|
||||
// is just viewing — allow it for anyone (the hidelist module may still restrict
|
||||
// it). Anything that changes a mode needs at least half-op; each handler then
|
||||
// enforces its own finer rule (prefixes need enough rank, +z needs all-secure…).
|
||||
let pure_list_query = args.is_empty()
|
||||
&& modestring
|
||||
.chars()
|
||||
.filter(|c| *c != '+' && *c != '-')
|
||||
.all(|c| chan_mode(c).is_some_and(|h| h.is_list()));
|
||||
if !pure_list_query && s.rank(uid, &key) < RANK_HALFOP {
|
||||
s.numeric(
|
||||
uid,
|
||||
ERR_CHANOPRIVSNEEDED,
|
||||
|
|
@ -98,10 +108,6 @@ pub fn apply_mode(s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
|||
);
|
||||
return CmdResult::Fail;
|
||||
}
|
||||
|
||||
// dispatch each mode letter to its handler
|
||||
let modestring = params[1].clone();
|
||||
let args = ¶ms[2..];
|
||||
let mut argi = 0usize;
|
||||
let mut sign = '+';
|
||||
let mut applied = String::new();
|
||||
|
|
|
|||
17
src/mode.rs
17
src/mode.rs
|
|
@ -32,6 +32,11 @@ pub trait ChanMode: Sync {
|
|||
fn letter(&self) -> char;
|
||||
/// Whether to consume an argument for this sign (taken only if one remains).
|
||||
fn wants_param(&self, adding: bool) -> bool;
|
||||
/// A list mode (+b/+e/+I/+g/+X/+w): a query with no argument is just viewing,
|
||||
/// so it needn't require channel-operator rank (unlike setting an entry).
|
||||
fn is_list(&self) -> bool {
|
||||
false
|
||||
}
|
||||
/// Apply `+`/`-` to channel `key` (display name `chan`) on behalf of `uid`.
|
||||
fn apply(
|
||||
&self,
|
||||
|
|
@ -571,6 +576,9 @@ impl ChanMode for ListMode {
|
|||
fn letter(&self) -> char {
|
||||
self.ch
|
||||
}
|
||||
fn is_list(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn wants_param(&self, _adding: bool) -> bool {
|
||||
true // a mask to add/remove; absent ⇒ list query
|
||||
}
|
||||
|
|
@ -586,6 +594,15 @@ impl ChanMode for ListMode {
|
|||
let (entry_num, end_num, noun) = self.kind.numerics();
|
||||
// no mask ⇒ list query
|
||||
let Some(mask) = param else {
|
||||
// hidelist: low-rank members may be barred from viewing this list
|
||||
if crate::modules::hidelist::denied(s, uid, key, self.ch) {
|
||||
s.numeric(
|
||||
uid,
|
||||
ERR_CHANOPRIVSNEEDED,
|
||||
&format!("{chan} :You do not have access to view the {noun}"),
|
||||
);
|
||||
return Applied::No;
|
||||
}
|
||||
let rows: Vec<(String, String, u64)> = s
|
||||
.channels
|
||||
.get(key)
|
||||
|
|
|
|||
37
src/modules/hidelist.rs
Normal file
37
src/modules/hidelist.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
//! hidelist — hide a channel list mode's entries (e.g. the +b ban list) from users
|
||||
//! below a configured rank, so ordinary members can't enumerate who's banned.
|
||||
//! Config, repeatable: `hidelist = <modechar> <rank>` where rank is one of
|
||||
//! owner|admin|op|halfop|voice. Opers always see. Reference: InspIRCd's
|
||||
//! `m_hidelist`. 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, // unknown ⇒ require op, the safe default
|
||||
}
|
||||
}
|
||||
|
||||
/// True if `uid` may NOT view the `modechar` list in channel `key`: the config sets
|
||||
/// a per-mode minimum rank via `hidelist = <modechar> <rank>`. Opers always see.
|
||||
pub fn denied(s: &Server, uid: Uid, key: &str, modechar: char) -> bool {
|
||||
if s.is_oper(uid) {
|
||||
return false;
|
||||
}
|
||||
for line in s.conf_all("hidelist") {
|
||||
let mut it = line.split_whitespace();
|
||||
if let (Some(mc), Some(rank)) = (it.next(), it.next()) {
|
||||
if mc.chars().next() == Some(modechar) {
|
||||
return s.rank(uid, key) < rank_value(rank);
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ pub mod filter;
|
|||
pub mod flood;
|
||||
pub mod geoip;
|
||||
pub mod hashident;
|
||||
pub mod hidelist;
|
||||
pub mod hidewhois;
|
||||
pub mod irccloudtags;
|
||||
pub mod jsonlog;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue