Introduce pseudo-clients with proper services user modes
All checks were successful
CI / check (push) Successful in 3m43s

Services and bots were introduced with only +i. Give them the standard services
umodes, configurable via [server] service_modes (default "iHkBT"): invisible,
hideoper, servprotect — unkillable/unkickable by opers, which the ircd accepts
because the services server is U-lined — bot, and block-CTCP.

Verified live: ChanServ now whoises as "is a bot" (+B) and every pseudo-client
still links cleanly, so every mode was accepted.
This commit is contained in:
Jean Chevronnet 2026-07-17 00:01:24 +00:00
parent 3f11f77a81
commit 65226aceb7
No known key found for this signature in database
3 changed files with 18 additions and 5 deletions

View file

@ -11,14 +11,17 @@ pub struct InspIrcd {
password: String, password: String,
protocol: u32, protocol: u32,
ts: u64, ts: u64,
// User modes our pseudo-clients (services + bots) are introduced with, e.g.
// "iHkBT" — invisible, hideoper, servprotect (U-line only), bot, block-CTCP.
service_modes: String,
// Monotonic membership id for our own IJOINs. InspIRCd requires a membid on // Monotonic membership id for our own IJOINs. InspIRCd requires a membid on
// IJOIN (`IJOIN <chan> <membid>`); it need only be unique among our members. // IJOIN (`IJOIN <chan> <membid>`); it need only be unique among our members.
membid: u64, membid: u64,
} }
impl InspIrcd { impl InspIrcd {
pub fn new(name: String, description: String, sid: String, password: String, protocol: u32, ts: u64) -> Self { pub fn new(name: String, description: String, sid: String, password: String, protocol: u32, ts: u64, service_modes: String) -> Self {
Self { sid, name, description, password, protocol, ts, membid: 0 } Self { sid, name, description, password, protocol, ts, service_modes, membid: 0 }
} }
fn sourced(&self, cmd: String) -> String { fn sourced(&self, cmd: String) -> String {
@ -298,8 +301,8 @@ impl Protocol for InspIrcd {
// insp4 UID: uuid nickts nick realhost disphost realuser dispuser ip signonts +modes :gecos // insp4 UID: uuid nickts nick realhost disphost realuser dispuser ip signonts +modes :gecos
// (both a real AND a displayed user field — see m_spanningtree/uid.cpp Builder). // (both a real AND a displayed user field — see m_spanningtree/uid.cpp Builder).
NetAction::IntroduceUser { uid, nick, ident, host, gecos } => vec![self.sourced(format!( NetAction::IntroduceUser { uid, nick, ident, host, gecos } => vec![self.sourced(format!(
"UID {uid} {ts} {nick} {host} {host} {ident} {ident} 0.0.0.0 {ts} +i :{gecos}", "UID {uid} {ts} {nick} {host} {host} {ident} {ident} 0.0.0.0 {ts} +{modes} :{gecos}",
uid = uid, ts = self.ts, nick = nick, host = host, ident = ident, gecos = gecos uid = uid, ts = self.ts, nick = nick, host = host, ident = ident, modes = self.service_modes, gecos = gecos
))], ))],
NetAction::Privmsg { from, to, text } => { NetAction::Privmsg { from, to, text } => {
vec![format!(":{} PRIVMSG {} :{}", from, to, text)] vec![format!(":{} PRIVMSG {} :{}", from, to, text)]
@ -488,7 +491,7 @@ mod tests {
use super::*; use super::*;
fn proto() -> InspIrcd { fn proto() -> InspIrcd {
InspIrcd::new("services.test".into(), "Federated Services".into(), "42S".into(), "pw".into(), 1206, 1) InspIrcd::new("services.test".into(), "Federated Services".into(), "42S".into(), "pw".into(), 1206, 1, "iHkBT".into())
} }
// A remote nick change must surface as a NickChange for the source uid, or // A remote nick change must surface as a NickChange for the source uid, or

View file

@ -280,6 +280,15 @@ pub struct Server {
// rather than the generic default. // rather than the generic default.
#[serde(default)] #[serde(default)]
pub service_host: String, pub service_host: String,
// User modes the service pseudo-clients (services + bots) are introduced with.
// Default "iHkBT": invisible, hideoper, servprotect (unkillable — needs the
// services server U-lined), bot, block-CTCP. Set per the ircd's loaded modules.
#[serde(default = "default_service_modes")]
pub service_modes: String,
}
fn default_service_modes() -> String {
"iHkBT".to_string()
} }
fn default_protocol() -> u32 { fn default_protocol() -> u32 {

View file

@ -82,6 +82,7 @@ async fn main() -> Result<()> {
cfg.uplink.password.clone(), cfg.uplink.password.clone(),
cfg.server.protocol, cfg.server.protocol,
ts, ts,
cfg.server.service_modes.clone(),
)); ));
// Bring up the service modules named in [modules] (default: the full // Bring up the service modules named in [modules] (default: the full