From 2d42fbd2ca23b293b4a43b47b6ea183175b10eab Mon Sep 17 00:00:00 2001 From: reverse Date: Sat, 29 Aug 2026 13:46:25 +0000 Subject: [PATCH] =?UTF-8?q?opers:=20add=20users/ignore-privdeaf=20?= =?UTF-8?q?=E2=80=94=20reach=20+D=20deaf=20users?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A holder's channel messages reach +D (deaf) members: gate the deaf-skip in to_channel_tagged and the TAGMSG fanout on the sender's users/ignore-privdeaf (relayed/service messages, which have no local sender, are unaffected). Granted by the auspex class; verified live (netadmin reaches a deaf user, a restricted oper does not). --- docs/operators.md | 6 ++++-- src/coremods/core_message.rs | 6 +++++- src/modules/opertypes.rs | 5 ++++- src/server.rs | 5 ++++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/operators.md b/docs/operators.md index b2f064c..ff2f40c 100644 --- a/docs/operators.md +++ b/docs/operators.md @@ -91,6 +91,7 @@ command. Assign them via `privs=` on a class or type (`*` = all, `-x` removes on | `users/flood` | exemption from the message- and join-flood limits | | `users/ignore-commonchans` | message a `+c` user without sharing a common channel | | `users/ignore-callerid` | message a `+g` (caller-ID) user without being on their accept list | +| `users/ignore-privdeaf` | reach a `+D` (deaf) user with your channel messages despite their deafness | | `users/ignore-restrictmsg` | private-message anyone while `restrictmsg` is on | | `users/secret-whois` | `/WHOIS` a `+W` (showwhois) user without notifying them | | `servers/use-disabled-commands` | use a command turned off by `disabled_commands` | @@ -100,8 +101,9 @@ command. Assign them via `privs=` on a class or type (`*` = all, `-x` removes on The built-in `override` class carries the channel/message/anti-spam bypasses (`channels/restricted-create`, `channels/ignore-nonicks`, `users/ignore-restrictmsg`, `servers/ignore-securelist`, `servers/ignore-blockamsg`), `auspex` carries the -see-through-privacy set (the three `*/auspex` plus `users/secret-whois` and -`users/ignore-callerid`), and `servers/use-disabled-commands` sits on the `server` class. +see-through-privacy set (the three `*/auspex` plus `users/secret-whois`, +`users/ignore-callerid` and `users/ignore-privdeaf`), and `servers/use-disabled-commands` +sits on the `server` class. ## Snomasks diff --git a/src/coremods/core_message.rs b/src/coremods/core_message.rs index c62754f..8f1495d 100644 --- a/src/coremods/core_message.rs +++ b/src/coremods/core_message.rs @@ -755,8 +755,12 @@ impl Command for TagMsg { .get(&key) .map(|c| c.members.keys().copied().collect()) .unwrap_or_default(); + let reach_deaf = + crate::modules::opertypes::has_priv(s, uid, crate::modules::opertypes::privs::USERS_IGNORE_PRIVDEAF); for m in members { - if (m == uid && !echo) || s.users.get(&m).map(|u| u.flags.deaf).unwrap_or(false) { + if (m == uid && !echo) + || (s.users.get(&m).map(|u| u.flags.deaf).unwrap_or(false) && !reach_deaf) + { continue; } // only message-tags clients receive a TAGMSG diff --git a/src/modules/opertypes.rs b/src/modules/opertypes.rs index 0b5c413..96f2965 100644 --- a/src/modules/opertypes.rs +++ b/src/modules/opertypes.rs @@ -42,6 +42,8 @@ pub mod privs { pub const CHANNELS_IGNORE_NONICKS: &str = "channels/ignore-nonicks"; /// message a +g (caller-id) user without being on their ACCEPT list pub const USERS_IGNORE_CALLERID: &str = "users/ignore-callerid"; + /// reach a +D (deaf) user with your channel messages despite their deafness + pub const USERS_IGNORE_PRIVDEAF: &str = "users/ignore-privdeaf"; /// `/WHOIS` a +W (showwhois) user without notifying them pub const USERS_SECRET_WHOIS: &str = "users/secret-whois"; /// private-message anyone while `restrictmsg` is on @@ -367,7 +369,7 @@ fn builtin() -> (HashMap, HashMap) { classes.insert("services".into(), cdef(&["SVSNICK", "SVSJOIN", "SVSPART", "SVSMODE", "SVSLOGIN", "SVSLOGOUT"], &[], "")); classes.insert("server".into(), cdef(&["CONNECT", "SQUIT", "DIE", "RESTART"], &["servers/use-disabled-commands"], "lr")); // auspex: see through user/channel privacy (real host+IP, geo, secret channels) - classes.insert("auspex".into(), cdef(&[], &["users/auspex", "channels/auspex", "servers/auspex", "users/secret-whois", "users/ignore-callerid"], "")); + classes.insert("auspex".into(), cdef(&[], &["users/auspex", "channels/auspex", "servers/auspex", "users/secret-whois", "users/ignore-callerid", "users/ignore-privdeaf"], "")); let mut types: HashMap = HashMap::default(); // The WHOIS title line is bold + colour 4 (red) by default; override per type @@ -685,6 +687,7 @@ mod tests { assert!(has("override", "users/ignore-restrictmsg")); assert!(has("override", "servers/ignore-securelist") && has("override", "servers/ignore-blockamsg")); assert!(has("auspex", "users/secret-whois") && has("auspex", "users/ignore-callerid")); + assert!(has("auspex", "users/ignore-privdeaf")); assert!(has("server", "servers/use-disabled-commands")); // netadmin holds every class ⇒ every one of the new privileges resolves in assert!(resolved("netadmin").all_privs); diff --git a/src/server.rs b/src/server.rs index 7ecb3ac..c97c6d3 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1354,6 +1354,9 @@ impl Server { let src_mask = self.users.get(&src).map(|su| su.prefix()).unwrap_or_default(); let src_signore: Vec = self.users.get(&src).map(|su| su.signore.clone()).unwrap_or_default(); + // users/ignore-privdeaf: this sender's channel messages reach +D deaf members + let src_reaches_deaf = + crate::modules::opertypes::has_priv(self, src, crate::modules::opertypes::privs::USERS_IGNORE_PRIVDEAF); // one cached line per (server_time, account_tag, message_tags) combination let mut cache: [Option>; 8] = std::array::from_fn(|_| None); for m in members { @@ -1363,7 +1366,7 @@ impl Server { let Some(u) = self.users.get(&m) else { continue; }; - if u.flags.deaf { + if u.flags.deaf && !src_reaches_deaf { continue; } // SIGNORE: skip a member mutually server-ignored with the sender