operprefix + ojoin: server oper prefix (!/mode y, above owner) auto-granted to opers + OJOIN command

This commit is contained in:
Jean Chevronnet 2026-08-10 10:05:37 +00:00
parent 8ebb106f97
commit 1dd7f77ca8
106 changed files with 687 additions and 711 deletions

View file

@ -24,9 +24,9 @@ pub fn commands() -> Vec<Box<dyn Command>> {
]
}
/// TBAN — set a +b ban that lifts itself after a duration (InspIRCd `m_timedbans`).
/// `TBAN <#chan> <duration> <mask>`; needs half-op or above. The background tick
/// removes it and announces `MODE -b` when it expires.
/// TBAN — set a +b ban that lifts itself after a duration. `TBAN <#chan>
/// <duration> <mask>`; needs half-op or above. The background tick removes it and
/// announces `MODE -b` when it expires.
struct Tban;
impl Command for Tban {
fn name(&self) -> &'static str {
@ -313,8 +313,8 @@ impl Command for Invite {
}
}
/// UNINVITE — revoke a pending invite (InspIRCd `m_uninvite`). `UNINVITE <nick>
/// <#chan>`; a channel op cancels an invite they (or another op) issued.
/// UNINVITE — revoke a pending invite. `UNINVITE <nick> <#chan>`; a channel op
/// cancels an invite they (or another op) issued.
struct Uninvite;
impl Command for Uninvite {
fn name(&self) -> &'static str {

View file

@ -193,7 +193,7 @@ impl Command for Info {
fn handle(&self, s: &mut Server, uid: Uid, _params: &[String]) -> CmdResult {
for line in [
format!("echoircd-{VERSION} — a from-scratch IRC daemon in Rust"),
"Modeled on InspIRCd's API; #![forbid(unsafe_code)]".to_string(),
"Memory-safe by construction; no unsafe code".to_string(),
format!("Running the {} network", s.network),
] {
s.numeric(uid, RPL_INFO, &format!(":{line}"));

View file

@ -18,8 +18,8 @@ pub fn commands() -> Vec<Box<dyn Command>> {
]
}
/// SSLINFO — report a user's TLS status and client-cert fingerprint (InspIRCd
/// `m_sslinfo`). You may query yourself; querying another user requires oper.
/// SSLINFO — report a user's TLS status and client-cert fingerprint. You may
/// query yourself; querying another user requires oper.
struct SslInfo;
impl Command for SslInfo {
fn name(&self) -> &'static str {

View file

@ -649,7 +649,7 @@ impl Command for Notice {
/// TAGMSG — an IRCv3 message that carries only client tags (typing, reactions, …)
/// and no text. Relayed to targets whose clients enabled `message-tags`; clients
/// without it never see it. Mirrors PRIVMSG's target / membership / +m rules.
/// without it never see it. Applies PRIVMSG's target / membership / +m rules.
struct TagMsg;
impl Command for TagMsg {
fn name(&self) -> &'static str {

View file

@ -1,6 +1,5 @@
//! core_mode — the MODE command. Both channel and user modes are dispatched to
//! the handler objects in [`crate::mode`] (InspIRCd-style `ModeHandler`s); this
//! file just parses the modestring and orchestrates.
//! MODE: parse the modestring and dispatch each letter to its handler in
//! [`crate::mode`] (channel and user modes alike).
use crate::channels::RANK_HALFOP;
use crate::command::{CmdResult, Command};

View file

@ -1,5 +1,5 @@
//! core_oper — IRC operator commands: OPER, KILL, WALLOPS. Mirrors InspIRCd's
//! `coremods/core_oper/`. Oper blocks are configured with `oper = name pass`.
//! IRC operator commands: OPER, KILL, WALLOPS, the SA*/SVS* set, X-lines, and
//! the oper CHG*/SET* tools. Oper blocks are configured with `oper = name pass`.
use crate::channels::Topic;
use crate::command::{CmdResult, Command};
@ -56,8 +56,8 @@ pub fn commands() -> Vec<Box<dyn Command>> {
]
}
/// OPERMOTD — show the IRC-operators' message of the day (InspIRCd `m_opermotd`),
/// configured with repeated `opermotd = <line>` entries.
/// OPERMOTD — show the IRC-operators' message of the day, configured with
/// repeated `opermotd = <line>` entries.
struct OperMotd;
impl Command for OperMotd {
fn name(&self) -> &'static str {
@ -95,7 +95,7 @@ impl Command for OperMotd {
}
/// An oper-set WHOIS line, stored per-user in `User.ext` and rendered by WHOIS
/// (RPL_WHOISSPECIAL 320). InspIRCd `m_swhois`.
/// (RPL_WHOISSPECIAL 320).
pub struct Swhois(pub String);
/// Reject non-opers with 481; returns whether the caller is an oper.
@ -178,11 +178,11 @@ impl Command for Kill {
}
}
/// SVSLOGIN / SVSLOGOUT — the **services interface** to the account layer
/// ([`crate::accounts`]). Over S2S these arrive from a services pseudoserver
/// (Anope/Atheme); until S2S exists an oper may invoke them to drive `+r` and the
/// account-gated channel modes. `SVSLOGIN <nick> <account>` logs a user in
/// (`account` of `*`/`0` logs out); `SVSLOGOUT <nick>` logs them out.
/// SVSLOGIN / SVSLOGOUT — the services interface to the account layer
/// ([`crate::accounts`]). Over S2S these arrive from a services pseudoserver;
/// until S2S exists an oper may invoke them to drive `+r` and the account-gated
/// channel modes. `SVSLOGIN <nick> <account>` logs a user in (`account` of `*`/`0`
/// logs out); `SVSLOGOUT <nick>` logs them out.
struct SvsLogin;
impl Command for SvsLogin {
fn name(&self) -> &'static str {
@ -415,8 +415,8 @@ impl Command for SaNick {
}
// --- SVS* : the services interface. Same enforcement as the SA* oper commands,
// under the names a services package speaks (like SVSLOGIN). Gated to opers/
// services; a linked services pseudoserver drives these once S2S routes them.
// under the names a services package speaks. Gated to opers/services; a linked
// services pseudoserver drives these once S2S routes them.
/// SVSNICK — force a nick change (nick-registration enforcement). An optional
/// third param is the new-nick TS, accepted and ignored (single-TS model).
@ -729,7 +729,7 @@ impl Command for Qline {
}
/// CBAN — forbid a channel-name glob (opers bypass it). Mask alone removes; a
/// mask + duration adds. InspIRCd `m_cban`.
/// mask + duration adds.
struct Cban;
impl Command for Cban {
fn name(&self) -> &'static str {
@ -743,8 +743,8 @@ impl Command for Cban {
}
}
/// NICKLOCK — force a user's nick and lock it so they can't change it (InspIRCd
/// `m_nicklock`). `NICKLOCK <nick> <newnick>`; opers/services still can.
/// NICKLOCK — force a user's nick and lock it so they can't change it.
/// `NICKLOCK <nick> <newnick>`; opers/services still can.
struct NickLock;
impl Command for NickLock {
fn name(&self) -> &'static str {
@ -1122,8 +1122,8 @@ impl Command for SaKick {
}
}
/// SAQUIT — force a user to quit the network (InspIRCd `m_saquit`). Looks to
/// everyone like a normal client QUIT.
/// SAQUIT — force a user to quit the network. Looks to everyone like a normal
/// client QUIT.
struct SaQuit;
impl Command for SaQuit {
fn name(&self) -> &'static str {
@ -1151,8 +1151,8 @@ impl Command for SaQuit {
}
}
/// CHGNAME — change another user's real name (InspIRCd `m_chgname`). The oper-driven
/// counterpart to SETNAME; broadcast to `setname`-capable peers so clients update live.
/// CHGNAME — change another user's real name (the oper-driven counterpart to
/// SETNAME); broadcast to `setname`-capable peers so clients update live.
struct ChgName;
impl Command for ChgName {
fn name(&self) -> &'static str {
@ -1187,8 +1187,8 @@ impl Command for ChgName {
}
}
/// CLEARCHAN — kick every user out of a channel (InspIRCd `m_clearchan`). Each
/// removal is a normal KICK, propagated like SAKICK.
/// CLEARCHAN — kick every user out of a channel. Each removal is a normal KICK,
/// propagated like SAKICK.
struct ClearChan;
impl Command for ClearChan {
fn name(&self) -> &'static str {
@ -1244,7 +1244,7 @@ impl Command for ClearChan {
}
}
/// CHECK — oper diagnostic dump for a nick or channel (InspIRCd `m_check`).
/// CHECK — oper diagnostic dump for a nick or channel.
struct Check;
impl Command for Check {
fn name(&self) -> &'static str {
@ -1329,8 +1329,8 @@ impl Command for Check {
}
}
/// SWHOIS — attach (or clear) an extra WHOIS line on a user (InspIRCd `m_swhois`).
/// `SWHOIS <nick> :<text>`; an empty text removes it. Shown as RPL_WHOISSPECIAL.
/// SWHOIS — attach (or clear) an extra WHOIS line on a user. `SWHOIS <nick>
/// :<text>`; an empty text removes it. Shown as RPL_WHOISSPECIAL.
struct SwhoisCmd;
impl Command for SwhoisCmd {
fn name(&self) -> &'static str {
@ -1361,8 +1361,8 @@ impl Command for SwhoisCmd {
}
}
/// SETIDLE — reset your own idle time (InspIRCd `m_setidle`). `SETIDLE <seconds>`
/// backdates the last-activity clock so WHOIS shows that idle time.
/// SETIDLE — reset your own idle time. `SETIDLE <seconds>` backdates the
/// last-activity clock so WHOIS shows that idle time.
struct SetIdle;
impl Command for SetIdle {
fn name(&self) -> &'static str {
@ -1384,8 +1384,8 @@ impl Command for SetIdle {
}
}
/// ALLTIME — show the current server time to the requesting oper (InspIRCd
/// `m_alltime`; on a single server there's just the one time to report).
/// ALLTIME — show the current server time to the requesting oper (on a single
/// server there's just the one time to report).
struct AllTime;
impl Command for AllTime {
fn name(&self) -> &'static str {

View file

@ -1,11 +1,8 @@
//! core_rehash — the REHASH command. Re-reads the config file and applies every
//! setting that can change at runtime, the way InspIRCd's rehash does:
//!
//! * opers only; replies with RPL_REHASHING (382) and a server-notice to +s opers;
//! * takes an optional `<servermask>` (we only rehash if it matches this server —
//! there's no remote-rehash over S2S yet);
//! * **keeps the running config if the file can't be read** (via `Config::try_load`),
//! so a REHASH of a deleted/renamed config never resets opers/cloak-key to defaults.
//! REHASH: re-read the config file and apply every setting that can change at
//! runtime (opers only; replies RPL_REHASHING 382). Takes an optional
//! `<servermask>`, matched against this server's name (no remote rehash over S2S).
//! A missing/unreadable config file leaves the running config intact (via
//! `Config::try_load`), so opers/cloak-key are never reset to defaults.
//!
//! Reloadable live: MOTD, oper blocks, cloak key, +G censor words, antimixedutf8,
//! and the reverse-DNS options. Listener/bind/SID changes still need a restart.
@ -50,8 +47,7 @@ impl Command for Rehash {
let path = s.conf_path.clone();
match Config::try_load(&path) {
Some(fresh) => {
// echoIRCd's own announcement (not InspIRCd's) — broadcast to
// everyone connected, not just opers.
// announce to everyone connected, not just opers
s.announce(&format!(
"admin {who} has changed the configuration of the server."
));

View file

@ -27,7 +27,7 @@ pub fn commands() -> Vec<Box<dyn Command>> {
}
/// VHOST — claim a self-service virtual host with `VHOST <user> <pass>` matching a
/// configured `vhost = <user> <pass> <host>` block (InspIRCd `m_vhost`).
/// configured `vhost = <user> <pass> <host>` block.
struct Vhost;
impl Command for Vhost {
fn name(&self) -> &'static str {
@ -247,10 +247,10 @@ fn cap_target(s: &Server, uid: Uid) -> String {
.unwrap_or_else(|| "*".to_string())
}
/// AUTHENTICATE — the SASL handshake. echoIRCd verifies nothing itself (it has no
/// AUTHENTICATE — the SASL handshake. The ircd verifies nothing itself (it has no
/// accounts); once a services server is linked over S2S the payload is relayed to
/// it and `set_login` applied on success. Until then — exactly like InspIRCd with
/// no services — SASL fails cleanly.
/// it and `set_login` applied on success. With no services linked, SASL fails
/// cleanly.
struct Authenticate;
impl Command for Authenticate {
fn name(&self) -> &'static str {
@ -274,7 +274,7 @@ impl Command for Authenticate {
let arg = &params[0];
let mech = s.users.get(&uid).and_then(|u| u.sasl_mech.clone());
// SASL is relayed to a linked services server (see `Server::sasl_relay`);
// with none configured/linked it fails cleanly, exactly like InspIRCd.
// with none configured/linked it fails cleanly.
let have_services = s.sasl_link().is_some();
match mech {
// step 1 — the client picks a mechanism

View file

@ -1,7 +1,6 @@
//! core_watch — WATCH, MONITOR (IRCv3) and SILENCE. The per-user lists live on
//! the `User`; the online/offline notifications are driven from the lifecycle
//! code via [`crate::server::Server::watch_notify_online`] / `_offline`. Mirrors
//! InspIRCd's `m_watch` / `m_monitor` / `m_silence`.
//! WATCH, MONITOR (IRCv3), SILENCE and ACCEPT. The per-user lists live on the
//! `User`; online/offline notifications are driven from the lifecycle code via
//! [`crate::server::Server::watch_notify_online`] / `_offline`.
use crate::channels::normalize_mask;
use crate::command::{CmdResult, Command};

View file

@ -1,7 +1,5 @@
//! The built-in commands, grouped the way InspIRCd groups its `coremods/`:
//! `core_user`, `core_channel`, `core_message`, `core_mode`, `core_oper`,
//! `core_info`. Each module exposes `commands()`; [`command_table`] assembles the
//! registry the core dispatches through.
//! Built-in commands. Each module exposes `commands()`; [`command_table`]
//! assembles the registry the core dispatches through.
pub mod core_channel;
pub mod core_extra;