add +k servprotect umode (server-set-only, advertised in 004/umodes): blocks KILL, KICK and SA* on a network service; tracked for remote services too
This commit is contained in:
parent
91022cffe8
commit
b7600ee5aa
5 changed files with 69 additions and 1 deletions
|
|
@ -506,6 +506,15 @@ impl Command for Kick {
|
||||||
);
|
);
|
||||||
return CmdResult::Fail;
|
return CmdResult::Fail;
|
||||||
}
|
}
|
||||||
|
// servprotect (+k): a network service can't be kicked
|
||||||
|
if s.uid_servprotected(tuid) {
|
||||||
|
s.numeric(
|
||||||
|
uid,
|
||||||
|
ERR_CHANOPRIVSNEEDED,
|
||||||
|
&format!("{chan} :You cannot kick a network service"),
|
||||||
|
);
|
||||||
|
return CmdResult::Fail;
|
||||||
|
}
|
||||||
// can't kick someone who out-ranks you
|
// can't kick someone who out-ranks you
|
||||||
if s.rank(uid, &key) < s.rank(tuid, &key) {
|
if s.rank(uid, &key) < s.rank(tuid, &key) {
|
||||||
s.numeric(
|
s.numeric(
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ pub fn svs_set_user_modes(s: &mut Server, tuid: Uid, modestring: &str) {
|
||||||
let mut sign = '+';
|
let mut sign = '+';
|
||||||
let mut applied = String::new();
|
let mut applied = String::new();
|
||||||
let mut last = ' ';
|
let mut last = ' ';
|
||||||
|
s.mode_sudo = true; // services authority — allows server-only modes like +k
|
||||||
for c in modestring.chars() {
|
for c in modestring.chars() {
|
||||||
if c == '+' || c == '-' {
|
if c == '+' || c == '-' {
|
||||||
sign = c;
|
sign = c;
|
||||||
|
|
@ -40,6 +41,7 @@ pub fn svs_set_user_modes(s: &mut Server, tuid: Uid, modestring: &str) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
s.mode_sudo = false;
|
||||||
if !applied.is_empty() {
|
if !applied.is_empty() {
|
||||||
let nick = s
|
let nick = s
|
||||||
.users
|
.users
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,11 @@ impl Command for Kill {
|
||||||
);
|
);
|
||||||
return CmdResult::Fail;
|
return CmdResult::Fail;
|
||||||
};
|
};
|
||||||
|
// servprotect (+k): a network service can't be killed
|
||||||
|
if s.uid_servprotected(tuid) {
|
||||||
|
s.numeric(uid, ERR_NOPRIVILEGES, ":You cannot KILL a network service");
|
||||||
|
return CmdResult::Fail;
|
||||||
|
}
|
||||||
// operlevels: a lower-level oper can't KILL a higher-level oper
|
// operlevels: a lower-level oper can't KILL a higher-level oper
|
||||||
if let Some(reason) = crate::modules::operlevels::deny_kill(s, uid, tuid) {
|
if let Some(reason) = crate::modules::operlevels::deny_kill(s, uid, tuid) {
|
||||||
s.numeric(uid, ERR_NOPRIVILEGES, &format!(":{reason}"));
|
s.numeric(uid, ERR_NOPRIVILEGES, &format!(":{reason}"));
|
||||||
|
|
@ -417,6 +422,7 @@ impl Command for SaNick {
|
||||||
);
|
);
|
||||||
return CmdResult::Fail;
|
return CmdResult::Fail;
|
||||||
};
|
};
|
||||||
|
if s.uid_servprotected(tuid) { s.numeric(uid, ERR_NOPRIVILEGES, ":Cannot use an SA command on a network service"); return CmdResult::Fail; }
|
||||||
let newnick = ¶ms[1];
|
let newnick = ¶ms[1];
|
||||||
if !valid_nick(newnick, s.conf_num("maxnick", 30usize)) {
|
if !valid_nick(newnick, s.conf_num("maxnick", 30usize)) {
|
||||||
s.numeric(
|
s.numeric(
|
||||||
|
|
@ -1174,6 +1180,10 @@ impl Command for SaKick {
|
||||||
);
|
);
|
||||||
return CmdResult::Fail;
|
return CmdResult::Fail;
|
||||||
}
|
}
|
||||||
|
if s.uid_servprotected(tuid) {
|
||||||
|
s.numeric(uid, ERR_NOPRIVILEGES, ":Cannot use an SA command on a network service");
|
||||||
|
return CmdResult::Fail;
|
||||||
|
}
|
||||||
let reason = params
|
let reason = params
|
||||||
.get(2)
|
.get(2)
|
||||||
.cloned()
|
.cloned()
|
||||||
|
|
@ -1221,6 +1231,7 @@ impl Command for SaQuit {
|
||||||
.get(1)
|
.get(1)
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_else(|| "Services forced quit".to_string());
|
.unwrap_or_else(|| "Services forced quit".to_string());
|
||||||
|
if s.uid_servprotected(tuid) { s.numeric(uid, ERR_NOPRIVILEGES, ":Cannot use an SA command on a network service"); return CmdResult::Fail; }
|
||||||
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);
|
||||||
|
|
|
||||||
21
src/mode.rs
21
src/mode.rs
|
|
@ -1165,6 +1165,7 @@ static USER_MODES: &[&(dyn UserMode + Sync)] = &[
|
||||||
&HIDEOPER,
|
&HIDEOPER,
|
||||||
®DEAF,
|
®DEAF,
|
||||||
®ISTERED,
|
®ISTERED,
|
||||||
|
&SERVPROTECT,
|
||||||
&SSLPM,
|
&SSLPM,
|
||||||
&SNOMASK,
|
&SNOMASK,
|
||||||
&CALLERID,
|
&CALLERID,
|
||||||
|
|
@ -1406,6 +1407,26 @@ impl UserMode for RegisteredMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `+k` — servprotect. Set only by a linked server / services (under `mode_sudo`);
|
||||||
|
/// a client can never toggle it. Marks the user as protected from KILL/KICK/SA*.
|
||||||
|
struct ServProtect;
|
||||||
|
static SERVPROTECT: ServProtect = ServProtect;
|
||||||
|
impl UserMode for ServProtect {
|
||||||
|
fn letter(&self) -> char {
|
||||||
|
'k'
|
||||||
|
}
|
||||||
|
fn apply(&self, s: &mut Server, uid: Uid, adding: bool) -> bool {
|
||||||
|
if !s.mode_sudo {
|
||||||
|
return false; // server/services-only
|
||||||
|
}
|
||||||
|
if let Some(u) = s.users.get_mut(&uid) {
|
||||||
|
u.flags.servprotect = adding;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `+s` — server-notice (snomask) receiver. Oper-only to set; anyone may drop it.
|
/// `+s` — server-notice (snomask) receiver. Oper-only to set; anyone may drop it.
|
||||||
static SNOMASK: OperFlag = OperFlag {
|
static SNOMASK: OperFlag = OperFlag {
|
||||||
ch: 's',
|
ch: 's',
|
||||||
|
|
|
||||||
27
src/users.rs
27
src/users.rs
|
|
@ -39,6 +39,7 @@ pub struct UserFlags {
|
||||||
pub helpop: bool, // +h (helpop: available for help; shown in WHOIS)
|
pub helpop: bool, // +h (helpop: available for help; shown in WHOIS)
|
||||||
pub deny_uncommon: bool, // +c (only users sharing a channel may PM you)
|
pub deny_uncommon: bool, // +c (only users sharing a channel may PM you)
|
||||||
pub nick_locked: bool, // NICKLOCK: services/oper holds this nick (no self-change)
|
pub nick_locked: bool, // NICKLOCK: services/oper holds this nick (no self-change)
|
||||||
|
pub servprotect: bool, // +k (services-only: can't be KILLed/KICKed/SA-commanded)
|
||||||
pub via_webirc: bool, // connected through a WEBIRC gateway (securitygroups)
|
pub via_webirc: bool, // connected through a WEBIRC gateway (securitygroups)
|
||||||
pub via_websocket: bool, // connected over the WebSocket transport (ws://, wss://)
|
pub via_websocket: bool, // connected over the WebSocket transport (ws://, wss://)
|
||||||
pub away: Option<String>, // AWAY message, if set
|
pub away: Option<String>, // AWAY message, if set
|
||||||
|
|
@ -62,6 +63,9 @@ impl UserFlags {
|
||||||
if self.bot {
|
if self.bot {
|
||||||
s.push('B');
|
s.push('B');
|
||||||
}
|
}
|
||||||
|
if self.servprotect {
|
||||||
|
s.push('k');
|
||||||
|
}
|
||||||
if self.deaf {
|
if self.deaf {
|
||||||
s.push('D');
|
s.push('D');
|
||||||
}
|
}
|
||||||
|
|
@ -353,6 +357,27 @@ impl Server {
|
||||||
self.users.get(&uid).map(|u| u.flags.oper).unwrap_or(false)
|
self.users.get(&uid).map(|u| u.flags.oper).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the user `uid` is servprotected (+k) — a service that must not be
|
||||||
|
/// KILLed / KICKed / SA-commanded.
|
||||||
|
pub fn uid_servprotected(&self, uid: Uid) -> bool {
|
||||||
|
self.users
|
||||||
|
.get(&uid)
|
||||||
|
.map(|u| u.flags.servprotect)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the nick `n` (local user or a remote services pseudo-client) is
|
||||||
|
/// servprotected (+k). Remote users carry their modes as a letter string.
|
||||||
|
pub fn nick_servprotected(&self, n: &str) -> bool {
|
||||||
|
if let Some(uid) = self.find_nick(n) {
|
||||||
|
return self.uid_servprotected(uid);
|
||||||
|
}
|
||||||
|
self.find_remote(n)
|
||||||
|
.and_then(|(uuid, _)| self.remote_users.get(&uuid))
|
||||||
|
.map(|ru| ru.modes.contains('k'))
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
/// Grant IRC-operator status and tell the user.
|
/// Grant IRC-operator status and tell the user.
|
||||||
pub fn oper_up(&mut self, uid: Uid) {
|
pub fn oper_up(&mut self, uid: Uid) {
|
||||||
if let Some(u) = self.users.get_mut(&uid) {
|
if let Some(u) = self.users.get_mut(&uid) {
|
||||||
|
|
@ -485,7 +510,7 @@ impl Server {
|
||||||
uid,
|
uid,
|
||||||
RPL_MYINFO,
|
RPL_MYINFO,
|
||||||
&format!(
|
&format!(
|
||||||
"{} echoircd-{VERSION} iowxsgBDIHrRzWhc qaohvbeIklimnpstzCTcSNORMfjFLgGuBQAPJUdKXwD",
|
"{} echoircd-{VERSION} iowxsgBkDIHrRzWhc qaohvbeIklimnpstzCTcSNORMfjFLgGuBQAPJUdKXwD",
|
||||||
self.name
|
self.name
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue