snomasks: make +s a parametric snomask mode with the standard category letters (acdfgjklnoqrtuvwx), route each server notice by category, RPL_SNOMASKIS 008; opers default to all and narrow with +s -c etc.
This commit is contained in:
parent
309201d7fb
commit
546f6279e7
16 changed files with 131 additions and 29 deletions
|
|
@ -847,7 +847,7 @@ impl Server {
|
||||||
.get(&uid)
|
.get(&uid)
|
||||||
.map(|u| u.nick.clone())
|
.map(|u| u.nick.clone())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
self.snotice(&format!("{nick} used oper override to join {name}"));
|
self.snotice_c('v', &format!("{nick} used oper override to join {name}"));
|
||||||
}
|
}
|
||||||
let is_new = !self.channels.contains_key(&key);
|
let is_new = !self.channels.contains_key(&key);
|
||||||
let ch = self
|
let ch = self
|
||||||
|
|
@ -876,7 +876,7 @@ impl Server {
|
||||||
.get(&uid)
|
.get(&uid)
|
||||||
.map(|u| u.nick.clone())
|
.map(|u| u.nick.clone())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
self.snotice(&format!("{who} created channel {name}"));
|
self.snotice_c('j', &format!("{who} created channel {name}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// JOIN broadcast — extended-join clients also get the account + realname
|
// JOIN broadcast — extended-join clients also get the account + realname
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,68 @@ pub fn svs_set_chan_modes(s: &mut Server, target: &str, modestring: &str, args:
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply a client `+s`/`-s` snomask change. Oper-only. `+s` with a mask edits the
|
||||||
|
/// subscribed categories (`+cq` adds, `-c` removes, `*` all); `+s` with no mask
|
||||||
|
/// subscribes to everything; `-s` clears it. Emits `RPL_SNOMASKIS` (008) and returns
|
||||||
|
/// whether the `s` mode char should appear in the MODE echo.
|
||||||
|
fn apply_snomask(s: &mut Server, uid: Uid, adding: bool, param: Option<&str>) -> bool {
|
||||||
|
if !s.is_oper(uid) {
|
||||||
|
s.numeric(
|
||||||
|
uid,
|
||||||
|
crate::numeric::ERR_NOPRIVILEGES,
|
||||||
|
":Permission denied - only operators may set a server notice mask",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let mut cats: std::collections::BTreeSet<char> = s
|
||||||
|
.users
|
||||||
|
.get(&uid)
|
||||||
|
.map(|u| u.flags.snomask_cats.chars().collect())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let all = || crate::users::DEFAULT_SNOMASK.chars().collect::<std::collections::BTreeSet<char>>();
|
||||||
|
if !adding {
|
||||||
|
cats.clear();
|
||||||
|
} else {
|
||||||
|
match param {
|
||||||
|
None => cats = all(),
|
||||||
|
Some(p) => {
|
||||||
|
let mut sign = '+';
|
||||||
|
for c in p.chars() {
|
||||||
|
match c {
|
||||||
|
'+' => sign = '+',
|
||||||
|
'-' => sign = '-',
|
||||||
|
'*' => cats = if sign == '+' { all() } else { Default::default() },
|
||||||
|
c if crate::users::DEFAULT_SNOMASK.contains(c) => {
|
||||||
|
if sign == '+' {
|
||||||
|
cats.insert(c);
|
||||||
|
} else {
|
||||||
|
cats.remove(&c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {} // ignore unknown snomask letters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mask: String = cats.iter().collect();
|
||||||
|
let on = !mask.is_empty();
|
||||||
|
if let Some(u) = s.users.get_mut(&uid) {
|
||||||
|
u.flags.snomask = on;
|
||||||
|
u.flags.snomask_cats = mask.clone();
|
||||||
|
}
|
||||||
|
s.numeric(
|
||||||
|
uid,
|
||||||
|
crate::numeric::RPL_SNOMASKIS,
|
||||||
|
&format!("+{mask} :Server notice mask"),
|
||||||
|
);
|
||||||
|
if adding {
|
||||||
|
on
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// User modes: dispatched to the [`crate::mode`] `UserMode` handler objects.
|
/// User modes: dispatched to the [`crate::mode`] `UserMode` handler objects.
|
||||||
fn apply_user_modes(s: &mut Server, uid: Uid, target: &str, params: &[String]) -> CmdResult {
|
fn apply_user_modes(s: &mut Server, uid: Uid, target: &str, params: &[String]) -> CmdResult {
|
||||||
let me = s
|
let me = s
|
||||||
|
|
@ -264,12 +326,29 @@ fn apply_user_modes(s: &mut Server, uid: Uid, target: &str, params: &[String]) -
|
||||||
let mut sign = '+';
|
let mut sign = '+';
|
||||||
let mut applied = String::new();
|
let mut applied = String::new();
|
||||||
let mut last = ' ';
|
let mut last = ' ';
|
||||||
|
let mut argi = 2usize; // params[2..] are mode arguments (the +s snomask mask)
|
||||||
for c in modestring.chars() {
|
for c in modestring.chars() {
|
||||||
if c == '+' || c == '-' {
|
if c == '+' || c == '-' {
|
||||||
sign = c;
|
sign = c;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let adding = sign == '+';
|
let adding = sign == '+';
|
||||||
|
// +s is a parametric snomask mode: it consumes the following mask argument
|
||||||
|
if c == 's' {
|
||||||
|
let param = if adding {
|
||||||
|
let p = params.get(argi).cloned();
|
||||||
|
if p.is_some() {
|
||||||
|
argi += 1;
|
||||||
|
}
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if apply_snomask(s, uid, adding, param.as_deref()) {
|
||||||
|
emit(&mut applied, &mut last, sign, c);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let Some(handler) = user_mode(c) else {
|
let Some(handler) = user_mode(c) else {
|
||||||
s.numeric(
|
s.numeric(
|
||||||
uid,
|
uid,
|
||||||
|
|
|
||||||
|
|
@ -868,7 +868,7 @@ impl Command for NickLock {
|
||||||
u.flags.nick_locked = true;
|
u.flags.nick_locked = true;
|
||||||
}
|
}
|
||||||
let by = oper_nick(s, uid);
|
let by = oper_nick(s, uid);
|
||||||
s.snotice(&format!("{by} used NICKLOCK on {newnick}"));
|
s.snotice_c('v', &format!("{by} used NICKLOCK on {newnick}"));
|
||||||
CmdResult::Ok
|
CmdResult::Ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -893,7 +893,7 @@ impl Command for NickUnlock {
|
||||||
u.flags.nick_locked = false;
|
u.flags.nick_locked = false;
|
||||||
}
|
}
|
||||||
let by = oper_nick(s, uid);
|
let by = oper_nick(s, uid);
|
||||||
s.snotice(&format!("{by} used NICKUNLOCK on {}", params[0]));
|
s.snotice_c('v', &format!("{by} used NICKUNLOCK on {}", params[0]));
|
||||||
CmdResult::Ok
|
CmdResult::Ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -933,7 +933,7 @@ impl Command for Connect {
|
||||||
let max_line = s.conf_num("max_line", crate::socketengine::DEFAULT_MAX_LINE);
|
let max_line = s.conf_num("max_line", crate::socketengine::DEFAULT_MAX_LINE);
|
||||||
std::thread::spawn(move || crate::socketengine::connect_link(&addr, tx, counter, max_line));
|
std::thread::spawn(move || crate::socketengine::connect_link(&addr, tx, counter, max_line));
|
||||||
let by = oper_nick(s, uid);
|
let by = oper_nick(s, uid);
|
||||||
s.snotice(&format!(
|
s.snotice_c('l', &format!(
|
||||||
"{by} used CONNECT to {} ({}:{})",
|
"{by} used CONNECT to {} ({}:{})",
|
||||||
b.name, b.ip, b.port
|
b.name, b.ip, b.port
|
||||||
));
|
));
|
||||||
|
|
@ -996,7 +996,7 @@ impl Command for ChgHost {
|
||||||
};
|
};
|
||||||
s.change_host_ident(t, None, Some(¶ms[1]));
|
s.change_host_ident(t, None, Some(¶ms[1]));
|
||||||
let by = oper_nick(s, uid);
|
let by = oper_nick(s, uid);
|
||||||
s.snotice(&format!(
|
s.snotice_c('v', &format!(
|
||||||
"{by} used CHGHOST on {}: {}",
|
"{by} used CHGHOST on {}: {}",
|
||||||
params[0], params[1]
|
params[0], params[1]
|
||||||
));
|
));
|
||||||
|
|
@ -1048,7 +1048,7 @@ impl Command for ChgIdent {
|
||||||
};
|
};
|
||||||
s.change_host_ident(t, Some(¶ms[1]), None);
|
s.change_host_ident(t, Some(¶ms[1]), None);
|
||||||
let by = oper_nick(s, uid);
|
let by = oper_nick(s, uid);
|
||||||
s.snotice(&format!(
|
s.snotice_c('v', &format!(
|
||||||
"{by} used CHGIDENT on {}: {}",
|
"{by} used CHGIDENT on {}: {}",
|
||||||
params[0], params[1]
|
params[0], params[1]
|
||||||
));
|
));
|
||||||
|
|
@ -1095,7 +1095,7 @@ impl Command for SaMode {
|
||||||
let r = apply_mode(s, uid, params);
|
let r = apply_mode(s, uid, params);
|
||||||
s.mode_sudo = false;
|
s.mode_sudo = false;
|
||||||
let by = oper_nick(s, uid);
|
let by = oper_nick(s, uid);
|
||||||
s.snotice(&format!("{by} used SAMODE: {}", params.join(" ")));
|
s.snotice_c('v', &format!("{by} used SAMODE: {}", params.join(" ")));
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1134,7 +1134,7 @@ impl Command for SaTopic {
|
||||||
s.to_channel(&key, &format!(":{prefix} TOPIC {chan} :{text}"), None);
|
s.to_channel(&key, &format!(":{prefix} TOPIC {chan} :{text}"), None);
|
||||||
s.propagate_topic(uid, chan, &text);
|
s.propagate_topic(uid, chan, &text);
|
||||||
let by = oper_nick(s, uid);
|
let by = oper_nick(s, uid);
|
||||||
s.snotice(&format!("{by} used SATOPIC on {chan}"));
|
s.snotice_c('v', &format!("{by} used SATOPIC on {chan}"));
|
||||||
CmdResult::Ok
|
CmdResult::Ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1195,7 +1195,7 @@ impl Command for SaKick {
|
||||||
s.events
|
s.events
|
||||||
.push_back(Hook::Part(tuid, key, "kicked".to_string()));
|
.push_back(Hook::Part(tuid, key, "kicked".to_string()));
|
||||||
let by = oper_nick(s, uid);
|
let by = oper_nick(s, uid);
|
||||||
s.snotice(&format!("{by} used SAKICK on {victim} in {chan}"));
|
s.snotice_c('v', &format!("{by} used SAKICK on {victim} in {chan}"));
|
||||||
CmdResult::Ok
|
CmdResult::Ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1224,7 +1224,7 @@ impl Command for SaQuit {
|
||||||
s.send(tuid, format!("ERROR :Closing link: (SAQUIT: {reason})"));
|
s.send(tuid, format!("ERROR :Closing link: (SAQUIT: {reason})"));
|
||||||
s.remove_user(tuid, &format!("Quit: {reason}"));
|
s.remove_user(tuid, &format!("Quit: {reason}"));
|
||||||
let by = oper_nick(s, uid);
|
let by = oper_nick(s, uid);
|
||||||
s.snotice(&format!("{by} used SAQUIT on {}: {reason}", params[0]));
|
s.snotice_c('v', &format!("{by} used SAQUIT on {}: {reason}", params[0]));
|
||||||
CmdResult::Ok
|
CmdResult::Ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1260,7 +1260,7 @@ impl Command for ChgName {
|
||||||
}
|
}
|
||||||
s.notify_peers(t, &line, |c| c.setname);
|
s.notify_peers(t, &line, |c| c.setname);
|
||||||
let by = oper_nick(s, uid);
|
let by = oper_nick(s, uid);
|
||||||
s.snotice(&format!("{by} used CHGNAME on {}: {realname}", params[0]));
|
s.snotice_c('v', &format!("{by} used CHGNAME on {}: {realname}", params[0]));
|
||||||
CmdResult::Ok
|
CmdResult::Ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1317,7 +1317,7 @@ impl Command for ClearChan {
|
||||||
}
|
}
|
||||||
s.channels.retain(|_, c| c.keep_alive());
|
s.channels.retain(|_, c| c.keep_alive());
|
||||||
let by = oper_nick(s, uid);
|
let by = oper_nick(s, uid);
|
||||||
s.snotice(&format!("{by} used CLEARCHAN on {chan}"));
|
s.snotice_c('v', &format!("{by} used CLEARCHAN on {chan}"));
|
||||||
CmdResult::Ok
|
CmdResult::Ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1434,7 +1434,7 @@ impl Command for SwhoisCmd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let by = oper_nick(s, uid);
|
let by = oper_nick(s, uid);
|
||||||
s.snotice(&format!("{by} used SWHOIS on {}: {text}", params[0]));
|
s.snotice_c('v', &format!("{by} used SWHOIS on {}: {text}", params[0]));
|
||||||
CmdResult::Ok
|
CmdResult::Ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1273,7 +1273,14 @@ fn set_helpop(f: &mut UserFlags, v: bool) {
|
||||||
f.helpop = v;
|
f.helpop = v;
|
||||||
}
|
}
|
||||||
fn set_snomask(f: &mut UserFlags, v: bool) {
|
fn set_snomask(f: &mut UserFlags, v: bool) {
|
||||||
|
// the plain `+s`/`-s` path (services, or a client giving no mask) toggles the
|
||||||
|
// full default set; the client `+s <mask>` path in apply_user_modes refines it
|
||||||
f.snomask = v;
|
f.snomask = v;
|
||||||
|
f.snomask_cats = if v {
|
||||||
|
crate::users::DEFAULT_SNOMASK.to_string()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An oper-only boolean flag (+H / +W / +h / +s): only an operator may **set** it;
|
/// An oper-only boolean flag (+H / +W / +h / +s): only an operator may **set** it;
|
||||||
|
|
|
||||||
|
|
@ -257,7 +257,7 @@ fn pick(
|
||||||
if let Some(max) = c.limit {
|
if let Some(max) = c.limit {
|
||||||
if class_count(s, &c.name, uid) >= max {
|
if class_count(s, &c.name, uid) >= max {
|
||||||
if c.maxconnwarn {
|
if c.maxconnwarn {
|
||||||
s.snotice(&format!("connect class {} is full ({max})", c.name));
|
s.snotice_c('c', &format!("connect class {} is full ({max})", c.name));
|
||||||
}
|
}
|
||||||
continue; // full — try the next class
|
continue; // full — try the next class
|
||||||
}
|
}
|
||||||
|
|
@ -323,7 +323,7 @@ pub fn assign(s: &mut Server, uid: Uid) -> Option<String> {
|
||||||
};
|
};
|
||||||
let warn = |s: &Server, why: &str| {
|
let warn = |s: &Server, why: &str| {
|
||||||
if class.maxconnwarn {
|
if class.maxconnwarn {
|
||||||
s.snotice(&format!("connect class {} refused {ip}: {why}", class.name));
|
s.snotice_c('c', &format!("connect class {} refused {ip}: {why}", class.name));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(max) = class.localmax {
|
if let Some(max) = class.localmax {
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ pub fn on_connect(s: &mut Server, ip: IpAddr) {
|
||||||
)
|
)
|
||||||
.to_string();
|
.to_string();
|
||||||
s.add_xline(XKind::Zline, &glob, dur, &setter, &reason);
|
s.add_xline(XKind::Zline, &glob, dur, &setter, &reason);
|
||||||
s.snotice(&format!(
|
s.snotice_c('x', &format!(
|
||||||
"Connect flooding from IP range {glob} (threshold {threshold})"
|
"Connect flooding from IP range {glob} (threshold {threshold})"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ fn act(s: &mut Server, uid: Uid, zone: &str, reply: Ipv4Addr) {
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
let action = s.dnsbl_action.clone();
|
let action = s.dnsbl_action.clone();
|
||||||
s.snotice(&format!(
|
s.snotice_c('d', &format!(
|
||||||
"DNSBL: {mask} is listed on {zone} ({reply}); action={action}"
|
"DNSBL: {mask} is listed on {zone} ({reply}); action={action}"
|
||||||
));
|
));
|
||||||
let reason = format!("{} (listed on {zone})", s.dnsbl_reason);
|
let reason = format!("{} (listed on {zone})", s.dnsbl_reason);
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ impl Module for Filter {
|
||||||
Some(u) => (u.prefix(), u.addr.ip().to_string()),
|
Some(u) => (u.prefix(), u.addr.ip().to_string()),
|
||||||
None => return ModResult::Deny,
|
None => return ModResult::Deny,
|
||||||
};
|
};
|
||||||
s.snotice(&format!(
|
s.snotice_c('f', &format!(
|
||||||
"FILTER: {mask} matched a filter (action={action}): {reason}"
|
"FILTER: {mask} matched a filter (action={action}): {reason}"
|
||||||
));
|
));
|
||||||
match action.as_str() {
|
match action.as_str() {
|
||||||
|
|
@ -171,7 +171,7 @@ impl Command for FilterCmd {
|
||||||
duration,
|
duration,
|
||||||
reason,
|
reason,
|
||||||
});
|
});
|
||||||
s.snotice(&format!("{nick} added FILTER {pattern} (action={action})"));
|
s.snotice_c('f', &format!("{nick} added FILTER {pattern} (action={action})"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ impl Command for GlobopsCmd {
|
||||||
return CmdResult::Fail;
|
return CmdResult::Fail;
|
||||||
}
|
}
|
||||||
let nick = s.users.get(&uid).map(|u| u.nick.clone()).unwrap_or_default();
|
let nick = s.users.get(&uid).map(|u| u.nick.clone()).unwrap_or_default();
|
||||||
s.snotice(&format!("GLOBOPS from {nick}: {}", params.join(" ")));
|
s.snotice_c('g', &format!("GLOBOPS from {nick}: {}", params.join(" ")));
|
||||||
CmdResult::Ok
|
CmdResult::Ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ impl Command for OjoinCmd {
|
||||||
if s.conf_bool("ojoin_op", true) {
|
if s.conf_bool("ojoin_op", true) {
|
||||||
crate::coremods::core_mode::svs_set_chan_modes(s, &chan, "+o", &[nick.clone()]);
|
crate::coremods::core_mode::svs_set_chan_modes(s, &chan, "+o", &[nick.clone()]);
|
||||||
}
|
}
|
||||||
s.snotice(&format!("{nick} used OJOIN to enter {chan}"));
|
s.snotice_c('v', &format!("{nick} used OJOIN to enter {chan}"));
|
||||||
CmdResult::Ok
|
CmdResult::Ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ pub fn handle(s: &mut Server, action: &str, params: &str) -> Result<String, RpcE
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
crate::socketengine::connect_link(&addr, tx, counter, max_line)
|
crate::socketengine::connect_link(&addr, tx, counter, max_line)
|
||||||
});
|
});
|
||||||
s.snotice(&format!("RPC initiated a link to {}", b.name));
|
s.snotice_c('l', &format!("RPC initiated a link to {}", b.name));
|
||||||
Ok(obj(&[("result", "true".into())]))
|
Ok(obj(&[("result", "true".into())]))
|
||||||
}
|
}
|
||||||
"disconnect" => {
|
"disconnect" => {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ impl Module for Snoop {
|
||||||
.map(|u| (u.nick.clone(), u.ident.clone(), u.host.clone()));
|
.map(|u| (u.nick.clone(), u.ident.clone(), u.host.clone()));
|
||||||
if let Some((nick, ident, host)) = info {
|
if let Some((nick, ident, host)) = info {
|
||||||
eprintln!("[snoop] connect {nick} ({ident}@{host})");
|
eprintln!("[snoop] connect {nick} ({ident}@{host})");
|
||||||
srv.snotice(&format!("Client connecting: {nick} ({ident}@{host})"));
|
srv.snotice_c('c', &format!("Client connecting: {nick} ({ident}@{host})"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn on_join(&mut self, srv: &mut Server, uid: Uid, chan: &str) {
|
fn on_join(&mut self, srv: &mut Server, uid: Uid, chan: &str) {
|
||||||
|
|
@ -30,7 +30,7 @@ impl Module for Snoop {
|
||||||
let nick = srv.users.get(&uid).map(|u| u.nick.clone());
|
let nick = srv.users.get(&uid).map(|u| u.nick.clone());
|
||||||
eprintln!("[snoop] quit uid={uid} ({reason})");
|
eprintln!("[snoop] quit uid={uid} ({reason})");
|
||||||
if let Some(nick) = nick {
|
if let Some(nick) = nick {
|
||||||
srv.snotice(&format!("Client exiting: {nick} ({reason})"));
|
srv.snotice_c('q', &format!("Client exiting: {nick} ({reason})"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ pub const RPL_WHOISBOT: u16 = 335; // "is a bot" (umode +B)
|
||||||
pub const RPL_WHOISACCOUNT: u16 = 330; // "<nick> <account> :is logged in as"
|
pub const RPL_WHOISACCOUNT: u16 = 330; // "<nick> <account> :is logged in as"
|
||||||
pub const RPL_WHOISREGNICK: u16 = 307; // "is a registered nick" (identified to an account)
|
pub const RPL_WHOISREGNICK: u16 = 307; // "is a registered nick" (identified to an account)
|
||||||
pub const RPL_WHOISMODES: u16 = 379; // oper/self-only: "is using modes +<umodes>"
|
pub const RPL_WHOISMODES: u16 = 379; // oper/self-only: "is using modes +<umodes>"
|
||||||
|
pub const RPL_SNOMASKIS: u16 = 8; // "+<mask> :Server notice mask" after a +s change
|
||||||
pub const ERR_NEEDREGGEDNICK: u16 = 477; // chan +R/+M — must be logged into an account
|
pub const ERR_NEEDREGGEDNICK: u16 = 477; // chan +R/+M — must be logged into an account
|
||||||
pub const RPL_WHOISHOST: u16 = 378; // oper-only: real host/ip behind a cloak
|
pub const RPL_WHOISHOST: u16 = 378; // oper-only: real host/ip behind a cloak
|
||||||
pub const RPL_WHOISSECURE: u16 = 671; // "is using a secure connection" (sslinfo)
|
pub const RPL_WHOISSECURE: u16 = 671; // "is using a secure connection" (sslinfo)
|
||||||
|
|
|
||||||
|
|
@ -843,12 +843,19 @@ impl Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a server notice to every operator who has snomask (+s) on.
|
/// Send a server notice to every operator who has snomask (+s) on.
|
||||||
|
/// A server notice in the general `a` (announcement) category.
|
||||||
pub fn snotice(&self, msg: &str) {
|
pub fn snotice(&self, msg: &str) {
|
||||||
|
self.snotice_c('a', msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A server notice tagged with snomask category `cat` — only opers whose snomask
|
||||||
|
/// (`+s`) subscribes to that letter receive it. Logging tees are unconditional.
|
||||||
|
pub fn snotice_c(&self, cat: char, msg: &str) {
|
||||||
self.log_push(msg);
|
self.log_push(msg);
|
||||||
let opers: Vec<Uid> = self
|
let opers: Vec<Uid> = self
|
||||||
.users
|
.users
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, u)| u.flags.oper && u.flags.snomask)
|
.filter(|(_, u)| u.flags.oper && u.flags.snomask_cats.contains(cat))
|
||||||
.map(|(&u, _)| u)
|
.map(|(&u, _)| u)
|
||||||
.collect();
|
.collect();
|
||||||
let jval = self.json_log_value(msg, &opers);
|
let jval = self.json_log_value(msg, &opers);
|
||||||
|
|
|
||||||
12
src/users.rs
12
src/users.rs
|
|
@ -11,6 +11,12 @@ use crate::server::{Server, VERSION};
|
||||||
use crate::socketengine::OutSink;
|
use crate::socketengine::OutSink;
|
||||||
use crate::Uid;
|
use crate::Uid;
|
||||||
|
|
||||||
|
/// Snomask category letters an oper subscribes to (the standard set): a announce,
|
||||||
|
/// c connect, d dnsbl, f filter, g globops, j chancreate, k kill, l link, n nick,
|
||||||
|
/// o oper, q quit, r rehash, t stats, u acctreg, v override, w gateway, x xline.
|
||||||
|
/// Opers get all of them by default and narrow with `+s -c` etc.
|
||||||
|
pub const DEFAULT_SNOMASK: &str = "acdfgjklnoqrtuvwx";
|
||||||
|
|
||||||
/// User modes and session flags. Kept in one `Default` bag so adding a mode
|
/// User modes and session flags. Kept in one `Default` bag so adding a mode
|
||||||
/// doesn't ripple through every `User { .. }` constructor.
|
/// doesn't ripple through every `User { .. }` constructor.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -27,6 +33,7 @@ pub struct UserFlags {
|
||||||
pub reg_only_pm: bool, // +R (only accept PMs from logged-in users)
|
pub reg_only_pm: bool, // +R (only accept PMs from logged-in users)
|
||||||
pub ssl_pm: bool, // +z (only accept PMs from TLS users)
|
pub ssl_pm: bool, // +z (only accept PMs from TLS users)
|
||||||
pub snomask: bool, // +s (oper: receive server notices)
|
pub snomask: bool, // +s (oper: receive server notices)
|
||||||
|
pub snomask_cats: String, // +s snomask category letters this oper is subscribed to
|
||||||
pub callerid: bool, // +g (only accept PMs from users on the ACCEPT list)
|
pub callerid: bool, // +g (only accept PMs from users on the ACCEPT list)
|
||||||
pub showwhois: bool, // +W (get a notice when someone WHOISes you)
|
pub showwhois: bool, // +W (get a notice when someone WHOISes you)
|
||||||
pub helpop: bool, // +h (helpop: available for help; shown in WHOIS)
|
pub helpop: bool, // +h (helpop: available for help; shown in WHOIS)
|
||||||
|
|
@ -351,6 +358,7 @@ impl Server {
|
||||||
if let Some(u) = self.users.get_mut(&uid) {
|
if let Some(u) = self.users.get_mut(&uid) {
|
||||||
u.flags.oper = true;
|
u.flags.oper = true;
|
||||||
u.flags.snomask = true; // opers get server notices by default
|
u.flags.snomask = true; // opers get server notices by default
|
||||||
|
u.flags.snomask_cats = DEFAULT_SNOMASK.to_string(); // all categories
|
||||||
}
|
}
|
||||||
let nick = self
|
let nick = self
|
||||||
.users
|
.users
|
||||||
|
|
@ -359,7 +367,7 @@ impl Server {
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
self.numeric(uid, RPL_YOUREOPER, ":You are now an IRC operator");
|
self.numeric(uid, RPL_YOUREOPER, ":You are now an IRC operator");
|
||||||
self.send(uid, format!(":{} MODE {nick} :+os", self.name));
|
self.send(uid, format!(":{} MODE {nick} :+os", self.name));
|
||||||
self.snotice(&format!("{nick} is now an IRC operator"));
|
self.snotice_c('o', &format!("{nick} is now an IRC operator"));
|
||||||
// operprefix: give this oper the ! prefix in every channel they're already in
|
// operprefix: give this oper the ! prefix in every channel they're already in
|
||||||
crate::modules::operprefix::grant_all(self, uid);
|
crate::modules::operprefix::grant_all(self, uid);
|
||||||
// opermodes: extra umodes on oper-up
|
// opermodes: extra umodes on oper-up
|
||||||
|
|
@ -438,7 +446,7 @@ impl Server {
|
||||||
self.send(t, line.clone());
|
self.send(t, line.clone());
|
||||||
}
|
}
|
||||||
if self.conf_bool("seenicks", false) {
|
if self.conf_bool("seenicks", false) {
|
||||||
self.snotice(&format!("{old} is now known as {newnick}"));
|
self.snotice_c('n', &format!("{old} is now known as {newnick}"));
|
||||||
}
|
}
|
||||||
// WATCH/MONITOR: the old nick is now gone, the new one is here
|
// WATCH/MONITOR: the old nick is now gone, the new one is here
|
||||||
self.watch_notify_offline(&old);
|
self.watch_notify_offline(&old);
|
||||||
|
|
|
||||||
|
|
@ -261,7 +261,7 @@ impl Server {
|
||||||
setter: setter.to_string(),
|
setter: setter.to_string(),
|
||||||
expires: if duration == 0 { 0 } else { n + duration },
|
expires: if duration == 0 { 0 } else { n + duration },
|
||||||
});
|
});
|
||||||
self.snotice(&format!(
|
self.snotice_c('x', &format!(
|
||||||
"{setter} added a {}-line on {mask}: {reason}",
|
"{setter} added a {}-line on {mask}: {reason}",
|
||||||
kind.tag()
|
kind.tag()
|
||||||
));
|
));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue