changeset 6322:f110bce35630

mod_block_registrations: Support blocking confusable usernames (UTS 39) This prevents registering e.g. "admin" but with a Cyrillic 'a' U+0430 and other such variations, including keming like "adrnin".
author Kim Alvefur <zash@zash.se>
date Sat, 06 Sep 2025 21:02:26 +0200
parents 2f01497b04c9
children 6216d85162dc
files mod_block_registrations/README.md mod_block_registrations/mod_block_registrations.lua
diffstat 2 files changed, 24 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/mod_block_registrations/README.md	Sat Sep 06 19:25:15 2025 +0200
+++ b/mod_block_registrations/README.md	Sat Sep 06 21:02:26 2025 +0200
@@ -24,6 +24,7 @@
   block_registrations_users      *See source code*   A list of reserved usernames
   block_registrations_matching   `{ }`               A list of [Lua patterns](http://www.lua.org/manual/5.1/manual.html#5.4.1) matching reserved usernames (slower than block_registrations_users)
   block_registrations_require    `nil`               A pattern that registered user accounts MUST match to be allowed
+  block_registrations_confusable `true`              Block usernames that look similar to the reserved usernames (requires Prosody be built with ICU)
 
 Some examples:
 
@@ -37,6 +38,6 @@
 =============
 
   ------ -------
+  13.0    Should work (please test)
   0.12    Works
-  0.11    Work
   ------ -------
--- a/mod_block_registrations/mod_block_registrations.lua	Sat Sep 06 19:25:15 2025 +0200
+++ b/mod_block_registrations/mod_block_registrations.lua	Sat Sep 06 21:02:26 2025 +0200
@@ -1,12 +1,29 @@
-local st = require "util.stanza";
 local nodeprep = require "util.encodings".stringprep.nodeprep;
 
+local normal = nodeprep;
+if module:get_option_boolean("block_registrations_confusable", true) then
+	local confusable = require "util.encodings".confusable;
+	if not confusable then
+		module:log("error", "Prosody not built with ICU, confusables mapping is unavailable.");
+		module:log("error", "Rebuild or disable this feature with 'block_registrations_confusable = false'");
+	end
+	function normal(username)
+		if username then
+			username = nodeprep(username);
+		end
+		if username then
+			username = confusable.skeleton(username);
+		end
+		return username;
+	end
+end
+
 local block_users = module:get_option_set("block_registrations_users", {
 	"abuse", "admin", "administrator", "hostmaster", "info", "news",
 	"noc", "operator", "owner", "postmaster", "register", "registration",
 	"root", "security", "service", "signup", "support", "sysadmin",
 	"sysop", "system", "test", "trouble", "webmaster", "www", "xmpp",
-});
+}) / normal;
 local block_patterns = module:get_option_set("block_registrations_matching", {});
 local require_pattern = module:get_option_string("block_registrations_require");
 
@@ -14,6 +31,9 @@
 	-- Check if the username is simply blocked
 	if block_users:contains(username) then return true; end
 
+	local normalized_username = normal(username);
+	if block_users:contains(normalized_username) then return true; end
+
 	for pattern in block_patterns do
 		if username:find(pattern) then
 			return true;