several updates
parent
1cf079fac8
commit
cd8384738a
11
TODO
11
TODO
|
@ -1,23 +1,22 @@
|
|||
|
||||
rework /asn_inspect
|
||||
- count number of entries a player has in the status table
|
||||
- only report players that have more than one entry in that table
|
||||
|
||||
rework /ban_record
|
||||
- make it a summary of whether a player might be suspicious based on known associations
|
||||
- like sban's ban record
|
||||
- function currently named ban_record would be renamed "status"
|
||||
- function currently named ban_record would be renamed "record"
|
||||
- "so you want to specify his name, and see a list of banned/troublesome accounts from his ASN."
|
||||
- "so with one command, I should see, perhaps, IP-linked accounts, first and last login, ban record, and flagged accounts sharing their asn"
|
||||
|
||||
create a way to search for players by IP/net instead of just ASN
|
||||
|
||||
add "last login" to players table
|
||||
make way to show last login IP on certain commands (e.g. /inspect_asn)
|
||||
|
||||
make settings for the hard-coded values for reporting timing
|
||||
|
||||
alert staff if there are new reports since their last login
|
||||
|
||||
a way to resolve reports?
|
||||
|
||||
DOCUMENTATION....
|
||||
--------------
|
||||
|
||||
# Administration
|
||||
|
|
75
commands.lua
75
commands.lua
|
@ -60,7 +60,7 @@ register_chatcommand('sban_import', {
|
|||
if not filename or filename == '' then
|
||||
filename = minetest.get_worldpath() .. '/sban.sqlite'
|
||||
end
|
||||
if not io.open(filename, 'r') then
|
||||
if not util.file_exists(filename) then
|
||||
return false, ('Could not open file %q.'):format(filename)
|
||||
end
|
||||
chat_send_player(caller, 'Importing SBAN. This can take a while...')
|
||||
|
@ -834,7 +834,7 @@ register_chatcommand('asn_unblock', {
|
|||
end
|
||||
})
|
||||
---------------- GET LOGS ---------------
|
||||
register_chatcommand('ban_record', {
|
||||
register_chatcommand('record', {
|
||||
description='shows the status log of a player',
|
||||
params='<player_name> [<number>]',
|
||||
privs={[mod_priv]=true},
|
||||
|
@ -885,6 +885,68 @@ register_chatcommand('ban_record', {
|
|||
end
|
||||
})
|
||||
|
||||
register_chatcommand('ban_record', {
|
||||
description = 'shows relevant info for a player',
|
||||
params = '<player_name>',
|
||||
privs = { [mod_priv] = true },
|
||||
func = function(caller, params)
|
||||
local player_name = params:match('^([a-zA-Z0-9_-]+)$')
|
||||
if not player_name or player_name:len() > 20 then
|
||||
return false, 'Invalid argument'
|
||||
end
|
||||
local player_id = data.get_player_id(player_name)
|
||||
if not player_id then
|
||||
return false, 'Unknown player'
|
||||
end
|
||||
local ipint = data.fumble_about_for_an_ip(player_name, player_id)
|
||||
local asn = lib_asn.lookup(ipint)
|
||||
|
||||
chat_send_player(caller, "Accounts associated by IP:")
|
||||
local rows = data.get_player_cluster(player_id)
|
||||
if not rows then return false, 'An error occurred (see server logs)' end
|
||||
local clustered = {}
|
||||
for _, row in ipairs(rows) do
|
||||
local color = data.player_status_color[row.player_status_id] or data.player_status.default.color
|
||||
table.insert(clustered, minetest.colorize(color, row.player_name))
|
||||
end
|
||||
chat_send_player(caller, table.concat(clustered, ', '))
|
||||
|
||||
chat_send_player(caller, "Flagged accounts on the same ASN:")
|
||||
rows = data.get_asn_associations(asn, '1y')
|
||||
if not rows then return false, 'An error occurred (see server logs)' end
|
||||
local assocs = {}
|
||||
for _, row in ipairs(rows) do
|
||||
local color = data.player_status_color[row.player_status_id] or data.player_status.default.color
|
||||
table.insert(assocs, minetest.colorize(color, row.player_name))
|
||||
end
|
||||
chat_send_player(caller, table.concat(clustered, ', '))
|
||||
|
||||
chat_send_player(caller, "Account status:")
|
||||
rows = data.get_player_status_log(player_id)
|
||||
if not rows then
|
||||
return false, 'An error occurred (see server logs)'
|
||||
end
|
||||
for index = 1,#rows do
|
||||
local row = rows[index]
|
||||
local message = ('%s: %s set status to %s.'):format(
|
||||
iso_date(row.timestamp),
|
||||
row.executor_name,
|
||||
row.status_name
|
||||
)
|
||||
local reason = row.reason
|
||||
if reason and reason ~= '' then
|
||||
message = ('%s Reason: %s'):format(message, reason)
|
||||
end
|
||||
local expires = row.expires
|
||||
if expires then
|
||||
message = ('%s Expires: %s'):format(message, iso_date(expires))
|
||||
end
|
||||
chat_send_player(caller, message)
|
||||
end
|
||||
return true
|
||||
end
|
||||
})
|
||||
|
||||
register_chatcommand('ip_record', {
|
||||
description='shows the status log of an IP',
|
||||
params='<IP> [<number>]',
|
||||
|
@ -1061,7 +1123,7 @@ register_chatcommand('inspect', {
|
|||
|
||||
register_chatcommand('ip_inspect', {
|
||||
description='list player accounts and statuses associated with an IP',
|
||||
params='<IP> [<timespan>=1w]',
|
||||
params='<IP> [<timespan>=1m]',
|
||||
privs={[mod_priv]=true},
|
||||
func=function(caller, params)
|
||||
local ipstr, timespan_str = params:match('^(%d+%.%d+%.%d+%.%d+)%s+(%w+)$')
|
||||
|
@ -1078,7 +1140,7 @@ register_chatcommand('ip_inspect', {
|
|||
return false, 'Invalid timespan'
|
||||
end
|
||||
else
|
||||
timespan = 60*60*24*7
|
||||
timespan = parse_time('1m')
|
||||
end
|
||||
local ipint = lib_ip.ipstr_to_ipint(ipstr)
|
||||
local rows = data.get_ip_associations(ipint, timespan)
|
||||
|
@ -1102,7 +1164,7 @@ register_chatcommand('ip_inspect', {
|
|||
|
||||
register_chatcommand('asn_inspect', {
|
||||
description='list player accounts and statuses associated with an ASN',
|
||||
params='<ASN> [<timespan>=1w]',
|
||||
params='<ASN> [<timespan>=1y]',
|
||||
privs={[mod_priv]=true},
|
||||
func=function(caller, params)
|
||||
local asn, timespan_str = params:match('^A?(%d+)%s+(%w+)$')
|
||||
|
@ -1119,7 +1181,7 @@ register_chatcommand('asn_inspect', {
|
|||
return false, 'Invalid timespan'
|
||||
end
|
||||
else
|
||||
timespan = 60*60*24*7
|
||||
timespan = parse_time('1y')
|
||||
end
|
||||
asn = tonumber(asn)
|
||||
local description = lib_asn.get_description(asn)
|
||||
|
@ -1201,6 +1263,7 @@ register_chatcommand('cluster', {
|
|||
return true
|
||||
end
|
||||
})
|
||||
alias_chatcommand('/whois', 'cluster')
|
||||
--
|
||||
register_chatcommand('who2', {
|
||||
description='Show current connected players, statuses, and sources',
|
||||
|
|
474
data.lua
474
data.lua
|
@ -1,42 +1,73 @@
|
|||
verbana.data = {}
|
||||
|
||||
local data = verbana.data
|
||||
local lib_asn = verbana.lib_asn
|
||||
local lib_ip = verbana.lib_ip
|
||||
local util = verbana.util
|
||||
local log = verbana.log
|
||||
|
||||
local sql = verbana.sql
|
||||
local db = verbana.db
|
||||
|
||||
data.version = 1
|
||||
|
||||
-- constants
|
||||
verbana.data.player_status = {
|
||||
default={name='default', id=1},
|
||||
suspicious={name='suspicious', id=2},
|
||||
banned={name='banned', id=3},
|
||||
whitelisted={name='whitelisted', id=4},
|
||||
unverified={name='unverified', id=5},
|
||||
kicked={name='kicked', id=6}, -- for logging kicks
|
||||
data.player_status = {
|
||||
default={name='default', id=1, color='#0F0'},
|
||||
suspicious={name='suspicious', id=2, color='#FF0'},
|
||||
banned={name='banned', id=3, color='#F00'},
|
||||
whitelisted={name='whitelisted', id=4, color='#FFF'},
|
||||
unverified={name='unverified', id=5, color='#00F'},
|
||||
kicked={name='kicked', id=6, color='#F0F'}, -- for logging kicks
|
||||
}
|
||||
verbana.data.player_status_name = {}
|
||||
for _, value in pairs(verbana.data.player_status) do verbana.data.player_status_name[value.id] = value.name end
|
||||
verbana.data.ip_status = {
|
||||
default={name='default', id=1},
|
||||
suspicious={name='suspicious', id=2},
|
||||
blocked={name='blocked', id=3},
|
||||
trusted={name='trusted', id=4},
|
||||
data.player_status_name = {}
|
||||
data.player_status_color = {}
|
||||
for _, value in pairs(data.player_status) do
|
||||
data.player_status_name[value.id] = value.name
|
||||
data.player_status_color[value.id] = value.color
|
||||
end
|
||||
|
||||
data.ip_status = {
|
||||
default={name='default', id=1, color='#0F0'},
|
||||
suspicious={name='suspicious', id=2, color='#FF0'},
|
||||
blocked={name='blocked', id=3, color='#F00'},
|
||||
trusted={name='trusted', id=4, color='#FFF'},
|
||||
}
|
||||
verbana.data.ip_status_name = {}
|
||||
for _, value in pairs(verbana.data.ip_status) do verbana.data.ip_status_name[value.id] = value.name end
|
||||
verbana.data.asn_status = {
|
||||
default={name='default', id=1},
|
||||
suspicious={name='suspicious', id=2},
|
||||
blocked={name='blocked', id=3},
|
||||
data.ip_status_name = {}
|
||||
data.ip_status_color = {}
|
||||
for _, value in pairs(data.ip_status) do
|
||||
data.ip_status_name[value.id] = value.name
|
||||
data.ip_status_color[value.id] = value.color
|
||||
end
|
||||
|
||||
data.asn_status = {
|
||||
default={name='default', id=1, color='#0F0'},
|
||||
suspicious={name='suspicious', id=2, color='#FF0'},
|
||||
blocked={name='blocked', id=3, color='#FF0'},
|
||||
}
|
||||
verbana.data.asn_status_name = {}
|
||||
for _, value in pairs(verbana.data.asn_status) do verbana.data.asn_status_name[value.id] = value.name end
|
||||
verbana.data.verbana_player = '!verbana!'
|
||||
verbana.data.verbana_player_id = 1
|
||||
data.asn_status_name = {}
|
||||
data.asn_status_color = {}
|
||||
for _, value in pairs(data.asn_status) do
|
||||
data.asn_status_name[value.id] = value.name
|
||||
data.asn_status_color[value.id] = value.color
|
||||
end
|
||||
|
||||
data.verbana_player = '!verbana!'
|
||||
data.verbana_player_id = 1
|
||||
|
||||
-- wrap sqllite API to make error reporting less messy
|
||||
local function check_description(description)
|
||||
return (
|
||||
type(description) == 'string' and
|
||||
description ~= ''
|
||||
)
|
||||
end
|
||||
|
||||
local function execute(code, description)
|
||||
if not check_description(description) then
|
||||
log('error', 'bad description for execute: %q', tostring(description))
|
||||
return false
|
||||
end
|
||||
if db:exec(code) ~= sql.OK then
|
||||
log('error', 'executing %s %q: %s', description, code, db:errmsg())
|
||||
return false
|
||||
|
@ -45,15 +76,23 @@ local function execute(code, description)
|
|||
end
|
||||
|
||||
local function prepare(code, description)
|
||||
if not check_description(description) then
|
||||
log('error', 'bad description for prepare: %q', tostring(description))
|
||||
return false
|
||||
end
|
||||
local statement = db:prepare(code)
|
||||
if not statement then
|
||||
log('error', 'preparing %s %q: %s', description, code, db:errmsg())
|
||||
return nil
|
||||
return
|
||||
end
|
||||
return statement
|
||||
end
|
||||
|
||||
local function bind(statement, description, ...)
|
||||
if not check_description(description) then
|
||||
log('error', 'bad description for bind: %q', tostring(description))
|
||||
return false
|
||||
end
|
||||
if statement:bind_values(...) ~= sql.OK then
|
||||
log('error', 'binding %s: %s %q', description, db:errmsg(), minetest.serialize({...}))
|
||||
return false
|
||||
|
@ -62,6 +101,10 @@ local function bind(statement, description, ...)
|
|||
end
|
||||
|
||||
local function bind_and_step(statement, description, ...)
|
||||
if not check_description(description) then
|
||||
log('error', 'bad description for bind_and_step: %q', tostring(description))
|
||||
return false
|
||||
end
|
||||
if not bind(statement, description, ...) then return false end
|
||||
if statement:step() ~= sql.DONE then
|
||||
log('error', 'stepping %s: %s %q', description, db:errmsg(), minetest.serialize({...}))
|
||||
|
@ -72,6 +115,10 @@ local function bind_and_step(statement, description, ...)
|
|||
end
|
||||
|
||||
local function finalize(statement, description)
|
||||
if not check_description(description) then
|
||||
log('error', 'bad description for finalize: %q', tostring(description))
|
||||
return false
|
||||
end
|
||||
if statement:finalize() ~= sql.OK then
|
||||
log('error', 'finalizing %s: %s', description, db:errmsg())
|
||||
return false
|
||||
|
@ -80,6 +127,10 @@ local function finalize(statement, description)
|
|||
end
|
||||
|
||||
local function execute_bind_one(code, description, ...)
|
||||
if not check_description(description) then
|
||||
log('error', 'bad description for execute_bind_one: %q', tostring(description))
|
||||
return false
|
||||
end
|
||||
local statement = prepare(code, description)
|
||||
if not statement then return false end
|
||||
if not bind_and_step(statement, description, ...) then return false end
|
||||
|
@ -88,6 +139,10 @@ local function execute_bind_one(code, description, ...)
|
|||
end
|
||||
|
||||
local function get_full_table(code, description, ...)
|
||||
if not check_description(description) then
|
||||
log('error', 'bad description for get_full_table: %q', tostring(description))
|
||||
return false
|
||||
end
|
||||
local statement = prepare(code, description)
|
||||
if not statement then return nil end
|
||||
if not bind(statement, description, ...) then return nil end
|
||||
|
@ -100,6 +155,10 @@ local function get_full_table(code, description, ...)
|
|||
end
|
||||
|
||||
local function get_full_ntable(code, description, ...)
|
||||
if not check_description(description) then
|
||||
log('error', 'bad description for get_full_ntable: %q', tostring(description))
|
||||
return false
|
||||
end
|
||||
local statement = prepare(code, description)
|
||||
if not statement then return nil end
|
||||
if not bind(statement, description, ...) then return nil end
|
||||
|
@ -111,18 +170,46 @@ local function get_full_ntable(code, description, ...)
|
|||
return rows
|
||||
end
|
||||
|
||||
local function sort_status_table(data)
|
||||
local function sort_status_table(status_table)
|
||||
local sortable = {}
|
||||
for _, value in pairs(data) do table.insert(sortable, value) end
|
||||
for _, value in pairs(status_table) do table.insert(sortable, value) end
|
||||
table.sort(sortable, function (a, b) return a.id < b.id end)
|
||||
return sortable
|
||||
end
|
||||
|
||||
local function init_status_table(table_name, data)
|
||||
-- SCHEMA INITIALIZATION
|
||||
local function get_current_schema_version()
|
||||
local code = [[
|
||||
SELECT name
|
||||
FROM sqlite_master
|
||||
WHERE type == 'table'
|
||||
AND name == ?;
|
||||
]]
|
||||
local rows = get_full_ntable(code, 'does version table exist?', 'version')
|
||||
if not rows or #rows > 1 then
|
||||
log('error', 'error checking if version table exists')
|
||||
return nil
|
||||
end -- ERROR
|
||||
if #rows == 0 then return 0 end -- if version table doesn't exist, assume DB is version 1
|
||||
code = [[SELECT version FROM version]]
|
||||
rows = get_full_ntable(code, 'get current version')
|
||||
if not rows or #rows ~= 1 then
|
||||
log('error', 'error querying version table')
|
||||
return nil
|
||||
end -- ERROR
|
||||
return rows[1].version
|
||||
end
|
||||
|
||||
local function set_current_schema_version(version)
|
||||
local code = [[UPDATE version SET version = ?]]
|
||||
execute_bind_one(code, 'set current schema version', version)
|
||||
end
|
||||
|
||||
local function init_status_table(table_name, status_table)
|
||||
local status_sql = ('INSERT OR IGNORE INTO %s_status (id, name) VALUES (?, ?)'):format(table_name)
|
||||
local status_statement = prepare(status_sql, ('initialize %s_status'):format(table_name))
|
||||
if not status_statement then return false end
|
||||
for _, status in ipairs(sort_status_table(data)) do
|
||||
for _, status in ipairs(sort_status_table(status_table)) do
|
||||
if not bind_and_step(status_statement, 'insert status', status.id, status.name) then
|
||||
return false
|
||||
end
|
||||
|
@ -133,32 +220,47 @@ local function init_status_table(table_name, data)
|
|||
return true
|
||||
end
|
||||
|
||||
local function init_db()
|
||||
local schema = verbana.util.load_file(verbana.modpath .. '/schema.sql')
|
||||
local function intialize_schema()
|
||||
verbana.log('action', 'initializing schema')
|
||||
local schema = util.load_file(verbana.modpath .. '/schema.sql')
|
||||
if not schema then
|
||||
error(('Could not find Verbana schema at %q'):format(verbana.modpath .. '/schema.sql'))
|
||||
error(('[Verbana] Could not find Verbana schema at %q'):format(verbana.modpath .. '/schema.sql'))
|
||||
end
|
||||
if db:exec(schema) ~= sql.OK then
|
||||
error(('Verbana failed to initialize the database: %s'):format(db:error_message()))
|
||||
end
|
||||
if not init_status_table('player', verbana.data.player_status) then
|
||||
error('error initializing player_status: see server log')
|
||||
end
|
||||
if not init_status_table('ip', verbana.data.ip_status) then
|
||||
error('error initializing ip_status: see server log')
|
||||
end
|
||||
if not init_status_table('asn', verbana.data.asn_status) then
|
||||
error('error initializing asn_status: see server log')
|
||||
end
|
||||
local verbana_player_sql = 'INSERT OR IGNORE INTO player (name) VALUES (?)'
|
||||
if not execute_bind_one(verbana_player_sql, 'verbana player', verbana.data.verbana_player) then
|
||||
error('error initializing verbana internal player: see server log')
|
||||
error(('[Verbana] failed to initialize the database: %s'):format(db:error_message()))
|
||||
end
|
||||
end
|
||||
|
||||
init_db()
|
||||
---- data API -----
|
||||
function verbana.data.reset_db()
|
||||
local function migrate_db(version)
|
||||
verbana.log('action', 'migrating DB to version %s', version)
|
||||
local filename = ('%s/migrations/%s.sql'):format(verbana.modpath, version)
|
||||
local schema = util.load_file(filename)
|
||||
if not schema then
|
||||
error(('[Verbana] Could not find Verbana migration schema at %q'):format(filename))
|
||||
end
|
||||
if db:exec(schema) ~= sql.OK then
|
||||
error(('[Verbana] failed to migrate the database to version %s: %s'):format(version, db:error_message()))
|
||||
end
|
||||
end
|
||||
|
||||
local function initialize_static_data()
|
||||
verbana.log('action', 'initializing static data')
|
||||
if not init_status_table('player', data.player_status) then
|
||||
error('[Verbana] error initializing player_status: see server log')
|
||||
end
|
||||
if not init_status_table('ip', data.ip_status) then
|
||||
error('[Verbana] error initializing ip_status: see server log')
|
||||
end
|
||||
if not init_status_table('asn', data.asn_status) then
|
||||
error('[Verbana] error initializing asn_status: see server log')
|
||||
end
|
||||
local verbana_player_sql = 'INSERT OR IGNORE INTO player (name) VALUES (?)'
|
||||
if not execute_bind_one(verbana_player_sql, 'verbana player', data.verbana_player) then
|
||||
error('[Verbana] error initializing verbana internal player: see server log')
|
||||
end
|
||||
end
|
||||
|
||||
local function clean_db()
|
||||
local code = [[
|
||||
PRAGMA writable_schema = 1;
|
||||
DELETE FROM sqlite_master WHERE type IN ('table', 'index', 'trigger');
|
||||
|
@ -166,13 +268,45 @@ function verbana.data.reset_db()
|
|||
VACUUM;
|
||||
PRAGMA INTEGRITY_CHECK;
|
||||
]]
|
||||
if not execute(code, 'erase current DB') then
|
||||
return false
|
||||
end
|
||||
init_db()
|
||||
return execute(code, 'erase current DB')
|
||||
end
|
||||
|
||||
function verbana.data.import_from_sban(filename)
|
||||
local function init_db()
|
||||
local initialized = false
|
||||
local current_version = get_current_schema_version()
|
||||
if not current_version then
|
||||
error('[Verbana] error getting current DB version; aborting.')
|
||||
elseif current_version > data.version then
|
||||
error('[Verbana] database version is more recent than code version; please upgrade code.')
|
||||
elseif current_version == data.version then
|
||||
return -- everything is up to date
|
||||
elseif current_version == 0 then
|
||||
-- wipe any pre-existing copies of the schema
|
||||
if not clean_db() then
|
||||
error('[Verbana] error wiping existing DB')
|
||||
end
|
||||
intialize_schema()
|
||||
current_version = 1
|
||||
initialized = true
|
||||
end
|
||||
for i = current_version + 1, data.version do
|
||||
migrate_db(i)
|
||||
set_current_schema_version(i)
|
||||
end
|
||||
initialize_static_data()
|
||||
|
||||
local sban_path = minetest.get_worldpath() .. '/sban.sqlite'
|
||||
if util.file_exists(sban_path) then
|
||||
log('action', 'automatically importing existing sban DB')
|
||||
if not data.import_from_sban(sban_path) then
|
||||
log('error', 'failed to import existing sban DB')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- initialize DB after registering import_from_sban
|
||||
---- data API -----
|
||||
function data.import_from_sban(filename)
|
||||
-- apologies for the very long method
|
||||
local start = os.clock()
|
||||
if not execute('BEGIN TRANSACTION', 'sban import transaction') then
|
||||
|
@ -188,10 +322,14 @@ function verbana.data.import_from_sban(filename)
|
|||
local function _error(message, ...)
|
||||
if message then
|
||||
log('error', message, ...)
|
||||
else
|
||||
log('error', 'An error occurred while importing from sban')
|
||||
end
|
||||
execute('ROLLBACK', 'sban import rollback')
|
||||
if sban_db:close() ~= sql.OK then
|
||||
log('error', 'closing sban DB %s', sban_db:errmsg())
|
||||
log('error', 'Error closing sban DB %s', sban_db:errmsg())
|
||||
else
|
||||
log('error', 'closed sban DB')
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
@ -236,8 +374,8 @@ function verbana.data.import_from_sban(filename)
|
|||
if not finalize(insert_assoc_statement, 'insert assoc') then return _error() end
|
||||
if not finalize(insert_log_statement, 'insert connection log') then return _error() end
|
||||
-- player status --
|
||||
local default_player_status_id = verbana.data.player_status.default.id
|
||||
local banned_player_status_id = verbana.data.player_status.banned.id
|
||||
local default_player_status_id = data.player_status.default.id
|
||||
local banned_player_status_id = data.player_status.banned.id
|
||||
local insert_player_status_sql = [[
|
||||
INSERT OR IGNORE
|
||||
INTO player_status_log (executor_id, player_id, status_id, timestamp, reason, expires)
|
||||
|
@ -245,13 +383,13 @@ function verbana.data.import_from_sban(filename)
|
|||
]]
|
||||
local insert_player_status_statement = prepare(insert_player_status_sql, 'insert player status')
|
||||
if not insert_player_status_statement then return _error() end
|
||||
-- local update_player_current_status_sql = [[
|
||||
-- UPDATE player
|
||||
-- SET current_status_id = ?
|
||||
-- WHERE id = ?
|
||||
-- ]]
|
||||
-- local update_player_current_status_statement = prepare(update_player_current_status_sql, 'update player current status')
|
||||
-- if not update_player_current_status_statement then return _error() end
|
||||
local flag_player_sql = [[
|
||||
UPDATE player
|
||||
SET flagged = TRUE
|
||||
WHERE id == ?
|
||||
]]
|
||||
local flag_player_statement = prepare(flag_player_sql, 'flag player')
|
||||
if not flag_player_statement then return _error() end
|
||||
local select_bans_sql = [[
|
||||
SELECT name, source, created, reason, expires, u_source, u_reason, u_date
|
||||
FROM bans
|
||||
|
@ -260,9 +398,10 @@ function verbana.data.import_from_sban(filename)
|
|||
for name, source, created, reason, expires, u_source, u_reason, u_date in sban_db:urows(select_bans_sql) do
|
||||
local player_id = player_id_by_name[name]
|
||||
local source_id = player_id_by_name[source]
|
||||
if not bind_and_step(flag_player_statement, 'flag player', player_id) then return _error() end
|
||||
local unban_source_id
|
||||
if u_source and u_source == 'sban' then
|
||||
unban_source_id = source_id
|
||||
unban_source_id = source_id -- if a temp ban expired, mark it as unban by the original banner
|
||||
else
|
||||
unban_source_id = player_id_by_name[u_source]
|
||||
end
|
||||
|
@ -274,10 +413,9 @@ function verbana.data.import_from_sban(filename)
|
|||
if unban_source_id then
|
||||
if not bind_and_step(insert_player_status_statement, 'insert player status (unban)', unban_source_id, player_id, default_player_status_id, u_date, u_reason, nil) then return _error() end
|
||||
end
|
||||
-- bind_and_step(update_player_current_status_statement, 'update player current status', ?, player_id)
|
||||
end
|
||||
if not finalize(insert_player_status_statement, 'insert player status') then return _error() end
|
||||
-- if not finalize(update_player_current_status_statement, 'update player current status') then return _error() end
|
||||
if not finalize(flag_player_statement, 'flag player') then return _error() end
|
||||
-- SET LAST ACTION --
|
||||
local set_current_status_id_sql = [[
|
||||
UPDATE player
|
||||
|
@ -286,8 +424,16 @@ function verbana.data.import_from_sban(filename)
|
|||
WHERE player_status_log.player_id == player.id);
|
||||
]]
|
||||
if not execute(set_current_status_id_sql, 'set last status') then return _error() end
|
||||
-- SET LAST LOGIN --
|
||||
local set_last_login_id_sql = [[
|
||||
UPDATE player
|
||||
SET last_login_id = (SELECT MAX(connection_log.id)
|
||||
FROM connection_log
|
||||
WHERE connection_log.player_id == player.id);
|
||||
]]
|
||||
if not execute(set_last_login_id_sql, 'set last login') then return _error() end
|
||||
-- CLEANUP --
|
||||
if not execute('COMMIT') then
|
||||
if not execute('COMMIT', 'commit sban import') then
|
||||
if sban_db:close() ~= sql.OK then
|
||||
log('error', 'closing sban DB %s', sban_db:errmsg())
|
||||
end
|
||||
|
@ -299,11 +445,12 @@ function verbana.data.import_from_sban(filename)
|
|||
end
|
||||
log('action', 'imported from SBAN in %s seconds', os.clock() - start)
|
||||
return true
|
||||
end -- verbana.data.import_from_sban
|
||||
end -- data.import_from_sban
|
||||
|
||||
init_db() -- initialize DB after registering import_from_sban
|
||||
|
||||
local player_id_cache = {}
|
||||
function verbana.data.get_player_id(name, create_if_new)
|
||||
function data.get_player_id(name, create_if_new)
|
||||
local cached_id = player_id_cache[name]
|
||||
if cached_id then return cached_id end
|
||||
if create_if_new then
|
||||
|
@ -321,9 +468,19 @@ function verbana.data.get_player_id(name, create_if_new)
|
|||
return table[1][1]
|
||||
end
|
||||
|
||||
function data.flag_player(player_id, flag)
|
||||
local code = [[
|
||||
UPDATE player
|
||||
SET flagged = ?
|
||||
WHERE player_id = ?
|
||||
]]
|
||||
if not flag then flag = true end
|
||||
return execute_bind_one(code, 'flag player', player_id)
|
||||
end
|
||||
|
||||
local player_status_cache = {}
|
||||
function verbana.data.get_player_status(player_id, create_if_new)
|
||||
player_id = verbana.data.get_master(player_id) or player_id
|
||||
function data.get_player_status(player_id, create_if_new)
|
||||
player_id = data.get_master(player_id) or player_id
|
||||
local cached_status = player_status_cache[player_id]
|
||||
if cached_status then return cached_status, false end
|
||||
local code = [[
|
||||
|
@ -351,14 +508,14 @@ function verbana.data.get_player_status(player_id, create_if_new)
|
|||
elseif not create_if_new then
|
||||
return nil, nil
|
||||
end
|
||||
if not verbana.data.set_player_status(player_id, verbana.data.verbana_player_id, verbana.data.player_status.default.id, 'creating initial player status') then
|
||||
if not data.set_player_status(player_id, data.verbana_player_id, data.player_status.default.id, 'creating initial player status') then
|
||||
log('error', 'failed to set initial player status')
|
||||
return nil, true
|
||||
end
|
||||
return verbana.data.get_player_status(player_id, false), true
|
||||
return data.get_player_status(player_id, false), true
|
||||
end
|
||||
function verbana.data.set_player_status(player_id, executor_id, status_id, reason, expires, no_update_current)
|
||||
player_id = verbana.data.get_master(player_id) or player_id
|
||||
function data.set_player_status(player_id, executor_id, status_id, reason, expires, no_update_current)
|
||||
player_id = data.get_master(player_id) or player_id
|
||||
player_status_cache[player_id] = nil
|
||||
local code = [[
|
||||
INSERT INTO player_status_log (player_id, executor_id, status_id, reason, expires, timestamp)
|
||||
|
@ -370,16 +527,19 @@ function verbana.data.set_player_status(player_id, executor_id, status_id, reaso
|
|||
code = 'UPDATE player SET current_status_id = ? WHERE id = ?'
|
||||
if not execute_bind_one(code, 'update player last status id', last_id, player_id) then return false end
|
||||
end
|
||||
if status_id ~= data.player_status.default.id then
|
||||
return data.flag_player(player_id, true)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function verbana.data.register_ip(ipint)
|
||||
function data.register_ip(ipint)
|
||||
local code = 'INSERT OR IGNORE INTO ip (ip) VALUES (?)'
|
||||
return execute_bind_one(code, 'register ip', ipint)
|
||||
end
|
||||
|
||||
local ip_status_cache = {}
|
||||
function verbana.data.get_ip_status(ipint, create_if_new)
|
||||
function data.get_ip_status(ipint, create_if_new)
|
||||
local cached_status = ip_status_cache[ipint]
|
||||
if cached_status then return cached_status end
|
||||
local code = [[
|
||||
|
@ -407,13 +567,13 @@ function verbana.data.get_ip_status(ipint, create_if_new)
|
|||
elseif not create_if_new then
|
||||
return nil
|
||||
end
|
||||
if not verbana.data.set_ip_status(ipint, verbana.data.verbana_player_id, verbana.data.ip_status.default.id, 'creating initial ip status') then
|
||||
if not data.set_ip_status(ipint, data.verbana_player_id, data.ip_status.default.id, 'creating initial ip status') then
|
||||
log('error', 'failed to set initial ip status')
|
||||
return nil
|
||||
end
|
||||
return verbana.data.get_ip_status(ipint, false)
|
||||
return data.get_ip_status(ipint, false)
|
||||
end
|
||||
function verbana.data.set_ip_status(ipint, executor_id, status_id, reason, expires)
|
||||
function data.set_ip_status(ipint, executor_id, status_id, reason, expires)
|
||||
ip_status_cache[ipint] = nil
|
||||
local code = [[
|
||||
INSERT INTO ip_status_log (ip, executor_id, status_id, reason, expires, timestamp)
|
||||
|
@ -426,13 +586,13 @@ function verbana.data.set_ip_status(ipint, executor_id, status_id, reason, expir
|
|||
return true
|
||||
end
|
||||
|
||||
function verbana.data.register_asn(asn)
|
||||
function data.register_asn(asn)
|
||||
local code = 'INSERT OR IGNORE INTO asn (asn) VALUES (?)'
|
||||
return execute_bind_one(code, 'register asn', asn)
|
||||
end
|
||||
|
||||
local asn_status_cache = {}
|
||||
function verbana.data.get_asn_status(asn, create_if_new)
|
||||
function data.get_asn_status(asn, create_if_new)
|
||||
local cached_status = asn_status_cache[asn]
|
||||
if cached_status then return cached_status end
|
||||
local code = [[
|
||||
|
@ -460,13 +620,13 @@ function verbana.data.get_asn_status(asn, create_if_new)
|
|||
elseif not create_if_new then
|
||||
return nil
|
||||
end
|
||||
if not verbana.data.set_asn_status(asn, verbana.data.verbana_player_id, verbana.data.asn_status.default.id, 'creating initial asn status') then
|
||||
if not data.set_asn_status(asn, data.verbana_player_id, data.asn_status.default.id, 'creating initial asn status') then
|
||||
log('error', 'failed to set initial asn status')
|
||||
return nil
|
||||
end
|
||||
return verbana.data.get_asn_status(asn, false)
|
||||
return data.get_asn_status(asn, false)
|
||||
end
|
||||
function verbana.data.set_asn_status(asn, executor_id, status_id, reason, expires)
|
||||
function data.set_asn_status(asn, executor_id, status_id, reason, expires)
|
||||
asn_status_cache[asn] = nil
|
||||
local code = [[
|
||||
INSERT INTO asn_status_log (asn, executor_id, status_id, reason, expires, timestamp)
|
||||
|
@ -479,17 +639,30 @@ function verbana.data.set_asn_status(asn, executor_id, status_id, reason, expire
|
|||
return true
|
||||
end
|
||||
|
||||
function verbana.data.log(player_id, ipint, asn, success)
|
||||
function data.log(player_id, ipint, asn, success)
|
||||
local code = [[
|
||||
INSERT INTO connection_log (player_id, ip, asn, success, timestamp)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
]]
|
||||
if not execute_bind_one(code, 'log connection', player_id, ipint, asn, success, os.time()) then return false end
|
||||
if not execute_bind_one(code, 'log connection', player_id, ipint, asn, success, os.time()) then
|
||||
return false
|
||||
end
|
||||
if success then
|
||||
local last_login_id = db:last_insert_rowid()
|
||||
code = [[
|
||||
UPDATE player
|
||||
SET last_login_id = ?
|
||||
WHERE id = ?
|
||||
]]
|
||||
if not execute_bind_one(code, 'set last login', last_login_id, player_id) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function verbana.data.assoc(player_id, ipint, asn)
|
||||
player_id = verbana.data.get_master(player_id) or player_id
|
||||
function data.assoc(player_id, ipint, asn)
|
||||
player_id = data.get_master(player_id) or player_id
|
||||
local insert_code = [[
|
||||
INSERT OR IGNORE INTO assoc (player_id, ip, asn, first_seen, last_seen)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
|
@ -505,21 +678,21 @@ function verbana.data.assoc(player_id, ipint, asn)
|
|||
if not execute_bind_one(update_code, 'update assoc', os.time(), player_id, ipint, asn) then return false end
|
||||
return true
|
||||
end
|
||||
function verbana.data.has_asn_assoc(player_id, asn)
|
||||
player_id = verbana.data.get_master(player_id) or player_id
|
||||
function data.has_asn_assoc(player_id, asn)
|
||||
player_id = data.get_master(player_id) or player_id
|
||||
local code = 'SELECT 1 FROM assoc WHERE player_id = ? AND asn == ? LIMIT 1'
|
||||
local table = get_full_table(code, 'find player asn assoc', player_id, asn)
|
||||
return #table == 1
|
||||
end
|
||||
function verbana.data.has_ip_assoc(player_id, ipint)
|
||||
player_id = verbana.data.get_master(player_id) or player_id
|
||||
function data.has_ip_assoc(player_id, ipint)
|
||||
player_id = data.get_master(player_id) or player_id
|
||||
local code = 'SELECT 1 FROM assoc WHERE player_id = ? AND ip == ? LIMIT 1'
|
||||
local table = get_full_table(code, 'find player asn assoc', player_id, ipint)
|
||||
return #table == 1
|
||||
end
|
||||
|
||||
function verbana.data.get_player_status_log(player_id)
|
||||
player_id = verbana.data.get_master(player_id) or player_id
|
||||
function data.get_player_status_log(player_id)
|
||||
player_id = data.get_master(player_id) or player_id
|
||||
local code = [[
|
||||
SELECT executor.name executor_name
|
||||
, status.name status_name
|
||||
|
@ -535,7 +708,7 @@ function verbana.data.get_player_status_log(player_id)
|
|||
]]
|
||||
return get_full_ntable(code, 'player status log', player_id)
|
||||
end
|
||||
function verbana.data.get_ip_status_log(ipint)
|
||||
function data.get_ip_status_log(ipint)
|
||||
local code = [[
|
||||
SELECT executor.name executor_name
|
||||
, status.name status_name
|
||||
|
@ -550,7 +723,7 @@ function verbana.data.get_ip_status_log(ipint)
|
|||
]]
|
||||
return get_full_ntable(code, 'ip status log', ipint)
|
||||
end
|
||||
function verbana.data.get_asn_status_log(asn)
|
||||
function data.get_asn_status_log(asn)
|
||||
local code = [[
|
||||
SELECT executor.name executor_name
|
||||
, status.name status_name
|
||||
|
@ -566,7 +739,7 @@ function verbana.data.get_asn_status_log(asn)
|
|||
return get_full_ntable(code, 'asn status log', asn)
|
||||
end
|
||||
|
||||
function verbana.data.get_first_login(player_id)
|
||||
function data.get_first_login(player_id)
|
||||
local code = [[
|
||||
SELECT timestamp
|
||||
FROM connection_log
|
||||
|
@ -576,7 +749,7 @@ function verbana.data.get_first_login(player_id)
|
|||
]]
|
||||
return get_full_ntable(code, 'first login', player_id)
|
||||
end
|
||||
function verbana.data.get_player_connection_log(player_id, limit)
|
||||
function data.get_player_connection_log(player_id, limit)
|
||||
local code = [[
|
||||
SELECT log.ip ipint
|
||||
, log.asn asn
|
||||
|
@ -598,9 +771,9 @@ function verbana.data.get_player_connection_log(player_id, limit)
|
|||
limit = 20
|
||||
end
|
||||
local t = get_full_ntable(code, 'player connection log', player_id, limit)
|
||||
return verbana.util.table_reversed(t)
|
||||
return util.table_reversed(t)
|
||||
end
|
||||
function verbana.data.get_ip_connection_log(ipint, limit)
|
||||
function data.get_ip_connection_log(ipint, limit)
|
||||
local code = [[
|
||||
SELECT player.name player_name
|
||||
, player.id player_id
|
||||
|
@ -622,9 +795,9 @@ function verbana.data.get_ip_connection_log(ipint, limit)
|
|||
limit = 20
|
||||
end
|
||||
local t = get_full_ntable(code, 'ip connection log', ipint, limit)
|
||||
return verbana.util.table_reversed(t)
|
||||
return util.table_reversed(t)
|
||||
end
|
||||
function verbana.data.get_asn_connection_log(asn, limit)
|
||||
function data.get_asn_connection_log(asn, limit)
|
||||
local code = [[
|
||||
SELECT player.name player_name
|
||||
, player.id player_id
|
||||
|
@ -646,10 +819,10 @@ function verbana.data.get_asn_connection_log(asn, limit)
|
|||
limit = 20
|
||||
end
|
||||
local t = get_full_ntable(code, 'asn connection log', asn, limit)
|
||||
return verbana.util.table_reversed(t)
|
||||
return util.table_reversed(t)
|
||||
end
|
||||
|
||||
function verbana.data.get_network_connection_log(asn, limit)
|
||||
function data.get_network_connection_log(asn, limit)
|
||||
local code = [[
|
||||
SELECT player.name player_name
|
||||
, player.id player_id
|
||||
|
@ -671,11 +844,10 @@ function verbana.data.get_network_connection_log(asn, limit)
|
|||
limit = 20
|
||||
end
|
||||
local t = get_full_ntable(code, 'asn connection log', asn, limit)
|
||||
return verbana.util.table_reversed(t)
|
||||
return util.table_reversed(t)
|
||||
end
|
||||
|
||||
|
||||
function verbana.data.get_player_associations(player_id)
|
||||
function data.get_player_associations(player_id)
|
||||
local code = [[
|
||||
SELECT assoc.ip ipint
|
||||
, assoc.asn asn
|
||||
|
@ -692,7 +864,7 @@ function verbana.data.get_player_associations(player_id)
|
|||
]]
|
||||
return get_full_ntable(code, 'player associations', player_id)
|
||||
end
|
||||
function verbana.data.get_ip_associations(ipint, from_time)
|
||||
function data.get_ip_associations(ipint, from_time)
|
||||
local code = [[
|
||||
SELECT
|
||||
DISTINCT player.name player_name
|
||||
|
@ -700,47 +872,55 @@ function verbana.data.get_ip_associations(ipint, from_time)
|
|||
FROM assoc
|
||||
JOIN player ON player.id == assoc.player_id
|
||||
LEFT JOIN player_status_log ON player_status_log.id == player.current_status_id
|
||||
JOIN connection_log ON connection_log.timestamp >= ? AND connection_log.ip == assoc.ip AND connection_log.asn == assoc.asn
|
||||
JOIN connection_log ON connection_log.ip == assoc.ip
|
||||
AND connection_log.asn == assoc.asn
|
||||
WHERE assoc.ip == ?
|
||||
AND connection_log.timestamp >= ?
|
||||
ORDER BY LOWER(player.name)
|
||||
]]
|
||||
return get_full_ntable(code, 'ip associations', from_time, ipint)
|
||||
return get_full_ntable(code, 'ip associations', ipint, from_time)
|
||||
end
|
||||
function verbana.data.get_asn_associations(asn, from_time)
|
||||
function data.get_asn_associations(asn, from_time)
|
||||
local code = [[
|
||||
SELECT
|
||||
DISTINCT player.name player_name
|
||||
, player_status_log.status_id player_status_id
|
||||
, connection_log.ip ipint
|
||||
, connection_log.asn asn
|
||||
FROM assoc
|
||||
JOIN player ON player.id == assoc.player_id
|
||||
LEFT JOIN player_status_log ON player_status_log.id == player.current_status_id
|
||||
JOIN connection_log ON connection_log.timestamp >= ?
|
||||
AND connection_log.ip == assoc.ip
|
||||
AND connection_log.asn == assoc.asn
|
||||
JOIN connection_log USING (ip, asn)
|
||||
JOIN player ON player.id == assoc.player_id
|
||||
LEFT JOIN player_status_log ON player_status_log.id == player.current_status_id
|
||||
LEFT JOIN connection_log ON connection_log.id == player.last_login_id
|
||||
WHERE assoc.asn == ?
|
||||
AND connection_log.timestamp >= ?
|
||||
AND player.flagged == TRUE
|
||||
ORDER BY LOWER(player.name)
|
||||
]]
|
||||
return get_full_ntable(code, 'asn associations', from_time, asn)
|
||||
return get_full_ntable(code, 'asn associations', asn, from_time)
|
||||
end
|
||||
|
||||
function verbana.data.get_player_cluster(player_id)
|
||||
function data.get_player_cluster(player_id)
|
||||
local code = [[
|
||||
SELECT
|
||||
DISTINCT other.name player_name
|
||||
DISTINCT other.name player_name
|
||||
, player_status_log.status_id player_status_id
|
||||
, connection_log.ip ipint
|
||||
, connection_log.asn asn
|
||||
FROM player
|
||||
JOIN assoc player_assoc ON player_assoc.player_id == player.id
|
||||
JOIN assoc other_assoc ON other_assoc.ip == player_assoc.ip
|
||||
AND player_assoc.player_id != other_assoc.player_id
|
||||
JOIN player other ON other.id == other_assoc.player_id
|
||||
LEFT JOIN player_status_log ON player_status_log.id == other.current_status_id
|
||||
LEFT JOIN connection_log ON connection_log.id == player.last_login_id
|
||||
WHERE player.id == ?
|
||||
AND player.id != other_assoc.player_id
|
||||
ORDER BY LOWER(other.name)
|
||||
]]
|
||||
return get_full_ntable(code, 'player cluster', player_id)
|
||||
end
|
||||
|
||||
function verbana.data.get_all_banned_players()
|
||||
function data.get_all_banned_players()
|
||||
local code = [[
|
||||
SELECT player.name player_name
|
||||
, player_status_log.status_id player_status_id
|
||||
|
@ -751,11 +931,11 @@ function verbana.data.get_all_banned_players()
|
|||
WHERE player_status_log.status_id == ?
|
||||
]]
|
||||
return get_full_ntable(code, 'all banned',
|
||||
verbana.data.player_status.banned
|
||||
data.player_status.banned
|
||||
)
|
||||
end
|
||||
|
||||
function verbana.data.fumble_about_for_an_ip(name, player_id)
|
||||
function data.fumble_about_for_an_ip(name, player_id)
|
||||
-- for some reason, get_player_ip is unreliable during register_on_newplayer
|
||||
local ipstr = minetest.get_player_ip(name)
|
||||
if not ipstr then
|
||||
|
@ -765,8 +945,8 @@ function verbana.data.fumble_about_for_an_ip(name, player_id)
|
|||
end
|
||||
end
|
||||
if not ipstr then
|
||||
if not player_id then player_id = verbana.data.get_player_id(name) end
|
||||
local connection_log = verbana.data.get_player_connection_log(player_id, 1)
|
||||
if not player_id then player_id = data.get_player_id(name) end
|
||||
local connection_log = data.get_player_connection_log(player_id, 1)
|
||||
if not connection_log or #connection_log ~= 1 then
|
||||
log('warning', 'player %s exists but has no connection log?', player_id)
|
||||
else
|
||||
|
@ -777,7 +957,7 @@ function verbana.data.fumble_about_for_an_ip(name, player_id)
|
|||
return ipstr
|
||||
end
|
||||
|
||||
function verbana.data.get_ban_log(limit)
|
||||
function data.get_ban_log(limit)
|
||||
local code = [[
|
||||
SELECT player.name player_name
|
||||
, executor.name executor_name
|
||||
|
@ -795,12 +975,12 @@ function verbana.data.get_ban_log(limit)
|
|||
]]
|
||||
if not limit or type(limit) ~= 'number' or limit < 0 then limit = 20 end
|
||||
return get_full_ntable(code, 'ban log',
|
||||
verbana.data.verbana_player_id,
|
||||
data.verbana_player_id,
|
||||
limit
|
||||
)
|
||||
end
|
||||
|
||||
function verbana.data.add_report(reporter_id, report)
|
||||
function data.add_report(reporter_id, report)
|
||||
local code = [[
|
||||
INSERT INTO report
|
||||
(reporter_id, report, timestamp)
|
||||
|
@ -809,7 +989,7 @@ function verbana.data.add_report(reporter_id, report)
|
|||
return execute_bind_one(code, 'add report', reporter_id, report, os.time())
|
||||
end
|
||||
|
||||
function verbana.data.get_reports(from_time)
|
||||
function data.get_reports(from_time)
|
||||
local code = [[
|
||||
SELECT player.name reporter
|
||||
, report.report report
|
||||
|
@ -821,7 +1001,7 @@ function verbana.data.get_reports(from_time)
|
|||
return get_full_ntable(code, 'get reports', from_time)
|
||||
end
|
||||
|
||||
function verbana.data.get_asn_stats(asn)
|
||||
function data.get_asn_stats(asn)
|
||||
local code = [[
|
||||
SELECT COALESCE(player_status_log.status_id, ?) player_status_id
|
||||
, COUNT(COALESCE(player_status_log.status_id, ?)) count
|
||||
|
@ -832,15 +1012,15 @@ function verbana.data.get_asn_stats(asn)
|
|||
ORDER BY COALESCE(player_status_log.status_id, ?)
|
||||
]]
|
||||
return get_full_ntable(code, 'asn stats',
|
||||
verbana.data.player_status.default.id,
|
||||
verbana.data.player_status.default.id,
|
||||
data.player_status.default.id,
|
||||
data.player_status.default.id,
|
||||
asn,
|
||||
verbana.data.player_status.default.id,
|
||||
verbana.data.player_status.default.id
|
||||
data.player_status.default.id,
|
||||
data.player_status.default.id
|
||||
)
|
||||
end
|
||||
|
||||
function verbana.data.get_master(player_id)
|
||||
function data.get_master(player_id)
|
||||
local code = [[
|
||||
SELECT master.id id
|
||||
, master.name name
|
||||
|
@ -854,7 +1034,7 @@ function verbana.data.get_master(player_id)
|
|||
end
|
||||
end
|
||||
|
||||
function verbana.data.set_master(player_id, master_id)
|
||||
function data.set_master(player_id, master_id)
|
||||
--[[
|
||||
case 1: master has no master
|
||||
just set player's master
|
||||
|
@ -867,9 +1047,9 @@ function verbana.data.set_master(player_id, master_id)
|
|||
loops can not be created this way, because we've ensured that a "true" master can't have
|
||||
a master of its own.
|
||||
]]
|
||||
local master_master_id = verbana.data.get_master(master_id)
|
||||
local master_master_id = data.get_master(master_id)
|
||||
if master_master_id == player_id then
|
||||
return verbana.data.swap_master(player_id, master_id)
|
||||
return data.swap_master(player_id, master_id)
|
||||
elseif master_master_id then
|
||||
master_id = master_master_id
|
||||
end
|
||||
|
@ -892,8 +1072,8 @@ function verbana.data.set_master(player_id, master_id)
|
|||
return true
|
||||
end
|
||||
|
||||
function verbana.data.swap_master(player_id, master_id)
|
||||
if verbana.data.get_master(player_id) ~= master_id then
|
||||
function data.swap_master(player_id, master_id)
|
||||
if data.get_master(player_id) ~= master_id then
|
||||
return false, 'not player\'s master'
|
||||
end
|
||||
local code = [[
|
||||
|
@ -918,7 +1098,7 @@ function verbana.data.swap_master(player_id, master_id)
|
|||
return true
|
||||
end
|
||||
|
||||
function verbana.data.unset_master(player_id)
|
||||
function data.unset_master(player_id)
|
||||
local code = [[
|
||||
UPDATE player
|
||||
SET master_id = NULL
|
||||
|
@ -927,7 +1107,7 @@ function verbana.data.unset_master(player_id)
|
|||
return execute_bind_one(code, 'unset master', player_id)
|
||||
end
|
||||
|
||||
function verbana.data.get_alts(player_id)
|
||||
function data.get_alts(player_id)
|
||||
local code = [[
|
||||
SELECT master.name name
|
||||
FROM player master
|
||||
|
@ -938,7 +1118,7 @@ function verbana.data.get_alts(player_id)
|
|||
JOIN player alt ON alt.master_id == master.id
|
||||
WHERE master.id == ?
|
||||
]]
|
||||
local master_id = verbana.data.get_master() or player_id
|
||||
local master_id = data.get_master() or player_id
|
||||
local rows = get_full_ntable(code, 'get alts', master_id)
|
||||
if rows then
|
||||
local alts = {}
|
||||
|
@ -949,7 +1129,7 @@ function verbana.data.get_alts(player_id)
|
|||
end
|
||||
end
|
||||
|
||||
function verbana.data.grep_player(pattern, limit)
|
||||
function data.grep_player(pattern, limit)
|
||||
local code = [[
|
||||
SELECT player.name name
|
||||
, COALESCE(player_status_log.status_id, ?) player_status_id
|
||||
|
@ -959,5 +1139,5 @@ function verbana.data.grep_player(pattern, limit)
|
|||
ORDER BY LOWER(player.name)
|
||||
LIMIT ?
|
||||
]]
|
||||
return get_full_ntable(code, 'grep player', verbana.data.player_status.default.id, pattern, limit)
|
||||
return get_full_ntable(code, 'grep player', data.player_status.default.id, pattern, limit)
|
||||
end
|
||||
|
|
7
init.lua
7
init.lua
|
@ -18,6 +18,10 @@ end
|
|||
dofile(verbana.modpath .. '/settings.lua')
|
||||
dofile(verbana.modpath .. '/privs.lua')
|
||||
|
||||
if verbana.settings.debug_mode then
|
||||
verbana.log('warning', 'Verbana is running in debug mode.')
|
||||
end
|
||||
|
||||
-- libraries
|
||||
dofile(verbana.modpath .. '/util.lua')
|
||||
dofile(verbana.modpath .. '/lib_ip.lua')
|
||||
|
@ -25,6 +29,9 @@ dofile(verbana.modpath .. '/lib_asn.lua')
|
|||
|
||||
-- connect to the DB - MAKE SURE TO CLEAN UP ALL "insecure" access points!
|
||||
local sql = verbana.ie.require('lsqlite3') -- TODO what happens if this isn't installed? ....
|
||||
if not sql then
|
||||
error('Verbana will not work unless lsqlite3 is installed. See README.txt')
|
||||
end
|
||||
verbana.sql = sql
|
||||
local db_location = verbana.settings.db_path
|
||||
local db, _, errmsg = sql.open(db_location)
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
TODO: turn this into proper documentation
|
||||
|
||||
check and reset tempban or tempblock, then...
|
||||
|
||||
new player logs in from a DEFAULT ip/network
|
||||
status = default
|
||||
new player logs in from a DANGEROUS ip/network
|
||||
new player logs in from a SUSPICIOUS ip/network
|
||||
if player is whitelisted: status = default
|
||||
else: status = unverified; alert mods
|
||||
new player logs in from a BLOCKED ip/network
|
||||
|
@ -10,18 +12,16 @@ new player logs in from a BLOCKED ip/network
|
|||
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 banned boot them
|
||||
if status is suspicious, let them in BUT ALERT MODS
|
||||
else allow them in
|
||||
old player logs in from a DANGEROUS ip/network
|
||||
old player logs in from a SUSPICIOUS 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
|
||||
if the player is banned, 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
|
||||
|
@ -29,30 +29,21 @@ old player logs in from a BLOCKED ip/network
|
|||
|
||||
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 banned
|
||||
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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
this folder is for possible future DB migration scripts
|
89
schema.sql
89
schema.sql
|
@ -1,24 +1,31 @@
|
|||
PRAGMA foreign_keys = OFF;
|
||||
BEGIN EXCLUSIVE TRANSACTION;
|
||||
CREATE TABLE version (version INTEGER);
|
||||
INSERT INTO version (version) VALUES (1);
|
||||
|
||||
-- PLAYER
|
||||
CREATE TABLE IF NOT EXISTS player_status (
|
||||
CREATE TABLE player_status (
|
||||
id INTEGER PRIMARY KEY
|
||||
, name TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS player_status_name ON player_status(name);
|
||||
CREATE INDEX player_status_name ON player_status(name);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS player (
|
||||
CREATE TABLE player (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
, name TEXT NOT NULL
|
||||
, master_id INTEGER
|
||||
, current_status_id INTEGER
|
||||
, last_login_id INTEGER
|
||||
, flagged BOOLEAN NOT NULL DEFAULT FALSE
|
||||
, FOREIGN KEY (master_id) REFERENCES player(id)
|
||||
, FOREIGN KEY (current_status_id) REFERENCES player_status_log(id)
|
||||
, FOREIGN KEY (last_login_id) REFERENCES connection_log(id)
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS player_name ON player(LOWER(name));
|
||||
CREATE INDEX IF NOT EXISTS player_master_id ON player(master_id);
|
||||
CREATE INDEX IF NOT EXISTS player_current_status_id ON player(current_status_id);
|
||||
CREATE UNIQUE INDEX player_name ON player(LOWER(name));
|
||||
CREATE INDEX player_master_id ON player(master_id);
|
||||
CREATE INDEX player_current_status_id ON player(current_status_id);
|
||||
CREATE INDEX player_last_login_id ON player(last_login_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS player_status_log (
|
||||
CREATE TABLE player_status_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
, executor_id INTEGER NOT NULL
|
||||
, player_id INTEGER NOT NULL
|
||||
|
@ -31,25 +38,25 @@ CREATE TABLE IF NOT EXISTS player_status_log (
|
|||
, FOREIGN KEY (status_id) REFERENCES player_status(id)
|
||||
, UNIQUE (player_id, status_id, timestamp)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS player_status_log_player_id ON player_status_log(player_id);
|
||||
CREATE INDEX IF NOT EXISTS player_status_log_timestamp ON player_status_log(timestamp);
|
||||
CREATE INDEX IF NOT EXISTS player_status_log_reason ON player_status_log(reason);
|
||||
CREATE INDEX player_status_log_player_id ON player_status_log(player_id);
|
||||
CREATE INDEX player_status_log_timestamp ON player_status_log(timestamp);
|
||||
CREATE INDEX player_status_log_reason ON player_status_log(reason);
|
||||
-- END PLAYER
|
||||
-- IP
|
||||
CREATE TABLE IF NOT EXISTS ip_status (
|
||||
CREATE TABLE ip_status (
|
||||
id INTEGER PRIMARY KEY
|
||||
, name TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ip_status_name ON ip_status(name);
|
||||
CREATE INDEX ip_status_name ON ip_status(name);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ip (
|
||||
CREATE TABLE ip (
|
||||
ip INTEGER PRIMARY KEY
|
||||
, current_status_id INTEGER
|
||||
, FOREIGN KEY (current_status_id) REFERENCES ip_status_log(id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ip_current_status_id ON ip(current_status_id);
|
||||
CREATE INDEX ip_current_status_id ON ip(current_status_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ip_status_log (
|
||||
CREATE TABLE ip_status_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
, executor_id INTEGER NOT NULL
|
||||
, ip INTEGER NOT NULL
|
||||
|
@ -61,25 +68,25 @@ CREATE TABLE IF NOT EXISTS ip_status_log (
|
|||
, FOREIGN KEY (ip) REFERENCES ip(ip)
|
||||
, FOREIGN KEY (status_id) REFERENCES ip_status(id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ip_status_log_ip ON ip_status_log(ip);
|
||||
CREATE INDEX IF NOT EXISTS ip_status_log_timestamp ON ip_status_log(timestamp);
|
||||
CREATE INDEX IF NOT EXISTS ip_status_log_reason ON ip_status_log(reason);
|
||||
CREATE INDEX ip_status_log_ip ON ip_status_log(ip);
|
||||
CREATE INDEX ip_status_log_timestamp ON ip_status_log(timestamp);
|
||||
CREATE INDEX ip_status_log_reason ON ip_status_log(reason);
|
||||
-- END IP
|
||||
-- ASN
|
||||
CREATE TABLE IF NOT EXISTS asn_status (
|
||||
CREATE TABLE asn_status (
|
||||
id INTEGER PRIMARY KEY
|
||||
, name TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS asn_status_name ON asn_status(name);
|
||||
CREATE INDEX asn_status_name ON asn_status(name);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS asn (
|
||||
CREATE TABLE asn (
|
||||
asn INTEGER PRIMARY KEY
|
||||
, current_status_id INTEGER
|
||||
, FOREIGN KEY (current_status_id) REFERENCES asn_status_log(id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS asn_current_status_id ON asn(current_status_id);
|
||||
CREATE INDEX asn_current_status_id ON asn(current_status_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS asn_status_log (
|
||||
CREATE TABLE asn_status_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
, executor_id INTEGER NOT NULL
|
||||
, asn INTEGER NOT NULL
|
||||
|
@ -91,13 +98,14 @@ CREATE TABLE IF NOT EXISTS asn_status_log (
|
|||
, FOREIGN KEY (asn) REFERENCES asn(asn)
|
||||
, FOREIGN KEY (status_id) REFERENCES asn_status(id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS asn_status_log_asn ON asn_status_log(asn);
|
||||
CREATE INDEX IF NOT EXISTS asn_status_log_timestamp ON asn_status_log(timestamp);
|
||||
CREATE INDEX IF NOT EXISTS asn_status_log_reason ON asn_status_log(reason);
|
||||
CREATE INDEX asn_status_log_asn ON asn_status_log(asn);
|
||||
CREATE INDEX asn_status_log_timestamp ON asn_status_log(timestamp);
|
||||
CREATE INDEX asn_status_log_reason ON asn_status_log(reason);
|
||||
-- END ASN
|
||||
-- LOGS AND ASSOCIATIONS
|
||||
CREATE TABLE IF NOT EXISTS connection_log (
|
||||
player_id INTEGER NOT NULL
|
||||
CREATE TABLE connection_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
, player_id INTEGER NOT NULL
|
||||
, ip INTEGER NOT NULL
|
||||
, asn INTEGER NOT NULL
|
||||
, success INTEGER NOT NULL
|
||||
|
@ -107,12 +115,12 @@ CREATE TABLE IF NOT EXISTS connection_log (
|
|||
, FOREIGN KEY (asn) REFERENCES asn(asn)
|
||||
, UNIQUE (player_id, ip, success, timestamp)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS log_player ON connection_log(player_id);
|
||||
CREATE INDEX IF NOT EXISTS log_ip ON connection_log(ip);
|
||||
CREATE INDEX IF NOT EXISTS log_asn ON connection_log(asn);
|
||||
CREATE INDEX IF NOT EXISTS log_timestamp ON connection_log(timestamp);
|
||||
CREATE INDEX log_player ON connection_log(player_id);
|
||||
CREATE INDEX log_ip ON connection_log(ip);
|
||||
CREATE INDEX log_asn ON connection_log(asn);
|
||||
CREATE INDEX log_timestamp ON connection_log(timestamp);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS assoc (
|
||||
CREATE TABLE assoc (
|
||||
player_id INTEGER NOT NULL
|
||||
, ip INTEGER NOT NULL
|
||||
, asn INTEGER NOT NULL
|
||||
|
@ -123,18 +131,19 @@ CREATE TABLE IF NOT EXISTS assoc (
|
|||
, 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);
|
||||
CREATE INDEX assoc_player ON assoc(player_id);
|
||||
CREATE INDEX assoc_ip ON assoc(ip);
|
||||
CREATE INDEX assoc_asn ON assoc(asn);
|
||||
-- END LOGS AND ASSOCIATIONS
|
||||
-- REPORTS
|
||||
CREATE TABLE IF NOT EXISTS report (
|
||||
CREATE TABLE report (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
, reporter_id INTEGER NOT NULL
|
||||
, report TEXT NOT NULL
|
||||
, timestamp INTEGER NOT NULL
|
||||
, FOREIGN KEY (reporter_id) REFERENCES player(id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS report_reporter ON report(reporter_id);
|
||||
CREATE INDEX report_reporter ON report(reporter_id);
|
||||
CREATE INDEX report_timestamp ON report(timestamp);
|
||||
-- END REPORTS
|
||||
PRAGMA foreign_keys = ON;
|
||||
COMMIT TRANSACTION;
|
||||
|
|
14
settings.lua
14
settings.lua
|
@ -63,6 +63,16 @@ verbana.settings.universal_verification = settings:get_bool('verbana.universal_v
|
|||
verbana.settings.jail_bounds = get_jail_bounds()
|
||||
verbana.settings.jail_check_period = get_setting('verbana.jail_check_period')
|
||||
|
||||
-- TODO: remove the default 'true' setting when we are ready
|
||||
verbana.settings.debug_mode = get_setting('verbana.debug_mode', 'true') == 'true'
|
||||
local debug_is_default -- we revert to debug mode if verification or sban is enabled
|
||||
if ((
|
||||
minetest.get_modpath('sban') and
|
||||
minetest.registered_chatcommands['bang'] -- sban doesn't publish an API, so use this as a proxy
|
||||
) or (
|
||||
minetest.global_exists('verification')
|
||||
)) then
|
||||
debug_is_default = 'true'
|
||||
else
|
||||
debug_is_default = 'false'
|
||||
end
|
||||
verbana.settings.debug_mode = get_setting('verbana.debug_mode', debug_is_default) == 'true'
|
||||
|
||||
|
|
10
util.lua
10
util.lua
|
@ -19,6 +19,16 @@ function verbana.util.parse_time(text)
|
|||
return n * time_units[unit]
|
||||
end
|
||||
|
||||
function verbana.util.file_exists(filename)
|
||||
local handle = io.open(filename,"r")
|
||||
if handle then
|
||||
io.close(handle)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function verbana.util.load_file(filename)
|
||||
local file = io.open(filename, 'r')
|
||||
if not file then
|
||||
|
|
Loading…
Reference in New Issue