diff --git a/src/modules/maphide.rs b/src/modules/maphide.rs new file mode 100644 index 0000000..8980088 --- /dev/null +++ b/src/modules/maphide.rs @@ -0,0 +1,44 @@ +//! maphide — hide the server map (`LINKS` / `MAP`) from ordinary users, so the +//! network topology isn't exposed to non-operators. Off unless `maphide = yes`. +//! +//! Behaviour reference: InspIRCd's `m_maphide`. Original native Rust. + +use crate::module::{ModResult, Module}; +use crate::server::Server; +use crate::Uid; + +pub struct MapHide; + +impl Module for MapHide { + fn name(&self) -> &'static str { + "maphide" + } + + fn on_pre_command( + &mut self, + srv: &mut Server, + uid: Uid, + cmd: &str, + _params: &[String], + ) -> ModResult { + if !srv.conf_bool("maphide", false) || srv.is_oper(uid) { + return ModResult::Passthru; + } + if cmd.eq_ignore_ascii_case("LINKS") || cmd.eq_ignore_ascii_case("MAP") { + let nick = srv + .users + .get(&uid) + .map(|u| u.nick.clone()) + .unwrap_or_default(); + srv.send( + uid, + format!( + ":{} NOTICE {nick} :The server map is hidden; ask an operator.", + srv.name + ), + ); + return ModResult::Deny; + } + ModResult::Passthru + } +} diff --git a/src/modules/mod.rs b/src/modules/mod.rs index aa5601c..862d6cf 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -27,6 +27,7 @@ pub mod hidewhois; pub mod irccloudtags; pub mod jsonlog; pub mod jwt; +pub mod maphide; pub mod markread; pub mod metadata; pub mod multiline; @@ -45,6 +46,7 @@ pub mod securelist; pub mod securitygroups; pub mod serverban; pub mod snoop; +pub mod tline; pub mod whoisport; use crate::command::Command; @@ -76,6 +78,7 @@ pub fn default_modules() -> Vec> { Box::new(irccloudtags::IrcCloudTags), Box::new(randquote::RandQuote), Box::new(disable::Disable), + Box::new(maphide::MapHide), ] } @@ -105,5 +108,6 @@ pub fn module_commands() -> Vec> { .chain(extjwt::commands()) .chain(filehost::commands()) .chain(extended_isupport::commands()) + .chain(tline::commands()) .collect() } diff --git a/src/modules/tline.rs b/src/modules/tline.rs new file mode 100644 index 0000000..166afa3 --- /dev/null +++ b/src/modules/tline.rs @@ -0,0 +1,63 @@ +//! tline — `TLINE `, an oper command that reports how many currently-connected +//! local users a would-be K/G/Z-line mask matches, so you can gauge the blast radius +//! before actually setting the ban. +//! +//! Behaviour reference: InspIRCd's `m_tline`. Original native Rust. + +use crate::channels::glob_match; +use crate::command::{CmdResult, Command}; +use crate::numeric::ERR_NOPRIVILEGES; +use crate::server::Server; +use crate::Uid; + +pub fn commands() -> Vec> { + vec![Box::new(TLine)] +} + +struct TLine; +impl Command for TLine { + fn name(&self) -> &'static str { + "TLINE" + } + fn min_params(&self) -> usize { + 1 + } + fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult { + if !s.is_oper(uid) { + s.numeric( + uid, + ERR_NOPRIVILEGES, + ":Permission Denied- You're not an IRC operator", + ); + return CmdResult::Fail; + } + let mask = ¶ms[0]; + let total = s.users.len(); + let matched = s + .users + .values() + .filter(|u| { + let forms = [ + format!("{}!{}@{}", u.nick, u.ident, u.host_display()), + format!("{}@{}", u.ident, u.host), + format!("{}@{}", u.ident, u.addr.ip()), + ]; + forms.iter().any(|f| glob_match(mask, f)) + }) + .count(); + let pct = (matched * 100).checked_div(total).unwrap_or(0); + let nick = s + .users + .get(&uid) + .map(|u| u.nick.clone()) + .unwrap_or_default(); + s.send( + uid, + format!( + ":{} NOTICE {nick} :*** TLINE: {mask} matches {matched} of {total} local users ({pct}%)", + s.name + ), + ); + CmdResult::Ok + } +}