From 0da0906dc9ffc57b35f21a7a8d517e41525cfade Mon Sep 17 00:00:00 2001 From: reverse Date: Mon, 17 Aug 2026 21:37:18 +0000 Subject: [PATCH] =?UTF-8?q?core:=20fire=20on=5Fuser=5Fquit=20for=20unregis?= =?UTF-8?q?tered=20users=20too=20=E2=80=94=20pre-registration=20module=20s?= =?UTF-8?q?tate=20(captcha=20Challenged/Verified)=20leaked=20one=20Uid=20p?= =?UTF-8?q?er=20held-then-dropped=20bot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ircd.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ircd.rs b/src/ircd.rs index 30125de..f8a5aef 100644 --- a/src/ircd.rs +++ b/src/ircd.rs @@ -623,12 +623,13 @@ impl Ircd { if !self.server.users.contains_key(&uid) { return; } - let registered = self.server.users[&uid].registered; - if registered { - // fire the quit hook while the user still exists - for m in &mut self.modules { - m.on_user_quit(&mut self.server, uid, reason); - } + // Fire the quit hook while the user still exists — for EVERY user, registered + // or not. A client that disconnects mid-registration (e.g. a captcha bot held + // before registration) still has per-uid module state to reclaim, and Uids are + // never reused, so skipping this leaks one entry per such disconnect (which + // scales with exactly the hostile traffic the captcha/challenge modules target). + for m in &mut self.modules { + m.on_user_quit(&mut self.server, uid, reason); } self.server.remove_user(uid, reason); }