operlevels: oper = <name> <pass> <level>; a lower-level oper can't KILL a higher-level one
This commit is contained in:
parent
3b2f30965a
commit
3b43abc88a
7 changed files with 64 additions and 8 deletions
|
|
@ -70,7 +70,7 @@ pub struct Config {
|
||||||
pub tls_cert: Option<String>, // PEM certificate chain
|
pub tls_cert: Option<String>, // PEM certificate chain
|
||||||
pub tls_key: Option<String>, // PEM private key
|
pub tls_key: Option<String>, // PEM private key
|
||||||
pub motd: Vec<String>,
|
pub motd: Vec<String>,
|
||||||
pub opers: Vec<(String, String)>, // (name, password)
|
pub opers: Vec<(String, String, u32)>, // (name, password, operlevel)
|
||||||
pub cloak_key: Option<String>, // secret key for host cloaking (+x); None = off
|
pub cloak_key: Option<String>, // secret key for host cloaking (+x); None = off
|
||||||
pub sid: String, // this server's 3-char server id (S2S)
|
pub sid: String, // this server's 3-char server id (S2S)
|
||||||
pub serverdesc: String, // this server's description
|
pub serverdesc: String, // this server's description
|
||||||
|
|
@ -197,7 +197,8 @@ impl Config {
|
||||||
"oper" => {
|
"oper" => {
|
||||||
let mut it = v.split_whitespace();
|
let mut it = v.split_whitespace();
|
||||||
if let (Some(n), Some(p)) = (it.next(), it.next()) {
|
if let (Some(n), Some(p)) = (it.next(), it.next()) {
|
||||||
c.opers.push((n.to_string(), p.to_string()));
|
let level = it.next().and_then(|l| l.parse().ok()).unwrap_or(0);
|
||||||
|
c.opers.push((n.to_string(), p.to_string(), level));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// +G censor word: `badword = <find> [replace]` (no replace ⇒ block)
|
// +G censor word: `badword = <find> [replace]` (no replace ⇒ block)
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,7 @@ impl Command for Stats {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
'o' => {
|
'o' => {
|
||||||
let opers: Vec<String> = s.opers.iter().map(|(n, _)| n.clone()).collect();
|
let opers: Vec<String> = s.opers.iter().map(|(n, _, _)| n.clone()).collect();
|
||||||
for n in opers {
|
for n in opers {
|
||||||
s.numeric(uid, RPL_STATSOLINE, &format!("O * * {n} :oper"));
|
s.numeric(uid, RPL_STATSOLINE, &format!("O * * {n} :oper"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -122,11 +122,14 @@ impl Command for Oper {
|
||||||
}
|
}
|
||||||
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
||||||
let (name, pass) = (¶ms[0], ¶ms[1]);
|
let (name, pass) = (¶ms[0], ¶ms[1]);
|
||||||
if s.opers
|
let level = s
|
||||||
|
.opers
|
||||||
.iter()
|
.iter()
|
||||||
.any(|(n, p)| n == name && crate::modules::password_hash::verify(p, pass))
|
.find(|(n, p, _)| n == name && crate::modules::password_hash::verify(p, pass))
|
||||||
{
|
.map(|(_, _, lvl)| *lvl);
|
||||||
|
if let Some(level) = level {
|
||||||
s.oper_up(uid);
|
s.oper_up(uid);
|
||||||
|
crate::modules::operlevels::set(s, uid, level); // operlevels: KILL protection
|
||||||
CmdResult::Ok
|
CmdResult::Ok
|
||||||
} else {
|
} else {
|
||||||
s.numeric(uid, ERR_PASSWDMISMATCH, ":Password incorrect");
|
s.numeric(uid, ERR_PASSWDMISMATCH, ":Password incorrect");
|
||||||
|
|
@ -165,6 +168,11 @@ impl Command for Kill {
|
||||||
);
|
);
|
||||||
return CmdResult::Fail;
|
return CmdResult::Fail;
|
||||||
};
|
};
|
||||||
|
// operlevels: a lower-level oper can't KILL a higher-level oper
|
||||||
|
if let Some(reason) = crate::modules::operlevels::deny_kill(s, uid, tuid) {
|
||||||
|
s.numeric(uid, ERR_NOPRIVILEGES, &format!(":{reason}"));
|
||||||
|
return CmdResult::Fail;
|
||||||
|
}
|
||||||
let killer = s
|
let killer = s
|
||||||
.users
|
.users
|
||||||
.get(&uid)
|
.get(&uid)
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ pub mod metadata;
|
||||||
pub mod multiline;
|
pub mod multiline;
|
||||||
pub mod network_icon;
|
pub mod network_icon;
|
||||||
pub mod ojoin;
|
pub mod ojoin;
|
||||||
|
pub mod operlevels;
|
||||||
pub mod operprefix;
|
pub mod operprefix;
|
||||||
pub mod password_hash;
|
pub mod password_hash;
|
||||||
pub mod profilelink;
|
pub mod profilelink;
|
||||||
|
|
|
||||||
46
src/modules/operlevels.rs
Normal file
46
src/modules/operlevels.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
//! operlevels — each `oper` block may carry a numeric level (`oper = <name> <pass>
|
||||||
|
//! <level>`, default 0). A lower-level oper cannot KILL a higher-level oper. The
|
||||||
|
//! level is stored on the user at OPER; with all levels at the default it's inert,
|
||||||
|
//! so no config flag is needed.
|
||||||
|
|
||||||
|
use crate::server::Server;
|
||||||
|
use crate::Uid;
|
||||||
|
|
||||||
|
/// The oper level stored on a user's `ext`.
|
||||||
|
struct OperLevel(u32);
|
||||||
|
|
||||||
|
/// Record `uid`'s oper level (called from the OPER handler after oper-up).
|
||||||
|
pub fn set(s: &mut Server, uid: Uid, level: u32) {
|
||||||
|
if let Some(u) = s.users.get_mut(&uid) {
|
||||||
|
*u.ext.get_or_insert_with(|| OperLevel(0)) = OperLevel(level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A user's oper level (0 if unset / not an oper).
|
||||||
|
pub fn level(s: &Server, uid: Uid) -> u32 {
|
||||||
|
s.users
|
||||||
|
.get(&uid)
|
||||||
|
.and_then(|u| u.ext.get::<OperLevel>())
|
||||||
|
.map(|l| l.0)
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The reason `actor` may not KILL `target` under operlevels, if any: only when the
|
||||||
|
/// target is an oper of a strictly higher level than the actor.
|
||||||
|
pub fn deny_kill(s: &Server, actor: Uid, target: Uid) -> Option<String> {
|
||||||
|
if !s.is_oper(target) {
|
||||||
|
return None; // non-opers aren't protected
|
||||||
|
}
|
||||||
|
if level(s, actor) < level(s, target) {
|
||||||
|
let tnick = s
|
||||||
|
.users
|
||||||
|
.get(&target)
|
||||||
|
.map(|u| u.nick.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
Some(format!(
|
||||||
|
"Permission Denied- {tnick} outranks you (higher oper level)"
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,7 +25,7 @@ pub fn handle(s: &mut Server, method: &str, _params: &str) -> Result<String, Rpc
|
||||||
let opers: Vec<String> = s
|
let opers: Vec<String> = s
|
||||||
.opers
|
.opers
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, _pass)| {
|
.map(|(name, _pass, _lvl)| {
|
||||||
obj(&[
|
obj(&[
|
||||||
("name", qstr(name)),
|
("name", qstr(name)),
|
||||||
("type", qstr("")),
|
("type", qstr("")),
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ pub struct Server {
|
||||||
pub nick_index: HashMap<String, Uid>, // lower nick -> uid
|
pub nick_index: HashMap<String, Uid>, // lower nick -> uid
|
||||||
pub channels: HashMap<String, Channel>, // lower name -> channel
|
pub channels: HashMap<String, Channel>, // lower name -> channel
|
||||||
pub events: VecDeque<Hook>,
|
pub events: VecDeque<Hook>,
|
||||||
pub opers: Vec<(String, String)>, // (name, password) from config
|
pub opers: Vec<(String, String, u32)>, // (name, password, operlevel) from config
|
||||||
pub cloak_key: Option<String>, // host-cloaking key (see modules::cloak)
|
pub cloak_key: Option<String>, // host-cloaking key (see modules::cloak)
|
||||||
pub line_ctags: String, // client-only tags of the line being handled
|
pub line_ctags: String, // client-only tags of the line being handled
|
||||||
// --- server-to-server (see crate::link) ---
|
// --- server-to-server (see crate::link) ---
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue