diff --git a/builtin/builtin.lua b/builtin/builtin.lua index 55220a6..45119be 100644 --- a/builtin/builtin.lua +++ b/builtin/builtin.lua @@ -12,7 +12,7 @@ print = minetest.debug -- --- +-- Define some random basic things -- function basic_dump2(o) @@ -89,6 +89,19 @@ function dump(o, dumped) end end +function string:split(sep) + local sep, fields = sep or ",", {} + local pattern = string.format("([^%s]+)", sep) + self:gsub(pattern, function(c) fields[#fields+1] = c end) + return fields +end + +function string:trim() + return (self:gsub("^%s*(.-)%s*$", "%1")) +end + +assert(string.trim("\n \t\tfoo\t ") == "foo") + -- -- Item definition helpers -- @@ -818,7 +831,7 @@ minetest.register_globalstep(function(dtime) end) function minetest.after(time, func, param) - table.insert(minetest.timers_to_add, {time=time, func=func, param=param}) + table.insert(minetest.timers_to_add, {time=time, func=func, param=param}) end function minetest.check_player_privs(name, privs) @@ -848,6 +861,21 @@ function minetest.get_connected_players() return list end +minetest.registered_privileges = {} +function minetest.register_privilege(name, description) + minetest.registered_privileges[name] = description +end + +minetest.register_privilege("interact", "Can interact with things and modify the world") +minetest.register_privilege("teleport", "Can use /teleport command") +minetest.register_privilege("settime", "Can use /time") +minetest.register_privilege("privs", "Can modify privileges") +minetest.register_privilege("server", "Can do server maintenance stuff") +minetest.register_privilege("shout", "Can speak in chat") +minetest.register_privilege("ban", "Can ban and unban players") +minetest.register_privilege("give", "Can use /give and /giveme") +minetest.register_privilege("password", "Can use /setpassword and /clearpassword") + -- -- Chat commands -- @@ -873,7 +901,7 @@ minetest.register_chatcommand("help", { if def.description and def.description ~= "" then msg = msg .. ": " .. def.description end return msg end - if not param or param == "" then + if param == "" then local msg = "" cmds = {} for cmd, def in pairs(minetest.chatcommands) do @@ -905,18 +933,89 @@ minetest.register_chatcommand("help", { -- Register C++ commands without functions minetest.register_chatcommand("me", {params = nil, description = "chat action (eg. /me orders a pizza)"}) minetest.register_chatcommand("status", {description = "print server status line"}) -minetest.register_chatcommand("privs", {params = "", description = "print out privileges of player"}) minetest.register_chatcommand("shutdown", {params = "", description = "shutdown server", privs = {server=true}}) minetest.register_chatcommand("setting", {params = " = ", description = "set line in configuration file", privs = {server=true}}) minetest.register_chatcommand("clearobjects", {params = "", description = "clear all objects in world", privs = {server=true}}) minetest.register_chatcommand("time", {params = "<0...24000>", description = "set time of day", privs = {settime=true}}) minetest.register_chatcommand("teleport", {params = ",,", description = "teleport to given position", privs = {teleport=true}}) -minetest.register_chatcommand("grant", {params = " ", description = "Give privilege to player", privs = {privs=true}}) -minetest.register_chatcommand("revoke", {params = " ", description = "Remove privilege from player", privs = {privs=true}}) minetest.register_chatcommand("ban", {params = "", description = "ban IP of player", privs = {ban=true}}) minetest.register_chatcommand("unban", {params = "", description = "remove IP ban", privs = {ban=true}}) -minetest.register_chatcommand("setpassword", {params = " ", description = "set given password", privs = {password=true}}) -minetest.register_chatcommand("clearpassword", {params = "", description = "set empty password", privs = {password=true}}) + +-- Register some other commands +minetest.register_chatcommand("privs", { + params = "", + description = "print out privileges of player", + func = function(name, param) + if param == "" then + param = name + else + if not minetest.check_player_privs(name, {privs=true}) then + minetest.chat_send_player(name, "Privileges of "..param.." are hidden from you.") + end + end + privs = {} + for priv, _ in pairs(minetest.get_player_privs(param)) do + table.insert(privs, priv) + end + minetest.chat_send_player(name, "Privileges of "..param..": "..table.concat(privs, " ")) + end, +}) +minetest.register_chatcommand("grant", { + params = " ", + description = "Give privilege to player", + privs = {privs=true}, + func = function(name, param) + local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)") + if not grantname or not grantprivstr then + minetest.chat_send_player(name, "Invalid parameters (see /help grant)") + return + end + local grantprivs = minetest.string_to_privs(grantprivstr) + local privs = minetest.get_player_privs(grantname) + for priv, _ in pairs(grantprivs) do + privs[priv] = true + end + minetest.set_player_privs(grantname, privs) + end, +}) +minetest.register_chatcommand("revoke", { + params = " ", + description = "Remove privilege from player", + privs = {privs=true}, + func = function(name, param) + local revokename, revokeprivstr = string.match(param, "([^ ]+) (.+)") + if not revokename or not revokeprivstr then + minetest.chat_send_player(name, "Invalid parameters (see /help revoke)") + return + end + local revokeprivs = minetest.string_to_privs(revokeprivstr) + local privs = minetest.get_player_privs(revokename) + for priv, _ in pairs(revokeprivs) do + table.remove(privs, priv) + end + minetest.set_player_privs(revokename, privs) + end, +}) +minetest.register_chatcommand("setpassword", { + params = " ", + description = "set given password", + privs = {password=true}, + func = function(name, param) + if param == "" then + minetest.chat_send_player(name, "Password field required") + return + end + minetest.set_player_password(name, param) + end, +}) +minetest.register_chatcommand("clearpassword", { + params = "", + description = "set empty password", + privs = {password=true}, + func = function(name, param) + minetest.set_player_password(name, '') + end, +}) -- -- Builtin chat handler @@ -924,6 +1023,9 @@ minetest.register_chatcommand("clearpassword", {params = "", description = minetest.register_on_chat_message(function(name, message) local cmd, param = string.match(message, "/([^ ]+) *(.*)") + if not param then + param = "" + end local cmd_def = minetest.chatcommands[cmd] if cmd_def then if not cmd_def.func then @@ -942,6 +1044,149 @@ minetest.register_on_chat_message(function(name, message) return false end) +-- +-- Authentication handler +-- + +function minetest.string_to_privs(str) + assert(type(str) == "string") + privs = {} + for _, priv in pairs(string.split(str, ',')) do + privs[priv:trim()] = true + end + return privs +end + +function minetest.privs_to_string(privs) + assert(type(privs) == "table") + list = {} + for priv, bool in pairs(privs) do + if bool then + table.insert(list, priv) + end + end + return table.concat(list, ',') +end + +assert(minetest.string_to_privs("a,b").b == true) +assert(minetest.privs_to_string({a=true,b=true}) == "a,b") + +minetest.auth_file_path = minetest.get_worldpath().."/auth.txt" +minetest.auth_table = {} + +local function read_auth_file() + local newtable = {} + local file, errmsg = io.open(minetest.auth_file_path, 'rb') + if not file then + error(minetest.auth_file_path.." could not be opened for reading: "..errmsg) + end + for line in file:lines() do + if line ~= "" then + local name, password, privilegestring = string.match(line, "([^:]*):([^:]*):([^:]*)") + if not name or not password or not privilegestring then + error("Invalid line in auth.txt: "..dump(line)) + end + local privileges = minetest.string_to_privs(privilegestring) + newtable[name] = {password=password, privileges=privileges} + end + end + io.close(file) + minetest.auth_table = newtable +end + +local function save_auth_file() + local newtable = {} + -- Check table for validness before attempting to save + for name, stuff in pairs(minetest.auth_table) do + assert(type(name) == "string") + assert(name ~= "") + assert(type(stuff) == "table") + assert(type(stuff.password) == "string") + assert(type(stuff.privileges) == "table") + end + local file, errmsg = io.open(minetest.auth_file_path, 'w+b') + if not file then + error(minetest.auth_file_path.." could not be opened for writing: "..errmsg) + end + for name, stuff in pairs(minetest.auth_table) do + local privstring = minetest.privs_to_string(stuff.privileges) + file:write(name..":"..stuff.password..":"..privstring..'\n') + end + io.close(file) +end + +read_auth_file() + +minetest.builtin_auth_handler = { + get_auth = function(name) + assert(type(name) == "string") + if not minetest.auth_table[name] then + minetest.builtin_auth_handler.create_auth(name, minetest.get_password_hash(name, minetest.setting_get("default_password"))) + end + if minetest.is_singleplayer() or name == minetest.setting_get("name") then + return { + password = "", + privileges = minetest.registered_privileges + } + else + return minetest.auth_table[name] + end + end, + create_auth = function(name, password) + assert(type(name) == "string") + assert(type(password) == "string") + minetest.log('info', "Built-in authentication handler adding player '"..name.."'") + minetest.auth_table[name] = { + password = password, + privileges = minetest.string_to_privs(minetest.setting_get("default_privs")), + } + save_auth_file() + end, + set_password = function(name, password) + assert(type(name) == "string") + assert(type(password) == "string") + if not minetest.auth_table[name] then + minetest.builtin_auth_handler.create_auth(name, password) + else + minetest.log('info', "Built-in authentication handler setting password of player '"..name.."'") + minetest.auth_table[name].password = password + save_auth_file() + end + end, + set_privileges = function(name, privileges) + assert(type(name) == "string") + assert(type(privileges) == "table") + if not minetest.auth_table[name] then + minetest.builtin_auth_handler.create_auth(name, minetest.get_password_hash(name, minetest.setting_get("default_password"))) + end + minetest.auth_table[name].privileges = privileges + save_auth_file() + end +} + +function minetest.register_authentication_handler(handler) + if minetest.registered_auth_handler then + error("Add-on authentication handler already registered by "..minetest.registered_auth_handler_modname) + end + minetest.registered_auth_handler = handler + minetest.registered_auth_handler_modname = minetest.get_current_modname() +end + +function minetest.get_auth_handler() + if minetest.registered_auth_handler then + return minetest.registered_auth_handler + end + return minetest.builtin_auth_handler +end + +function minetest.set_player_password(name, password) + minetest.get_auth_handler().set_password(name, password) +end + +function minetest.set_player_privs(name, privs) + minetest.get_auth_handler().set_privileges(name, privs) +end + -- -- Set random seed --