protocol: verify echos pseudoclients request servprotect (+k, by name from CAPAB USERMODES) so they cannot be killed by operators; warn if service_modes lacks it
All checks were successful
CI / check (push) Successful in 3m45s
All checks were successful
CI / check (push) Successful in 3m45s
This commit is contained in:
parent
2f70b0c133
commit
c64c3820a7
3 changed files with 44 additions and 0 deletions
|
|
@ -77,6 +77,10 @@ pub enum NetEvent {
|
|||
ModulesAvailable { modules: Vec<String> },
|
||||
// `CAPAB END` — the module lists are complete; verify dependencies now.
|
||||
CapabEnd,
|
||||
// The ircd's `servprotect` user mode (from CAPAB USERMODES) and whether echo's
|
||||
// configured service_modes request it — a services pseudoclient without it can
|
||||
// be killed/hijacked by an operator.
|
||||
ServProtect { letter: char, requested: bool },
|
||||
// A server split away (SQUIT). `server` is its SID; every user behind it — and
|
||||
// behind any server in its subtree — is gone, since a split is signalled once
|
||||
// rather than as a QUIT per user.
|
||||
|
|
|
|||
|
|
@ -347,6 +347,14 @@ impl Protocol for InspIrcd {
|
|||
vec![NetEvent::ModulesAvailable { modules }]
|
||||
}
|
||||
}
|
||||
// CAPAB USERMODES: we only need servprotect (+k) — verify our own
|
||||
// pseudoclients request it, so they can't be killed by operators.
|
||||
Some(s) if s.eq_ignore_ascii_case("USERMODES") => {
|
||||
match trailing(rest).split_whitespace().find_map(servprotect_letter) {
|
||||
Some(letter) => vec![NetEvent::ServProtect { letter, requested: self.service_modes.contains(letter) }],
|
||||
None => vec![],
|
||||
}
|
||||
}
|
||||
Some(s) if s.eq_ignore_ascii_case("END") => vec![NetEvent::CapabEnd],
|
||||
_ => vec![],
|
||||
},
|
||||
|
|
@ -585,6 +593,18 @@ fn parse_extban_cap(token: &str) -> Option<echo_api::ExtbanCap> {
|
|||
(!name.is_empty()).then(|| echo_api::ExtbanCap { name: name.to_string(), letter, acting })
|
||||
}
|
||||
|
||||
// The mode letter of the `servprotect` user mode in a CAPAB USERMODES token
|
||||
// (`simple:servprotect=k`), or None for any other mode.
|
||||
fn servprotect_letter(token: &str) -> Option<char> {
|
||||
let (_ty, spec) = token.split_once(':')?;
|
||||
let (name, val) = spec.split_once('=')?;
|
||||
if name.eq_ignore_ascii_case("servprotect") {
|
||||
val.chars().next()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize a `CAPAB MODULES` token (`m_foo.so=data` / `foo=data` / `foo`) to the
|
||||
// bare module name, matching the ircd's own naming.
|
||||
fn module_name(token: &str) -> String {
|
||||
|
|
@ -682,6 +702,18 @@ mod tests {
|
|||
assert!(p.parse("CAPAB CHANMODES :ban=b").is_empty(), "other CAPAB subcommands ignored");
|
||||
}
|
||||
|
||||
// CAPAB USERMODES surfaces servprotect and whether our service_modes request it
|
||||
// (proto()'s service_modes includes k). Only servprotect is extracted.
|
||||
#[test]
|
||||
fn parses_capab_usermodes_servprotect() {
|
||||
let mut p = proto();
|
||||
assert!(matches!(p.parse("CAPAB USERMODES :simple:invisible=i simple:servprotect=k param:snomask=s").as_slice(),
|
||||
[NetEvent::ServProtect { letter: 'k', requested: true }]));
|
||||
assert!(matches!(p.parse("CAPAB USERMODES :simple:servprotect=Z").as_slice(),
|
||||
[NetEvent::ServProtect { letter: 'Z', requested: false }]), "char not in service_modes -> not requested");
|
||||
assert!(p.parse("CAPAB USERMODES :simple:invisible=i simple:wallops=w").is_empty(), "no servprotect -> nothing");
|
||||
}
|
||||
|
||||
// CAPAB MODULES/MODSUPPORT surface normalized module names; CAPAB END triggers
|
||||
// the dependency check. Names are stripped of m_/.so/=data.
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -1124,6 +1124,14 @@ impl Engine {
|
|||
verify_ircd_dependencies(&self.network);
|
||||
Vec::new()
|
||||
}
|
||||
NetEvent::ServProtect { letter, requested } => {
|
||||
if requested {
|
||||
tracing::info!(mode = %letter, "services are servprotected (+{letter})");
|
||||
} else {
|
||||
tracing::warn!(mode = %letter, "service_modes does not request servprotect (+{letter}) — echo's pseudoclients can be killed or hijacked by operators; add it to [server] service_modes");
|
||||
}
|
||||
Vec::new()
|
||||
}
|
||||
NetEvent::Casemapping { name } => {
|
||||
// echo folds identifiers as ascii. Verify the ircd agrees, rather than
|
||||
// assuming it silently — a mismatch would desync account/channel identity.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue