diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f6e1a3d --- /dev/null +++ b/LICENSE @@ -0,0 +1,127 @@ +Ideas for this mod were taken liberally from sban (MIT license, Copyright (c) 2017 shivajiva101) +and BillyS's verification mod (no listed license). Very little was explicitly copied, however. +Not sure how that affects the license here. + +Otherwise: +------------------------------------------------------------------------------- +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/README.md b/README.md new file mode 100644 index 0000000..36a044d --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +Verbana: Verification and banning mod for minetest +================================================== + +Name +---- +A portmanteau of "verification", "ban", and the herb verbena. + +Motivation +---------- + +This mod is a response to sban, an IP-aware banning mod derived from xban, +and BillyS's verification mod for Blocky Survival. Both of these mods have +problems that I've long wanted to resolve, and it seemed the best resolution +to those problems was to create a new integrating the features of both. + +Sban is a good first attempt at IP-aware bans, but it has several major flaws: +1. Multiple users may be associated with an IP, and banning one often bans + them all. +2. Banned IPs can still "hack" into existing accounts of other players by + brute-forcing weak passwords. +3. For many trolls, getting access to a new IP is far too easy, and there is no + effective way to keep them off the server. + +BillyS's verification mod was created to deal with one particular troll on +the BlockySurvival server. When enabled, it requires all new players to be +verified by a player with moderator privileges before they can interact with +the server or communicate with non-moderator players. + +The flaws in the verification mod are +1. Moderators are not always online to verify new players. +2. New players come from all over the world, and may not be able to communicate + with the moderator. +3. New players are of all ages, and may not be able to communicate in chat at + all. + +Verbena aims to provide name-based banning, as well as ip and network based +blocking and verification. +1. IPs and Networks may be marked as "untrusted" - all new players from + untrusted IPs/networks must go through verification, while other new + players may join at will. +2. IPs and Networks may be blocked or temporarily blocked, should the need + arise. +3. There is a three tiered privilege system: Normal players, moderators, + and admins. Moderators may ban and verify players, but only admins have + the ability to mark IPs and networks as untrusted. This way, player's + personal details may be kept private. However, oderators may execute queries + to determine if a player is associated with other banned players by IP or + network. diff --git a/asn_db.lua b/asn.lua similarity index 85% rename from asn_db.lua rename to asn.lua index 3cd5aff..d1a2cb2 100644 --- a/asn_db.lua +++ b/asn.lua @@ -1,13 +1,14 @@ if not verbana then verbana = {} end -if not verbana.ip then dofile('ipmanip.lua') end +if not verbana.modpath then verbana.modpath = '.' end +if not verbana.ip then dofile(verbana.modpath .. '/ipmanip.lua') end if not verbana.log then function verbana.log(_, message, ...) print(message:format(...)) end end -verbana.asn_db = {} +verbana.asn = {} local ASN_DESCRIPTION_FILE = 'data-used-autnums' local NETWORK_ASN_FILE = 'data-raw-table' local function load_file(filename) - local file = io.open(filename, 'r') + local file = io.open(('%s/%s'):format(verbana.modpath, filename), 'r') if not file then verbana.log('error', 'error opening "%s"', filename) return @@ -31,7 +32,7 @@ local function refresh_asn_descriptions() end end - verbana.asn_db.description = description + verbana.asn.description = description end local function refresh_asn_table() @@ -77,10 +78,10 @@ local function refresh_asn_table() end end - verbana.asn_db.network = networks + verbana.asn.network = networks end -function verbana.asn_db.refresh() +function verbana.asn.refresh() local start = os.clock() refresh_asn_descriptions() refresh_asn_table() @@ -88,34 +89,34 @@ function verbana.asn_db.refresh() verbana.log('action', 'refreshed ASN tables in %s seconds', os.clock() - start) end -verbana.asn_db.refresh() +verbana.asn.refresh() local function find(ipint) - local t = verbana.asn_db.network - local low = 0 + local t = verbana.asn.network + local low = 1 local high = #t while low <= high do local mid = math.floor((low + high) / 2) + verbana.log('action', '%s %s %s %s %s', ipint, low, mid, high, #t) local element = t[mid] local start = element[1] local end_ = element[2] - local asn = element[3] if start <= ipint and ipint <= end_ then - return asn + return element[3] elseif start > ipint then - low = mid + 1 - else high = mid - 1 + else + low = mid + 1 end end end -function verbana.asn_db.lookup(ipstr) +function verbana.asn.lookup(ipstr) local ipint = verbana.ip.ipstr_to_number(ipstr) local asn = find(ipint) if asn then - return asn, verbana.asn_db.description[asn] + return asn, verbana.asn.description[asn] else return nil, nil end diff --git a/commands.lua b/commands.lua new file mode 100644 index 0000000..bcc5e2a --- /dev/null +++ b/commands.lua @@ -0,0 +1,197 @@ +local mod_priv = verbana.privs.moderator +local admin_priv = verbana.privs.admin + +minetest.register_chatcommand('import_sban', { + params='', + description='import records from sban', + privs={[admin_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.register_chatcommand('get_asn', { + params=' | ', + description='get the ASN associated with an IP or player name', + privs={[mod_priv]=true}, + func = function(caller, ipstr) + if not verbana.ip.is_valid_ip(ipstr) then + -- TODO assume its a player? + return false, ('"%s" is not a valid ip'):format(ipstr) + end + local asn, description = verbana.asn.lookup(ipstr) + if not asn then + return false, ('could not find ASN for "%s"'):format(ipstr) + end + description = description or '' + return true, ('A%u %s'):format(asn, description) + end +}) + +minetest.register_chatcommand('verify', { + params='', + description='verify a player', + privs={[mod_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.register_chatcommand('unverify', { + params='', + description='unverify a player', + privs={[mod_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.override_chatcommand('kick', { + params=' []', + description='kick a player', + privs={[mod_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.register_chatcommand('lock', { + params=' []', + description='lock a player\'s account', + privs={[mod_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.register_chatcommand('unlock', { + params=' []', + description='unlock a player\'s account', + privs={[mod_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.override_chatcommand('ban', { + params=' []', + description='ban a player', + privs={[mod_priv]=true}, + func=function(caller, params) + -- todo: make sure that the begining of 'reason' doesn't look like a timespan =b + return false, 'TODO: implement' + end +}) + +minetest.register_chatcommand('tempban', { + params=' []', + description='ban a player for a length of time', + privs={[mod_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.override_chatcommand('unban', { + params=' []', + description='unban a player', + privs={[mod_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.register_chatcommand('whitelist', { + params=' []', + description='whitelist a player', + privs={[admin_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.register_chatcommand('unwhitelist', { + params=' []', + description='whitelist a player', + privs={[admin_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.register_chatcommand('suspect', { + params=' []', + description='mark a player as suspicious', + privs={[mod_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.register_chatcommand('unsuspect', { + params=' []', + description='unmark a player as suspicious', + privs={[mod_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.register_chatcommand('ban_record', { + params=' []', + description='shows the ban record of a player', + privs={[mod_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.register_chatcommand('login_record', { + params=' []', + description='shows the login record of a player', + privs={[admin_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.register_chatcommand('inspect', { + params=' | ', + description='list data associated with a player or IP', + privs={[admin_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.register_chatcommand('inspect_asn', { + params='', + description='list player accounts and statuses associated with an ASN', + privs={[admin_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.register_chatcommand('set_ip_status', { + params=' ', + description='set the status of an IP (default, dangerous, blocked)', + privs={[admin_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +minetest.register_chatcommand('set_asn_status', { + params=' ', + description='set the status of an ASN (default, dangerous, blocked)', + privs={[admin_priv]=true}, + func=function(caller, params) + return false, 'TODO: implement' + end +}) + +-- alias (for listing an account's primary, cascade status) +-- list recent bans/kicks/locks/etc +-- first_login (=b) for all players +-- asn statistics diff --git a/data.lua b/data.lua new file mode 100644 index 0000000..de6c8b5 --- /dev/null +++ b/data.lua @@ -0,0 +1,179 @@ +if not verbana then verbana = {} end +if not verbana.modpath then verbana.modpath = '.' end +if not verbana.ip then dofile(verbana.modpath .. '/ipmanip.lua') end +if not verbana.log then function verbana.log(_, message, ...) print(message:format(...)) end end + +local ie = minetest.request_insecure_environment() +if not ie then + error('Verbana will not work unless it has been listed under secure.trusted_mods in minetest.conf') +end + +local sql = ie.require("lsqlite3") +local db = sql.open(('%s/verbana.sqlite'):format(minetest.get_worldpath())) -- TODO get path from settings +sqlite3 = nil -- remove sqlite3 from the global (secure) namespace + +minetest.register_on_shutdown(function() + db:close() +end) + +local function db_exec(stmt) + local status = db:exec(stmt) + if status ~= sql.OK then + verbana.log('error', 'SQLite ERROR: %s', db:errmsg()) + return false + end + return true +end + +local function init_db() + db_exec([[ +PRAGMA foreign_keys = OFF; +-- PLAYER +CREATE TABLE IF NOT EXISTS player_status ( + id INTEGER PRIMARY KEY + , name TEXT NOT NULL +); + CREATE INDEX IF NOT EXISTS player_status_name ON player_status(name); + INSERT OR IGNORE INTO player_status + (id, name) + VALUES ( 0, 'default') + , ( 1, 'unverified') + , ( 2, 'banned') + , ( 3, 'tempbanned') + , ( 4, 'locked') + , ( 5, 'whitelisted') + , ( 6, 'suspicious'); + +CREATE TABLE IF NOT EXISTS player ( + id INTEGER PRIMARY KEY AUTOINCREMENT + , name TEXT NOT NULL + , main_player_id INTEGER + , last_action_id INTEGER + , FOREIGN KEY (main_player_id) REFERENCES player(id) + , FOREIGN KEY (last_action_id) REFERENCES player_action_log(id) +); + CREATE UNIQUE INDEX IF NOT EXISTS player_name ON player(name); + CREATE INDEX IF NOT EXISTS player_main_player_id ON player(main_player_id); + CREATE INDEX IF NOT EXISTS player_last_action_id ON player(last_action_id); + +CREATE TABLE IF NOT EXISTS player_action_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT + , executor_id INTEGER NOT NULL + , player_id INTEGER NOT NULL + , status_id INTEGER NOT NULL + , timestamp INTEGER NOT NULL + , reason TEXT + , expires INTEGER + , FOREIGN KEY (executor_id) REFERENCES player(id) + , FOREIGN KEY (player_id) REFERENCES player(id) + , FOREIGN KEY (status_id) REFERENCES player_status(id) +); + CREATE INDEX IF NOT EXISTS player_action_log_player_id ON player_action_log(player_id); + CREATE INDEX IF NOT EXISTS player_action_log_timestamp ON player_action_log(timestamp); + CREATE INDEX IF NOT EXISTS player_action_log_reason ON player_action_log(reason); +-- END PLAYER +-- IP +CREATE TABLE IF NOT EXISTS ip_status ( + id INTEGER PRIMARY KEY + , name TEXT NOT NULL +); + CREATE INDEX IF NOT EXISTS ip_status_name ON ip_status(name); + INSERT OR IGNORE INTO ip_status + (id, name) + VALUES ( 0, 'default') + , ( 1, 'untrusted') + , ( 2, 'blocked') + , ( 3, 'tempblocked'); + +CREATE TABLE IF NOT EXISTS ip ( + ip INTEGER PRIMARY KEY + , last_action_id INTEGER + , FOREIGN KEY (last_action_id) REFERENCES ip_action_log(id) +); + CREATE INDEX IF NOT EXISTS ip_last_action_id ON ip(last_action_id); + +CREATE TABLE IF NOT EXISTS ip_action_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT + , executor_id INTEGER NOT NULL + , ip INTEGER NOT NULL + , status_id INTEGER NOT NULL + , timestamp INTEGER NOT NULL + , reason TEXT + , expires INTEGER + , FOREIGN KEY (executor_id) REFERENCES player(id) + , FOREIGN KEY (ip) REFERENCES ip(ip) + , FOREIGN KEY (status_id) REFERENCES ip_status(id) +); + CREATE INDEX IF NOT EXISTS ip_action_log_ip ON ip_action_log(ip); + CREATE INDEX IF NOT EXISTS ip_action_log_timestamp ON ip_action_log(timestamp); + CREATE INDEX IF NOT EXISTS ip_action_log_reason ON ip_action_log(reason); +-- END IP +-- ASN +CREATE TABLE IF NOT EXISTS asn_status ( + id INTEGER PRIMARY KEY + , name TEXT NOT NULL +); + CREATE INDEX IF NOT EXISTS asn_status_name ON asn_status(name); + INSERT OR IGNORE INTO asn_status + (id, name) + VALUES ( 0, 'default') + , ( 1, 'untrusted') + , ( 2, 'blocked') + , ( 3, 'tempblocked'); + +CREATE TABLE IF NOT EXISTS asn ( + asn INTEGER PRIMARY KEY + , last_action_id INTEGER + , FOREIGN KEY (last_action_id) REFERENCES asn_action_log(id) +); + CREATE INDEX IF NOT EXISTS asn_last_action_id ON asn(last_action_id); + +CREATE TABLE IF NOT EXISTS asn_action_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT + , executor_id INTEGER NOT NULL + , asn INTEGER NOT NULL + , status_id INTEGER NOT NULL + , timestamp INTEGER NOT NULL + , reason TEXT + , expires INTEGER + , FOREIGN KEY (executor_id) REFERENCES player(id) + , FOREIGN KEY (asn) REFERENCES asn(asn) + , FOREIGN KEY (status_id) REFERENCES asn_status(id) +); + CREATE INDEX IF NOT EXISTS asn_action_log_asn ON asn_action_log(asn); + CREATE INDEX IF NOT EXISTS asn_action_log_timestamp ON asn_action_log(timestamp); + CREATE INDEX IF NOT EXISTS asn_action_log_reason ON asn_action_log(reason); +-- END ASN +-- OTHER +CREATE TABLE IF NOT EXISTS log ( + player_id INTEGER NOT NULL + , ip INTEGER NOT NULL + , asn INTEGER NOT NULL + , timestamp INTEGER NOT NULL + , FOREIGN KEY (player_id) REFERENCES player(id) + , FOREIGN KEY (ip) REFERENCES ip(ip) + , FOREIGN KEY (asn) REFERENCES asn(asn) +); + CREATE INDEX IF NOT EXISTS log_player ON log(player_id); + CREATE INDEX IF NOT EXISTS log_ip ON log(ip); + CREATE INDEX IF NOT EXISTS log_asn ON log(asn); + CREATE INDEX IF NOT EXISTS log_timestamp ON log(timestamp); + +CREATE TABLE IF NOT EXISTS assoc ( + player_id INTEGER + , ip INTEGER + , asn INTEGER + , PRIMARY KEY (player_id, ip, asn) + , FOREIGN KEY (player_id) REFERENCES player(id) + , FOREIGN KEY (ip) REFERENCES ip(ip) + , FOREIGN KEY (asn) REFERENCES asn(asn) +); + CREATE INDEX IF NOT EXISTS assoc_player ON assoc(player_id); + CREATE INDEX IF NOT EXISTS assoc_ip ON assoc(ip); + CREATE INDEX IF NOT EXISTS assoc_asn ON assoc(asn); +-- END OTHER +PRAGMA foreign_keys = ON; + ]]) +end -- init_db() + +init_db() diff --git a/depends.txt b/depends.txt new file mode 100644 index 0000000..e69de29 diff --git a/description.txt b/description.txt new file mode 100644 index 0000000..f9befac --- /dev/null +++ b/description.txt @@ -0,0 +1 @@ +Verification and Banning mod diff --git a/init.lua b/init.lua index 1b651dc..8ba907b 100644 --- a/init.lua +++ b/init.lua @@ -1,5 +1,22 @@ verbana = {} +local modname = minetest.get_current_modname() +verbana.modpath = minetest.get_modpath(modname) -function verbana.log(level, message) - +function verbana.log(level, message, ...) + minetest.log(level, ('[%s] %s'):format(modname, message:format(...))) end + +if not minetest.request_insecure_environment() then + error('insecure environment inaccessible - make sure this mod has been added to minetest.conf!') +end + +dofile(verbana.modpath .. '/settings.lua') +dofile(verbana.modpath .. '/privs.lua') + +dofile(verbana.modpath .. '/ipmanip.lua') +dofile(verbana.modpath .. '/asn.lua') + +dofile(verbana.modpath .. '/data.lua') + +dofile(verbana.modpath .. '/commands.lua') +dofile(verbana.modpath .. '/login_handling.lua') diff --git a/ipmanip.lua b/ipmanip.lua index bdb73e9..c6144db 100644 --- a/ipmanip.lua +++ b/ipmanip.lua @@ -2,13 +2,19 @@ if not verbana then verbana = {} end verbana.ip = {} -function verbana.ip.ipstr_to_number(ip) - local a, b, c, d = ip:match('^(%d+)%.(%d+)%.(%d+)%.(%d+)$') +function verbana.ip.is_valid_ip(ipstr) + local a, b, c, d = ipstr:match('^(%d+)%.(%d+)%.(%d+)%.(%d+)$') + if not a and b and c and d then return false end a = tonumber(a) b = tonumber(b) c = tonumber(c) d = tonumber(d) - return (a * 16777216) + (b * 65536) + (c * 256) + d + return 0 <= a and a < 256 and 0 <= b and b < 256 and 0 <= c and c < 256 and 0 <= d and d < 256 +end + +function verbana.ip.ipstr_to_number(ipstr) + local a, b, c, d = ipstr:match('^(%d+)%.(%d+)%.(%d+)%.(%d+)$') + return (tonumber(a) * 16777216) + (tonumber(b) * 65536) + (tonumber(c) * 256) + tonumber(d) end function verbana.ip.number_to_ipstr(number) diff --git a/logic_notes.txt b/logic_notes.txt new file mode 100644 index 0000000..2748203 --- /dev/null +++ b/logic_notes.txt @@ -0,0 +1,60 @@ +TODO: turn this into proper documentation + +new player logs in from a DEFAULT ip/network + status = default +new player logs in from a DANGEROUS ip/network + if player is whitelisted: status = default + else: status = unverified; alert mods +new player logs in from a BLOCKED ip/network + if player is whitelisted: let them in + else: status is not changed (we should refuse to allow them to register the account) + +old player logs in from a DEFAULT ip/network + if status is banned, boot them + if status is temp banned, check expiry and conditionally boot them + if status is locked, boot them BUT ALERT MODS + if status is suspicious, let them in BUT ALERT MODS + else allow them in +old player logs in from a DANGEROUS ip/network + if player has never used that ip/network + if the player is whitelisted let them in + else refuse entry + else + if the player is whitelisted, let them in + if the player is banned/tempbanned/locked, as for default ip/net status + else let them in, but alert mods +old player logs in from a BLOCKED ip/network + if the player is whitelisted, let them in + else refuse entry + +when status == default: + account can be banned + account can be temp banned + account can be locked + account can be whitelisted by admins + account can be be marked suspicious + account can be unverified (sent back to verification area) +when status == unverified + account can be verified (status -> default) + account can be banned, tempbanned, locked + account can be whitelisted by admins (also lets them out of verification area) + account can be marked suspicious (also lets them out of verification area) +when status == banned + account can be unbanned (status -> default | suspicious depending on network) + account can be tempbanned (override previous behavior) + account can be locked (override previous behavior) +when status == tempbanned + account can be unbanned (status -> default | suspicious depending on network) + account can be banned (override previous behavior) + account can be locked (override previous behavior) +when status == locked + account can be unlocked (status -> default | suspicious depending on network) + account can be banned (override previous behavior) + account can be tempbanned (override previous behavior) +when status == whitelisted + account can be locked by mods + account can be unwhitelisted by admins (status -> default) + account can be banned by admins + account can be tempbanned by admins +when status == suspicious + as for default, mutatis mutandis diff --git a/login_handling.lua b/login_handling.lua new file mode 100644 index 0000000..176290c --- /dev/null +++ b/login_handling.lua @@ -0,0 +1,7 @@ +minetest.register_on_prejoinplayer(function(name, ip) +end) + +minetest.register_on_joinplayer(function(player) + local name = player:get_player_name() + local ip = minetest.get_player_ip(name) +end) diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..ad57abf --- /dev/null +++ b/mod.conf @@ -0,0 +1,2 @@ +name = verbana +description = Verification and Banning mod diff --git a/privs.lua b/privs.lua new file mode 100644 index 0000000..35097af --- /dev/null +++ b/privs.lua @@ -0,0 +1,7 @@ +if not verbana then verbana = {} end +verbana.privs = {} + +minetest.register_privilege('ban_admin', 'administrator for verification/bans') + +verbana.privs.admin = 'ban_admin' -- TODO load from settings +verbana.privs.moderator = 'basic_privs' -- TODO load from settings diff --git a/settings.lua b/settings.lua new file mode 100644 index 0000000..807d08c --- /dev/null +++ b/settings.lua @@ -0,0 +1 @@ +-- config: privs_to_whitelist: if a player has this/these privs, they are treated as whitelisted diff --git a/settingtypes.txt b/settingtypes.txt new file mode 100644 index 0000000..e69de29 diff --git a/verbana_cl.lua b/verbana_cl.lua new file mode 100644 index 0000000..bf3f707 --- /dev/null +++ b/verbana_cl.lua @@ -0,0 +1 @@ +-- TODO: command line interface to manipulate the DB (e.g. for emergencies)