import echoircd — from-scratch irc daemon in native rust
This commit is contained in:
commit
9b12791774
38 changed files with 9757 additions and 0 deletions
179
src/coremods/core_mode.rs
Normal file
179
src/coremods/core_mode.rs
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
//! 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.
|
||||
|
||||
use crate::channels::RANK_HALFOP;
|
||||
use crate::command::{CmdResult, Command};
|
||||
use crate::mode::{chan_mode, user_mode, Applied};
|
||||
use crate::numeric::*;
|
||||
use crate::server::Server;
|
||||
use crate::Uid;
|
||||
|
||||
pub fn commands() -> Vec<Box<dyn Command>> {
|
||||
vec![Box::new(Mode)]
|
||||
}
|
||||
|
||||
/// Append one mode change to the echo string, emitting the +/- only when it flips.
|
||||
fn emit(applied: &mut String, last: &mut char, sign: char, c: char) {
|
||||
if *last != sign {
|
||||
applied.push(sign);
|
||||
*last = sign;
|
||||
}
|
||||
applied.push(c);
|
||||
}
|
||||
|
||||
struct Mode;
|
||||
impl Command for Mode {
|
||||
fn name(&self) -> &'static str {
|
||||
"MODE"
|
||||
}
|
||||
fn min_params(&self) -> usize {
|
||||
1
|
||||
}
|
||||
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
||||
apply_mode(s, uid, params)
|
||||
}
|
||||
}
|
||||
|
||||
/// The MODE body, shared with SAMODE (which wraps it in `Server::mode_sudo` so
|
||||
/// the rank gates below all pass — see `core_oper::SaMode`).
|
||||
pub fn apply_mode(s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
||||
let target = ¶ms[0];
|
||||
if !target.starts_with('#') {
|
||||
return apply_user_modes(s, uid, target, params);
|
||||
}
|
||||
let key = target.to_ascii_lowercase();
|
||||
if !s.channels.contains_key(&key) {
|
||||
s.numeric(
|
||||
uid,
|
||||
ERR_NOSUCHCHANNEL,
|
||||
&format!("{target} :No such channel"),
|
||||
);
|
||||
return CmdResult::Fail;
|
||||
}
|
||||
// query: `MODE #c`
|
||||
if params.len() < 2 {
|
||||
let modestr = s.channels[&key].modes.render(s.is_member(uid, &key));
|
||||
s.numeric(uid, RPL_CHANNELMODEIS, &format!("{target} {modestr}"));
|
||||
let created = s.channels[&key].created;
|
||||
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 {
|
||||
s.numeric(
|
||||
uid,
|
||||
ERR_CHANOPRIVSNEEDED,
|
||||
&format!("{target} :You're not a channel operator"),
|
||||
);
|
||||
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();
|
||||
let mut last = ' ';
|
||||
let mut echoed: Vec<String> = Vec::new();
|
||||
for c in modestring.chars() {
|
||||
if c == '+' || c == '-' {
|
||||
sign = c;
|
||||
continue;
|
||||
}
|
||||
let adding = sign == '+';
|
||||
let Some(handler) = chan_mode(c) else {
|
||||
s.numeric(
|
||||
uid,
|
||||
ERR_UNKNOWNMODE,
|
||||
&format!("{c} :is unknown mode char to me"),
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let param = if handler.wants_param(adding) {
|
||||
let p = args.get(argi).cloned();
|
||||
if p.is_some() {
|
||||
argi += 1;
|
||||
}
|
||||
p
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Applied::Yes(echo) = handler.apply(s, target, &key, uid, adding, param.as_deref()) {
|
||||
emit(&mut applied, &mut last, sign, c);
|
||||
if let Some(p) = echo {
|
||||
echoed.push(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !applied.is_empty() {
|
||||
let prefix = s.users[&uid].prefix();
|
||||
let pstr = if echoed.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" {}", echoed.join(" "))
|
||||
};
|
||||
s.to_channel(
|
||||
&key,
|
||||
&format!(":{prefix} MODE {target} {applied}{pstr}"),
|
||||
None,
|
||||
);
|
||||
s.propagate_from_user(uid, &format!("MODE {target} {applied}{pstr}"));
|
||||
// links
|
||||
}
|
||||
CmdResult::Ok
|
||||
}
|
||||
|
||||
/// User modes: dispatched to the [`crate::mode`] `UserMode` handler objects.
|
||||
fn apply_user_modes(s: &mut Server, uid: Uid, target: &str, params: &[String]) -> CmdResult {
|
||||
let me = s
|
||||
.users
|
||||
.get(&uid)
|
||||
.map(|u| u.nick.clone())
|
||||
.unwrap_or_default();
|
||||
if !target.eq_ignore_ascii_case(&me) {
|
||||
s.numeric(
|
||||
uid,
|
||||
ERR_USERSDONTMATCH,
|
||||
":Can't change mode for other users",
|
||||
);
|
||||
return CmdResult::Ok;
|
||||
}
|
||||
if params.len() < 2 {
|
||||
let um = s
|
||||
.users
|
||||
.get(&uid)
|
||||
.map(|u| u.flags.umodes())
|
||||
.unwrap_or_else(|| "+".to_string());
|
||||
s.numeric(uid, RPL_UMODEIS, &um);
|
||||
return CmdResult::Ok;
|
||||
}
|
||||
let modestring = params[1].clone();
|
||||
let mut sign = '+';
|
||||
let mut applied = String::new();
|
||||
let mut last = ' ';
|
||||
for c in modestring.chars() {
|
||||
if c == '+' || c == '-' {
|
||||
sign = c;
|
||||
continue;
|
||||
}
|
||||
let adding = sign == '+';
|
||||
let Some(handler) = user_mode(c) else {
|
||||
s.numeric(
|
||||
uid,
|
||||
ERR_UMODEUNKNOWNFLAG,
|
||||
&format!(":Unknown MODE flag {c}"),
|
||||
);
|
||||
continue;
|
||||
};
|
||||
if handler.apply(s, uid, adding) {
|
||||
emit(&mut applied, &mut last, sign, c);
|
||||
}
|
||||
}
|
||||
if !applied.is_empty() {
|
||||
s.send(uid, format!(":{me} MODE {me} :{applied}"));
|
||||
}
|
||||
CmdResult::Ok
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue