From 7563daf3a38a1557870c1055c510505140c51b8a Mon Sep 17 00:00:00 2001 From: reverse Date: Mon, 17 Aug 2026 22:13:37 +0000 Subject: [PATCH] account_registration: GC the per-IP rate-limit table on the tick (an entry per distinct registering IP was never dropped) --- src/modules/account_registration.rs | 21 +++++++++++++++++++++ src/modules/mod.rs | 1 + 2 files changed, 22 insertions(+) diff --git a/src/modules/account_registration.rs b/src/modules/account_registration.rs index fbfafee..701d260 100644 --- a/src/modules/account_registration.rs +++ b/src/modules/account_registration.rs @@ -23,6 +23,7 @@ use std::collections::HashMap; use crate::command::{CmdResult, Command}; use crate::http::{json_str, urlencode}; +use crate::module::Module; use crate::server::{now, Server}; use crate::Uid; @@ -30,6 +31,26 @@ use crate::Uid; #[derive(Default)] struct RateState(HashMap>); +/// Ticks the rate-limit table: prunes each IP's timestamps to the window and drops +/// IPs with none left, so the map can't accumulate one entry per distinct IP that +/// ever issued a REGISTER over the process lifetime. +pub struct AcctRegGc; +impl Module for AcctRegGc { + fn name(&self) -> &'static str { + "account_registration" + } + fn on_tick(&mut self, s: &mut Server) { + let window = s.conf_num("acctregister_ratetime", 3600u64); + let n = now(); + if let Some(st) = s.ext.get_mut::() { + st.0.retain(|_, hist| { + hist.retain(|&t| n.saturating_sub(t) < window); + !hist.is_empty() + }); + } + } +} + fn enabled(s: &Server) -> bool { s.conf_bool("account_registration", false) && s.conf("acctregister_registerurl").is_some() } diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 148b796..8f8dbe9 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -112,6 +112,7 @@ pub fn default_modules() -> Vec> { Box::new(operprefix::OperPrefix), Box::new(permchannels::PermChannels::default()), Box::new(chathistory::ChatHistoryGc), + Box::new(account_registration::AcctRegGc), ] }