modules: port randquote, disable, restrictchans, channames
This commit is contained in:
parent
bda7eecfc2
commit
140554719b
6 changed files with 172 additions and 0 deletions
|
|
@ -533,6 +533,14 @@ impl Server {
|
||||||
if crate::modules::denychans::intercept(self, uid, name, is_oper) {
|
if crate::modules::denychans::intercept(self, uid, name, is_oper) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// restrictchans — only opers may create new channels (unless whitelisted)
|
||||||
|
if crate::modules::restrictchans::intercept(self, uid, name, is_oper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// channames — forbidden characters in new channel names
|
||||||
|
if crate::modules::channames::intercept(self, uid, name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// an existing channel can refuse the join (+k / +b / +i / +z / +R / +J)
|
// 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 {
|
||||||
|
|
|
||||||
33
src/modules/channames.rs
Normal file
33
src/modules/channames.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
//! channames — restrict which characters may appear in *new* channel names, beyond
|
||||||
|
//! the protocol minimum. `channames_deny = <chars>` lists forbidden characters (e.g.
|
||||||
|
//! control codes or fancy Unicode an admin doesn't want in channel names). Existing
|
||||||
|
//! channels are unaffected. Off unless `channames_deny` is set. Dispatched from
|
||||||
|
//! `Server::join`.
|
||||||
|
//!
|
||||||
|
//! Behaviour reference: InspIRCd's `m_channames`. Original native Rust.
|
||||||
|
|
||||||
|
use crate::numeric::ERR_BADCHANNEL;
|
||||||
|
use crate::server::Server;
|
||||||
|
use crate::Uid;
|
||||||
|
|
||||||
|
/// Called from `Server::join`. Returns true when `name` uses a forbidden character
|
||||||
|
/// (the caller returns without joining). Only creation of new channels is checked.
|
||||||
|
pub fn intercept(s: &mut Server, uid: Uid, name: &str) -> bool {
|
||||||
|
let Some(deny) = s.conf("channames_deny").filter(|d| !d.is_empty()) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let deny: Vec<char> = deny.chars().collect();
|
||||||
|
// joining an existing channel is always allowed
|
||||||
|
if s.channels.contains_key(&name.to_ascii_lowercase()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if name.chars().any(|c| deny.contains(&c)) {
|
||||||
|
s.numeric(
|
||||||
|
uid,
|
||||||
|
ERR_BADCHANNEL,
|
||||||
|
&format!("{name} :Channel name contains characters not permitted here"),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
45
src/modules/disable.rs
Normal file
45
src/modules/disable.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
//! disable — refuse a configured set of commands to ordinary users (opers bypass).
|
||||||
|
//! `disabled_commands = LIST WHO KNOCK` (space-separated; repeatable). A disabled
|
||||||
|
//! command replies with `421` as if it didn't exist. Off unless configured.
|
||||||
|
//!
|
||||||
|
//! Behaviour reference: InspIRCd's `m_disable`. Original native Rust.
|
||||||
|
|
||||||
|
use crate::module::{ModResult, Module};
|
||||||
|
use crate::numeric::ERR_UNKNOWNCOMMAND;
|
||||||
|
use crate::server::Server;
|
||||||
|
use crate::Uid;
|
||||||
|
|
||||||
|
pub struct Disable;
|
||||||
|
|
||||||
|
impl Module for Disable {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"disable"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_pre_command(
|
||||||
|
&mut self,
|
||||||
|
srv: &mut Server,
|
||||||
|
uid: Uid,
|
||||||
|
cmd: &str,
|
||||||
|
_params: &[String],
|
||||||
|
) -> ModResult {
|
||||||
|
// opers are never restricted
|
||||||
|
if srv.is_oper(uid) {
|
||||||
|
return ModResult::Passthru;
|
||||||
|
}
|
||||||
|
let disabled = srv
|
||||||
|
.conf_all("disabled_commands")
|
||||||
|
.iter()
|
||||||
|
.flat_map(|line| line.split_whitespace())
|
||||||
|
.any(|c| c.eq_ignore_ascii_case(cmd));
|
||||||
|
if disabled {
|
||||||
|
srv.numeric(
|
||||||
|
uid,
|
||||||
|
ERR_UNKNOWNCOMMAND,
|
||||||
|
&format!("{cmd} :This command has been disabled by the administrator."),
|
||||||
|
);
|
||||||
|
return ModResult::Deny;
|
||||||
|
}
|
||||||
|
ModResult::Passthru
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ pub mod account_registration;
|
||||||
pub mod antimixedutf8;
|
pub mod antimixedutf8;
|
||||||
pub mod antirandom;
|
pub mod antirandom;
|
||||||
pub mod blockamsg;
|
pub mod blockamsg;
|
||||||
|
pub mod channames;
|
||||||
pub mod channelban;
|
pub mod channelban;
|
||||||
pub mod chathistory;
|
pub mod chathistory;
|
||||||
pub mod cloak;
|
pub mod cloak;
|
||||||
|
|
@ -14,6 +15,7 @@ pub mod cloudflare_challenge;
|
||||||
pub mod connectban;
|
pub mod connectban;
|
||||||
pub mod connflood;
|
pub mod connflood;
|
||||||
pub mod denychans;
|
pub mod denychans;
|
||||||
|
pub mod disable;
|
||||||
pub mod dnsbl;
|
pub mod dnsbl;
|
||||||
pub mod extended_isupport;
|
pub mod extended_isupport;
|
||||||
pub mod extjwt;
|
pub mod extjwt;
|
||||||
|
|
@ -31,9 +33,11 @@ pub mod multiline;
|
||||||
pub mod network_icon;
|
pub mod network_icon;
|
||||||
pub mod password_hash;
|
pub mod password_hash;
|
||||||
pub mod profilelink;
|
pub mod profilelink;
|
||||||
|
pub mod randquote;
|
||||||
pub mod realnameban;
|
pub mod realnameban;
|
||||||
pub mod recaptcha;
|
pub mod recaptcha;
|
||||||
pub mod reputation;
|
pub mod reputation;
|
||||||
|
pub mod restrictchans;
|
||||||
pub mod restrictcommands;
|
pub mod restrictcommands;
|
||||||
pub mod restrictmsg;
|
pub mod restrictmsg;
|
||||||
pub mod rpc;
|
pub mod rpc;
|
||||||
|
|
@ -70,6 +74,8 @@ pub fn default_modules() -> Vec<Box<dyn Module>> {
|
||||||
Box::new(cloudflare_challenge::CloudflareChallenge),
|
Box::new(cloudflare_challenge::CloudflareChallenge),
|
||||||
Box::new(filehost::FileHost),
|
Box::new(filehost::FileHost),
|
||||||
Box::new(irccloudtags::IrcCloudTags),
|
Box::new(irccloudtags::IrcCloudTags),
|
||||||
|
Box::new(randquote::RandQuote),
|
||||||
|
Box::new(disable::Disable),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
44
src/modules/randquote.rs
Normal file
44
src/modules/randquote.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
//! randquote — greet each connecting user with a random line from a configured
|
||||||
|
//! set of quotes. Off unless one or more `randquote = <line>` are configured.
|
||||||
|
//!
|
||||||
|
//! Behaviour reference: InspIRCd's `m_randquote`. Original native Rust.
|
||||||
|
|
||||||
|
use openssl::rand::rand_bytes;
|
||||||
|
|
||||||
|
use crate::module::Module;
|
||||||
|
use crate::server::Server;
|
||||||
|
use crate::Uid;
|
||||||
|
|
||||||
|
/// A random index in `0..n` from the CSPRNG (0 if `n == 0` or on error).
|
||||||
|
fn rand_below(n: usize) -> usize {
|
||||||
|
if n == 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let mut b = [0u8; 8];
|
||||||
|
if rand_bytes(&mut b).is_err() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
(u64::from_le_bytes(b) % n as u64) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RandQuote;
|
||||||
|
|
||||||
|
impl Module for RandQuote {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"randquote"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_user_connect(&mut self, srv: &mut Server, uid: Uid) {
|
||||||
|
let quotes = srv.conf_all("randquote");
|
||||||
|
if quotes.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let quote = quotes[rand_below(quotes.len())].clone();
|
||||||
|
let nick = srv
|
||||||
|
.users
|
||||||
|
.get(&uid)
|
||||||
|
.map(|u| u.nick.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
srv.send(uid, format!(":{} NOTICE {nick} :{quote}", srv.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/modules/restrictchans.rs
Normal file
36
src/modules/restrictchans.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
//! restrictchans — only opers may *create* new channels; everyone can still join
|
||||||
|
//! existing ones. A `restrictchan = <glob>` whitelist lets ordinary users create
|
||||||
|
//! channels whose name matches (e.g. `restrictchan = #public-*`). Off unless
|
||||||
|
//! `restrictchans = yes`. Dispatched from `Server::join` (like denychans).
|
||||||
|
//!
|
||||||
|
//! Behaviour reference: InspIRCd's `m_restrictchans`. Original native Rust.
|
||||||
|
|
||||||
|
use crate::channels::glob_match;
|
||||||
|
use crate::numeric::ERR_BADCHANNEL;
|
||||||
|
use crate::server::Server;
|
||||||
|
use crate::Uid;
|
||||||
|
|
||||||
|
/// Called from `Server::join`. Returns true when creating `name` should be blocked
|
||||||
|
/// (the caller returns without joining). Opers and joins to *existing* channels pass.
|
||||||
|
pub fn intercept(s: &mut Server, uid: Uid, name: &str, is_oper: bool) -> bool {
|
||||||
|
if is_oper || !s.conf_bool("restrictchans", false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// joining a channel that already exists is always fine
|
||||||
|
if s.channels.contains_key(&name.to_ascii_lowercase()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// creating a new one: allowed only if it matches a whitelist glob
|
||||||
|
if s.conf_all("restrictchan")
|
||||||
|
.iter()
|
||||||
|
.any(|g| glob_match(g, name))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
s.numeric(
|
||||||
|
uid,
|
||||||
|
ERR_BADCHANNEL,
|
||||||
|
&format!("{name} :Only IRC operators may create new channels here"),
|
||||||
|
);
|
||||||
|
true
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue