Skip to content

Network based new account ban #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions create_account_ban.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
-- Ban new player account creation from selected addresses

if not minetest.settings:get_bool("beowulf.create_account_ban.enable", true) then
-- Module disabled by server configuration, bail out
return
end

local CHAT_NAME = "*Beowulf*"
local MSG_BLOCKED = "Contact us on Discord or IRC if this seems wrong: New accounts are blocked from your address."
local LOG_BLOCKED = "Blocked new account %s from %s %s"

local has_beerchat = minetest.get_modpath("beerchat")

local storage = minetest.get_mod_storage()

local blacklist_ip
local blacklist_asn
local active
local logging

local inform_admin = has_beerchat and function(msg)
minetest.log("action", "[beowulf] " .. msg)
beerchat.on_channel_message(beerchat.moderator_channel_name, CHAT_NAME, msg)
beerchat.send_on_channel(CHAT_NAME, beerchat.moderator_channel_name, msg)
end or function(msg)
minetest.log("action", "[beowulf] " .. msg)
end

local jail_player = has_beerchat and beerchat.jail and function(jailer, target)
beerchat.jail.chat_jail(jailer, target)
end or function() end

local function cleanup()
local dirty = false
local now = os.time()
for key, options in pairs(blacklist_ip) do
if options.expire and options.expire >= now then
blacklist_ip[key] = nil
dirty = true
end
end
for key, options in pairs(blacklist_asn) do
if options.expire and options.expire >= now then
blacklist_asn[key] = nil
dirty = true
end
end
return dirty
end

local function read_storage()
blacklist_ip = minetest.deserialize(storage:get("create_account_ban.blacklist_ip")) or {}
blacklist_asn = minetest.deserialize(storage:get("create_account_ban.blacklist_asn")) or {}
active = storage:get("create_account_ban.active") ~= nil
logging = storage:get_int("create_account_ban.logging")
end

local function write_storage()
storage:set_string("create_account_ban.blacklist_ip", blacklist_ip and minetest.serialize(blacklist_ip) or "")
storage:set_string("create_account_ban.blacklist_asn", blacklist_asn and minetest.serialize(blacklist_asn) or "")
storage:set_string("create_account_ban.active", active and "1" or "")
storage:set_int("create_account_ban.logging", logging)
end

local function match_values(a, b, options)
if type(options) == "table" and options.pattern then
return a:find(b) ~= nil
end
return a == b
end

minetest.register_on_prejoinplayer(function(name, ip)
if active and blacklist_ip and not minetest.get_auth_handler().get_auth(name) then
for ip_pattern, options in pairs(blacklist_ip) do
if match_values(ip, ip_pattern, options) then
-- Account creation blocked by blacklisted IP address
inform_admin(LOG_BLOCKED:format(name, "IP", ip_pattern))
return MSG_BLOCKED
end
end
end
end)

local function kick_and_delete_player_account(name)
minetest.kick_player(name, MSG_BLOCKED)
local auth_handler = minetest.get_auth_handler()
if auth_handler.get_auth(name) then
minetest.log("action", "[beowulf] Removing player account: "..name)
auth_handler.delete_auth(name)
else
-- Should not happen, barrier is most probably leaking for some reason if this ever gets logged
minetest.log("error", "[beowulf] Player account should exist but not found: "..name)
end
end

if minetest.get_modpath("geoip") then

-- Register prejoin handler for more efficient blocking after first failure from blocked AS
geoip.register_on_prejoinplayer(function(name, result, last_login)
if active and blacklist_asn and last_login == nil and result.asn and blacklist_asn[result.asn] then
-- Account creation blocked by blacklisted AS number
inform_admin(LOG_BLOCKED:format(name, "ASN", result.asn))
-- Disconnect player
return MSG_BLOCKED
end
end)

-- Register join handler to prevent creating new accounts from blocked AS
geoip.register_on_joinplayer(function(name, result, last_login)
if active and blacklist_asn and last_login == nil and result.asn and blacklist_asn[result.asn] then
-- Account creation blocked by blacklisted AS number
inform_admin(LOG_BLOCKED:format(name, "ASN", result.asn))
-- Disconnect and remove new player account
minetest.after(0, function()
kick_and_delete_player_account(name)
end)
-- Event handled completely, stop propagation
return true
end
end)

end

minetest.register_chatcommand("block", {
params = "<playername>|<ASN>|<IP pattern>|-enable|-disable|-logging",
privs = { ban = true },
description = "Block new player account registration using IP pattern or ASN",
func = function(name, param)

local bantype, target
if not param then
return false
elseif param:find("^-[eE]") then
active = true
cleanup()
write_storage()
return true, "Enabled IP/ASN based new account blocking"
elseif param:find("^-[dD]") then
active = false
cleanup()
write_storage()
return true, "Disabled IP/ASN based new account blocking"
elseif param:find(":") then
bantype = "IPv6"
target = param
elseif param:find("^[0-9]+%.") then
bantype = "IPv4"
target = param
elseif param:find("^[0-9]$") then
bantype = "ASN"
target = param
elseif param:find("^[%w_]+$") then
bantype = "player IP"
target = minetest.get_player_ip(param)
jail_player(name, param)
else
return false, "Invalid option"
end

-- Test pattern to catch possible lua pattern errors
if not pcall(function() (""):find(target) end) then
return false, "Target is not valid lua pattern for matching"
end

local expire = os.time() + 60 * 60 * 24 * 5
if bantype == "ASN" then
blacklist_asn[target] = { expire = expire }
else
blacklist_ip[target] = { expire = expire }
end
write_storage()

local msg = "Blocked new accounts from " .. bantype .. " " .. target
minetest.log("action", "[beowulf] "..msg)
return true, msg..". Kick/ban/jail players if needed."
end
})

