echoIRCd/src/modules/snoop.rs

61 lines
2.4 KiB
Rust

//! A tiny example module: log connects, joins and quits to stderr. It exercises
//! the hook wiring end-to-end and is the template for real modules.
use crate::module::Module;
use crate::server::Server;
use crate::Uid;
pub struct Snoop;
impl Module for Snoop {
fn name(&self) -> &'static str {
"snoop"
}
fn on_user_connect(&mut self, srv: &mut Server, uid: Uid) {
// `conf_bool`/`snotice_c` are `&self`, so we can hold the `&User` borrow and
// reference its fields directly instead of cloning them out.
let Some(u) = srv.users.get(&uid) else {
return;
};
if srv.conf_bool("snoop_stderr", false) {
eprintln!("[snoop] connect {} ({}@{})", u.nick, u.ident, u.host);
}
// port is always shown; sni/account only when present, so plaintext or
// anonymous connects don't carry empty fields.
let mut extra = format!(", port: {}", u.port);
if let Some(sni) = &u.sni {
extra.push_str(&format!(", sni: {sni}"));
}
if let Some(acct) = &u.account {
extra.push_str(&format!(", account: {acct}"));
}
let msg = format!("Client connecting: {} ({}@{}){extra}", u.nick, u.ident, u.host);
srv.snotice_c('c', &msg);
}
fn on_join(&mut self, srv: &mut Server, uid: Uid, chan: &str) {
if srv.conf_bool("snoop_stderr", false) {
if let Some(u) = srv.users.get(&uid) {
eprintln!("[snoop] {} joined {chan}", u.nick);
}
}
}
fn on_user_quit(&mut self, srv: &mut Server, uid: Uid, reason: &str) {
// Only announce clients that actually registered. A health/liveness probe — or
// any client that drops mid-handshake — never fired a connect notice, so it must
// not fire an exit notice either, else it spams the +q snomask on every probe.
// (on_user_quit itself still fires for unregistered users so modules reclaim
// their per-uid state; only this operator-facing notice is gated.)
let Some(nick) = srv
.users
.get(&uid)
.filter(|u| u.registered)
.map(|u| u.nick.clone())
else {
return;
};
if srv.conf_bool("snoop_stderr", false) {
eprintln!("[snoop] quit uid={uid} ({reason})");
}
srv.snotice_c('q', &format!("Client exiting: {nick} ({reason})"));
}
}