-- Initialize data and cleanup if needed
read_storage()
if cleanup() then
write_storage()
end
42 changes: 28 additions & 14 deletions geoip_asn_kick.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,41 @@

local has_beerchat = minetest.get_modpath("beerchat")

local ban_message = "you are joining from a blacklisted network that is known for "
.. "troublemakers, if you think this is a mistake please report this on irc/discord"

local blacklisted_asn = {
-- "map-attack" hacks over various ip's
[49453] = true
}

assert(type(geoip.register_on_joinplayer) == "function", "geoip-mod is out of date ('register_on_joinplayer' mmissing)")
geoip.register_on_joinplayer(function(name, result)
if result.asn then
if blacklisted_asn[result.asn] then
-- blacklisted, kick player
local msg = "Player '" .. name .. "' joined from a blacklisted ASN: '" .. result.asn .. "'"
minetest.log("action", "[beowulf] " .. msg)
if has_beerchat then
beerchat.send_on_channel("Geoip-ASN-Kick", beerchat.moderator_channel_name, msg)
end
if minetest.settings:get_bool("beowulf.geoip_asn_kick.enable", false) then
minetest.after(0, function()
minetest.kick_player(name, "you are joining from a blacklisted network that is known for troublemakers, " ..
"if you think this is a mistake please report this on irc/discord")
end)
end
if result.asn and blacklisted_asn[result.asn] then
-- blacklisted, kick player
local msg = "Player '" .. name .. "' joined from a blacklisted ASN: '" .. result.asn .. "'"
minetest.log("action", "[beowulf] " .. msg)
if has_beerchat then
beerchat.send_on_channel("Geoip-ASN-Kick", beerchat.moderator_channel_name, msg)
end
if minetest.settings:get_bool("beowulf.geoip_asn_kick.enable", false) then
minetest.after(0, function()
minetest.kick_player(name, ban_message)
end)
end
end
end)

geoip.register_on_prejoinplayer(function(name, result)
if result.asn and blacklisted_asn[result.asn] then
-- blacklisted, kick player
local msg = "Player '" .. name .. "' prejoin from a blacklisted ASN: '" .. result.asn .. "'"
minetest.log("action", "[beowulf] " .. msg)
if has_beerchat then
beerchat.send_on_channel("Geoip-ASN-Kick", beerchat.moderator_channel_name, msg)
end
if minetest.settings:get_bool("beowulf.geoip_asn_kick.enable", false) then
return ban_message
end
end
end)
2 changes: 2 additions & 0 deletions init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ end
if minetest.get_modpath("geoip") then
dofile(MP.."/geoip_asn_kick.lua")
end

dofile(MP.."/create_account_ban.lua")
13 changes: 7 additions & 6 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ Image source: https://en.wikipedia.org/wiki/Beowulf_(hero)

![](https://github.com/mt-mods/beowulf/workflows/luacheck/badge.svg)

* State: **WIP**

# Features

* Kicks dragonfire clients if it detects a known version string
Expand All @@ -17,13 +15,16 @@ Image source: https://en.wikipedia.org/wiki/Beowulf_(hero)

# Requirements

* Needs an engine debug-build or patch to expose the `version_string` in `minetest.get_player_information()`
* Some functionality needs an engine debug-build or patch to expose the `version_string` in `minetest.get_player_information()`.

# Settings

* **beowulf.dfdetect.enable_kick** if `true`: kicks the player if it detects a dragonfire version string, defaults to `false`
* **beowulf.noclip_hurt.enable** enables damage in common ground nodes to make the `noclip` experience as worse as possible (default: `false`)
* **beowulf.geoip_asn_kick.enable** enables kicking of blacklisted ASN's resolved by the `geoip` mod (default `false`)
| Configuration key | Default | Description
| ----------------------------------- | ------- | -------------------------------
| `beowulf.dfdetect.enable_kick` | `true` | If `true` kicks the player if it detects a dragonfire version string.
| `beowulf.noclip_hurt.enable` | `false` | Enables damage in common ground nodes to make the `noclip` experience as worse as possible (default: `false`).
| `beowulf.geoip_asn_kick.enable` | `false` | Enables kicking of blacklisted ASN's resolved by the `geoip` mod (default `false`).
| `beowulf.create_account_ban.enable` | `true` | Enables new account ban functionality, allows banning account creation by IP pattern or with geoip also by ASN.

# License

Expand Down