From 7cad0a2dcd817b179f82066964c45937a603d138 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Fri, 30 Mar 2012 18:42:18 +0300 Subject: [PATCH] Reimplement authentication handler in Lua; now we have 1) infinite privilege names, 2) minetest.register_authentication_handler() --- builtin/builtin.lua | 261 +++++++++++++++++++++++++++++++++-- doc/lua_api.txt | 26 +++- src/CMakeLists.txt | 1 - src/auth.cpp | 306 ------------------------------------------ src/auth.h | 107 --------------- src/scriptapi.cpp | 129 +++++++++++++++++- src/scriptapi.h | 7 + src/server.cpp | 178 ++++++++---------------- src/server.h | 17 +-- src/servercommand.cpp | 164 +--------------------- src/servercommand.h | 8 +- 11 files changed, 477 insertions(+), 727 deletions(-) delete mode 100644 src/auth.cpp delete mode 100644 src/auth.h diff --git a/builtin/builtin.lua b/builtin/builtin.lua index 55220a607..45119be48 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 -- diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 1199f8108..a355f8932 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -464,6 +464,8 @@ dump2(obj, name="_", dumped={}) ^ Return object serialized as a string, handles reference loops dump(obj, dumped={}) ^ Return object serialized as a string +string:split(separator) +string:trim() minetest namespace reference ----------------------------- @@ -480,6 +482,7 @@ minetest.log(line) minetest.log(loglevel, line) ^ loglevel one of "error", "action", "info", "verbose" +Registration functions: (Call these only at load time) minetest.register_entity(name, prototype table) minetest.register_abm(abm definition) minetest.register_node(name, node definition) @@ -487,7 +490,6 @@ minetest.register_tool(name, item definition) minetest.register_craftitem(name, item definition) minetest.register_alias(name, convert_to) minetest.register_craft(recipe) - minetest.register_globalstep(func(dtime)) minetest.register_on_placenode(func(pos, newnode, placer)) minetest.register_on_dignode(func(pos, oldnode, digger)) @@ -500,24 +502,40 @@ minetest.register_on_respawnplayer(func(ObjectRef)) ^ currently called _before_ repositioning of player occurs minetest.register_on_chat_message(func(name, message)) minetest.register_chatcommand(cmd, chatcommand definition) +minetest.register_privilege(name, description) +minetest.register_authentication_handler(handler) +^ See minetest.builtin_auth_handler in builtin.lua for reference -minetest.add_to_creative_inventory(itemstring) +Setting-related: minetest.setting_get(name) -> string or nil minetest.setting_getbool(name) -> boolean value or nil +minetest.add_to_creative_inventory(itemstring) +Authentication: +minetest.get_password_hash(name, raw_password) +minetest.set_player_password(name, password_hash) +minetest.string_to_privs(str) -> {priv1=true,...} +minetest.privs_to_string(privs) -> "priv1,priv2,..." +minetest.set_player_privs(name, {priv1=true,...}) +minetest.get_player_privs(name) -> {priv1=true,...} +minetest.check_player_privs(name, {priv1=true,...}) -> bool, missing_privs + +Chat: minetest.chat_send_all(text) minetest.chat_send_player(name, text) -minetest.get_player_privs(name) -> set of privs -minetest.check_player_privs(name, {priv1=true,...}) -> bool, missing_privs + +Inventory: minetest.get_inventory(location) -> InvRef ^ location = eg. {type="player", name="celeron55"} {type="node", pos={x=, y=, z=}} +Sounds: minetest.sound_play(spec, parameters) -> handle ^ spec = SimpleSoundSpec ^ parameters = sound parameter table minetest.sound_stop(handle) +Timing: minetest.after(time, func, param) ^ Call function after time seconds ^ param is optional; to pass multiple parameters, pass a table. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fce887047..0c7fc988c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -173,7 +173,6 @@ set(common_SRCS mapgen.cpp content_nodemeta.cpp content_mapnode.cpp - auth.cpp collision.cpp nodemetadata.cpp serverobject.cpp diff --git a/src/auth.cpp b/src/auth.cpp deleted file mode 100644 index cafeb38d4..000000000 --- a/src/auth.cpp +++ /dev/null @@ -1,306 +0,0 @@ -/* -Minetest-c55 -Copyright (C) 2011 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "auth.h" -#include -#include -//#include "main.h" // for g_settings -#include -#include "strfnd.h" -#include "log.h" - -std::set privsToSet(u64 privs) -{ - std::set s; - if(privs & PRIV_INTERACT) - s.insert("interact"); - if(privs & PRIV_TELEPORT) - s.insert("teleport"); - if(privs & PRIV_SETTIME) - s.insert("settime"); - if(privs & PRIV_PRIVS) - s.insert("privs"); - if(privs & PRIV_SERVER) - s.insert("server"); - if(privs & PRIV_SHOUT) - s.insert("shout"); - if(privs & PRIV_BAN) - s.insert("ban"); - if(privs & PRIV_GIVE) - s.insert("give"); - if(privs & PRIV_PASSWORD) - s.insert("password"); - return s; -} - -// Convert a privileges value into a human-readable string, -// with each component separated by a comma. -std::string privsToString(u64 privs) -{ - std::ostringstream os(std::ios_base::binary); - if(privs & PRIV_INTERACT) - os<<"interact,"; - if(privs & PRIV_TELEPORT) - os<<"teleport,"; - if(privs & PRIV_SETTIME) - os<<"settime,"; - if(privs & PRIV_PRIVS) - os<<"privs,"; - if(privs & PRIV_SERVER) - os<<"server,"; - if(privs & PRIV_SHOUT) - os<<"shout,"; - if(privs & PRIV_BAN) - os<<"ban,"; - if(privs & PRIV_GIVE) - os<<"give,"; - if(privs & PRIV_PASSWORD) - os<<"password,"; - if(os.tellp()) - { - // Drop the trailing comma. (Why on earth can't - // you truncate a C++ stream anyway???) - std::string tmp = os.str(); - return tmp.substr(0, tmp.length() -1); - } - return os.str(); -} - -// Converts a comma-seperated list of privilege values into a -// privileges value. The reverse of privsToString(). Returns -// PRIV_INVALID if there is anything wrong with the input. -u64 stringToPrivs(std::string str) -{ - u64 privs=0; - Strfnd f(str); - while(f.atend() == false) - { - std::string s = trim(f.next(",")); - if(s == "build") - privs |= PRIV_INTERACT; - else if(s == "interact") - privs |= PRIV_INTERACT; - else if(s == "teleport") - privs |= PRIV_TELEPORT; - else if(s == "settime") - privs |= PRIV_SETTIME; - else if(s == "privs") - privs |= PRIV_PRIVS; - else if(s == "server") - privs |= PRIV_SERVER; - else if(s == "shout") - privs |= PRIV_SHOUT; - else if(s == "ban") - privs |= PRIV_BAN; - else if(s == "give") - privs |= PRIV_GIVE; - else if(s == "password") - privs |= PRIV_PASSWORD; - else - return PRIV_INVALID; - } - return privs; -} - -AuthManager::AuthManager(const std::string &authfilepath): - m_authfilepath(authfilepath), - m_modified(false) -{ - m_mutex.Init(); - - try{ - load(); - } - catch(SerializationError &e) - { - infostream<<"WARNING: AuthManager: creating " - <::Iterator - i = m_authdata.getIterator(); - i.atEnd()==false; i++) - { - std::string name = i.getNode()->getKey(); - if(name == "") - continue; - AuthData ad = i.getNode()->getValue(); - os<::Node *n; - n = m_authdata.find(username); - if(n == NULL) - return false; - return true; -} - -void AuthManager::set(const std::string &username, AuthData ad) -{ - JMutexAutoLock lock(m_mutex); - - m_authdata[username] = ad; - - m_modified = true; -} - -void AuthManager::add(const std::string &username) -{ - JMutexAutoLock lock(m_mutex); - - m_authdata[username] = AuthData(); - - m_modified = true; -} - -std::string AuthManager::getPassword(const std::string &username) -{ - JMutexAutoLock lock(m_mutex); - - core::map::Node *n; - n = m_authdata.find(username); - if(n == NULL) - throw AuthNotFoundException(""); - - return n->getValue().pwd; -} - -void AuthManager::setPassword(const std::string &username, - const std::string &password) -{ - JMutexAutoLock lock(m_mutex); - - core::map::Node *n; - n = m_authdata.find(username); - if(n == NULL) - throw AuthNotFoundException(""); - - AuthData ad = n->getValue(); - ad.pwd = password; - n->setValue(ad); - - m_modified = true; -} - -u64 AuthManager::getPrivs(const std::string &username) -{ - JMutexAutoLock lock(m_mutex); - - core::map::Node *n; - n = m_authdata.find(username); - if(n == NULL) - throw AuthNotFoundException(""); - - return n->getValue().privs; -} - -void AuthManager::setPrivs(const std::string &username, u64 privs) -{ - JMutexAutoLock lock(m_mutex); - - core::map::Node *n; - n = m_authdata.find(username); - if(n == NULL) - throw AuthNotFoundException(""); - - AuthData ad = n->getValue(); - ad.privs = privs; - n->setValue(ad); - - m_modified = true; -} - -bool AuthManager::isModified() -{ - JMutexAutoLock lock(m_mutex); - return m_modified; -} - - diff --git a/src/auth.h b/src/auth.h deleted file mode 100644 index 6f176931a..000000000 --- a/src/auth.h +++ /dev/null @@ -1,107 +0,0 @@ -/* -Minetest-c55 -Copyright (C) 2011 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#ifndef AUTH_HEADER -#define AUTH_HEADER - -#include -#include -#include -#include -#include "irrlichttypes.h" -#include "exceptions.h" - -// Player privileges. These form a bitmask stored in the privs field -// of the player, and define things they're allowed to do. See also -// the static methods Player::privsToString and stringToPrivs that -// convert these to human-readable form. -const u64 PRIV_INTERACT = 1; // Can interact -const u64 PRIV_TELEPORT = 2; // Can teleport -const u64 PRIV_SETTIME = 4; // Can set the time -const u64 PRIV_PRIVS = 8; // Can grant and revoke privileges -const u64 PRIV_SERVER = 16; // Can manage the server (e.g. shutodwn - // ,settings) -const u64 PRIV_SHOUT = 32; // Can broadcast chat messages to all - // players -const u64 PRIV_BAN = 64; // Can ban players -const u64 PRIV_GIVE = 128; // Can give stuff -const u64 PRIV_PASSWORD = 256; // Can set other players' passwords - -// Default privileges - these can be overriden for new players using the -// config option "default_privs" - however, this value still applies for -// players that existed before the privileges system was added. -const u64 PRIV_DEFAULT = PRIV_INTERACT|PRIV_SHOUT; -const u64 PRIV_ALL = 0x7FFFFFFFFFFFFFFFULL; -const u64 PRIV_INVALID = 0x8000000000000000ULL; - -std::set privsToSet(u64 privs); - -// Convert a privileges value into a human-readable string, -// with each component separated by a comma. -std::string privsToString(u64 privs); - -// Converts a comma-seperated list of privilege values into a -// privileges value. The reverse of privsToString(). Returns -// PRIV_INVALID if there is anything wrong with the input. -u64 stringToPrivs(std::string str); - -struct AuthData -{ - std::string pwd; - u64 privs; - - AuthData(): - privs(PRIV_DEFAULT) - { - } -}; - -class AuthNotFoundException : public BaseException -{ -public: - AuthNotFoundException(const char *s): - BaseException(s) - {} -}; - -class AuthManager -{ -public: - AuthManager(const std::string &authfilepath); - ~AuthManager(); - void load(); - void save(); - bool exists(const std::string &username); - void set(const std::string &username, AuthData ad); - void add(const std::string &username); - std::string getPassword(const std::string &username); - void setPassword(const std::string &username, - const std::string &password); - u64 getPrivs(const std::string &username); - void setPrivs(const std::string &username, u64 privs); - bool isModified(); -private: - JMutex m_mutex; - std::string m_authfilepath; - core::map m_authdata; - bool m_modified; -}; - -#endif - diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp index 65894e284..4ca114a0c 100644 --- a/src/scriptapi.cpp +++ b/src/scriptapi.cpp @@ -141,14 +141,14 @@ static Server* get_server(lua_State *L) return server; } -static ServerEnvironment* get_env(lua_State *L) +/*static ServerEnvironment* get_env(lua_State *L) { // Get environment from registry lua_getfield(L, LUA_REGISTRYINDEX, "minetest_env"); ServerEnvironment *env = (ServerEnvironment*)lua_touserdata(L, -1); lua_pop(L, 1); return env; -} +}*/ static void objectref_get(lua_State *L, u16 id) { @@ -647,6 +647,27 @@ static void read_groups(lua_State *L, int index, } } +/* + Privileges +*/ +static void read_privileges(lua_State *L, int index, + std::set &result) +{ + result.clear(); + lua_pushnil(L); + if(index < 0) + index -= 1; + while(lua_next(L, index) != 0){ + // key at index -2 and value at index -1 + std::string key = luaL_checkstring(L, -2); + bool value = lua_toboolean(L, -1); + if(value) + result.insert(key); + // removes value, keeps key for next iteration + lua_pop(L, 1); + } +} + /* ToolCapabilities */ @@ -3837,8 +3858,7 @@ static int l_get_player_privs(lua_State *L) // Do it lua_newtable(L); int table = lua_gettop(L); - u64 privs_i = server->getPlayerEffectivePrivs(name); - std::set privs_s = privsToSet(privs_i); + std::set privs_s = server->getPlayerEffectivePrivs(name); for(std::set::const_iterator i = privs_s.begin(); i != privs_s.end(); i++){ lua_pushboolean(L, true); @@ -3954,6 +3974,17 @@ static int l_is_singleplayer(lua_State *L) return 1; } +// get_password_hash(name, raw_password) +static int l_get_password_hash(lua_State *L) +{ + std::string name = luaL_checkstring(L, 1); + std::string raw_password = luaL_checkstring(L, 2); + std::string hash = translatePassword(name, + narrow_to_wide(raw_password)); + lua_pushstring(L, hash.c_str()); + return 1; +} + static const struct luaL_Reg minetest_f [] = { {"debug", l_debug}, {"log", l_log}, @@ -3974,6 +4005,7 @@ static const struct luaL_Reg minetest_f [] = { {"sound_play", l_sound_play}, {"sound_stop", l_sound_stop}, {"is_singleplayer", l_is_singleplayer}, + {"get_password_hash", l_get_password_hash}, {NULL, NULL} }; @@ -4421,6 +4453,10 @@ void scriptapi_on_leaveplayer(lua_State *L, ServerActiveObject *player) void scriptapi_get_creative_inventory(lua_State *L, ServerActiveObject *player) { + realitycheck(L); + assert(lua_checkstack(L, 20)); + StackUnroller stack_unroller(L); + Inventory *inv = player->getInventory(); assert(inv); @@ -4430,6 +4466,91 @@ void scriptapi_get_creative_inventory(lua_State *L, ServerActiveObject *player) inventory_set_list_from_lua(inv, "main", L, -1, PLAYER_INVENTORY_SIZE); } +static void get_auth_handler(lua_State *L) +{ + lua_getglobal(L, "minetest"); + lua_getfield(L, -1, "registered_auth_handler"); + if(lua_isnil(L, -1)){ + lua_pop(L, 1); + lua_getfield(L, -1, "builtin_auth_handler"); + } + if(lua_type(L, -1) != LUA_TTABLE) + throw LuaError(L, "Authentication handler table not valid"); +} + +bool scriptapi_get_auth(lua_State *L, const std::string &playername, + std::string *dst_password, std::set *dst_privs) +{ + realitycheck(L); + assert(lua_checkstack(L, 20)); + StackUnroller stack_unroller(L); + + get_auth_handler(L); + lua_getfield(L, -1, "get_auth"); + if(lua_type(L, -1) != LUA_TFUNCTION) + throw LuaError(L, "Authentication handler missing get_auth"); + lua_pushstring(L, playername.c_str()); + if(lua_pcall(L, 1, 1, 0)) + script_error(L, "error: %s", lua_tostring(L, -1)); + + // nil = login not allowed + if(lua_isnil(L, -1)) + return false; + luaL_checktype(L, -1, LUA_TTABLE); + + std::string password; + bool found = getstringfield(L, -1, "password", password); + if(!found) + throw LuaError(L, "Authentication handler didn't return password"); + if(dst_password) + *dst_password = password; + + lua_getfield(L, -1, "privileges"); + if(!lua_istable(L, -1)) + throw LuaError(L, + "Authentication handler didn't return privilege table"); + if(dst_privs) + read_privileges(L, -1, *dst_privs); + lua_pop(L, 1); + + return true; +} + +void scriptapi_create_auth(lua_State *L, const std::string &playername, + const std::string &password) +{ + realitycheck(L); + assert(lua_checkstack(L, 20)); + StackUnroller stack_unroller(L); + + get_auth_handler(L); + lua_getfield(L, -1, "create_auth"); + if(lua_type(L, -1) != LUA_TFUNCTION) + throw LuaError(L, "Authentication handler missing create_auth"); + lua_pushstring(L, playername.c_str()); + lua_pushstring(L, password.c_str()); + if(lua_pcall(L, 2, 0, 0)) + script_error(L, "error: %s", lua_tostring(L, -1)); +} + +bool scriptapi_set_password(lua_State *L, const std::string &playername, + const std::string &password) +{ + realitycheck(L); + assert(lua_checkstack(L, 20)); + StackUnroller stack_unroller(L); + + get_auth_handler(L); + lua_getfield(L, -1, "set_password"); + if(lua_type(L, -1) != LUA_TFUNCTION) + throw LuaError(L, "Authentication handler missing set_password"); + lua_pushstring(L, playername.c_str()); + lua_pushstring(L, password.c_str()); + if(lua_pcall(L, 2, 1, 0)) + script_error(L, "error: %s", lua_tostring(L, -1)); + return lua_toboolean(L, -1); +} + /* item callbacks and node callbacks */ diff --git a/src/scriptapi.h b/src/scriptapi.h index b80039f9e..fe8b26e04 100644 --- a/src/scriptapi.h +++ b/src/scriptapi.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes.h" #include #include "mapnode.h" +#include class Server; class ServerEnvironment; @@ -60,6 +61,12 @@ bool scriptapi_on_respawnplayer(lua_State *L, ServerActiveObject *player); void scriptapi_on_joinplayer(lua_State *L, ServerActiveObject *player); void scriptapi_on_leaveplayer(lua_State *L, ServerActiveObject *player); void scriptapi_get_creative_inventory(lua_State *L, ServerActiveObject *player); +bool scriptapi_get_auth(lua_State *L, const std::string &playername, + std::string *dst_password, std::set *dst_privs); +void scriptapi_create_auth(lua_State *L, const std::string &playername, + const std::string &password); +bool scriptapi_set_password(lua_State *L, const std::string &playername, + const std::string &password); /* item callbacks */ bool scriptapi_item_on_drop(lua_State *L, ItemStack &item, diff --git a/src/server.cpp b/src/server.cpp index 4a6165f30..e9b236cc4 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -142,6 +142,10 @@ void * ServerThread::Thread() { m_server->setAsyncFatalError(e.what()); } + catch(LuaError &e) + { + m_server->setAsyncFatalError(e.what()); + } } END_DEBUG_EXCEPTION_HANDLER(errorstream) @@ -905,7 +909,6 @@ Server::Server( m_async_fatal_error(""), m_env(NULL), m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this), - m_authmanager(path_world+DIR_DELIM+"auth.txt"), m_banmanager(path_world+DIR_DELIM+"ipban.txt"), m_lua(NULL), m_itemdef(createItemDefManager()), @@ -1844,10 +1847,6 @@ void Server::AsyncRunStep() ScopeProfiler sp(g_profiler, "Server: saving stuff"); - // Auth stuff - if(m_authmanager.isModified()) - m_authmanager.save(); - //Ban stuff if(m_banmanager.isModified()) m_banmanager.save(); @@ -2083,34 +2082,30 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) password[PASSWORD_SIZE-1] = 0; } - // Add player to auth manager - if(m_authmanager.exists(playername) == false) - { - std::wstring default_password = + std::string checkpwd; + bool has_auth = scriptapi_get_auth(m_lua, playername, &checkpwd, NULL); + + if(!has_auth){ + std::wstring raw_default_password = narrow_to_wide(g_settings->get("default_password")); - std::string translated_default_password = - translatePassword(playername, default_password); + std::string use_password = + translatePassword(playername, raw_default_password); // If default_password is empty, allow any initial password - if (default_password.length() == 0) - translated_default_password = password; + if (raw_default_password.length() == 0) + use_password = password; - infostream<<"Server: adding player "<get("default_privs"))); - m_authmanager.save(); + scriptapi_create_auth(m_lua, playername, use_password); + } + + has_auth = scriptapi_get_auth(m_lua, playername, &checkpwd, NULL); + + if(!has_auth){ + SendAccessDenied(m_con, peer_id, L"Not allowed to login"); + return; } - std::string checkpwd = m_authmanager.getPassword(playername); - - /*infostream<<"Server: Client gave password '"<= g_settings->getU16("max_users") && - (m_authmanager.getPrivs(playername) - & (PRIV_SERVER|PRIV_BAN|PRIV_PRIVS|PRIV_PASSWORD)) == 0 && + !checkPriv(playername, "server") && + !checkPriv(playername, "ban") && + !checkPriv(playername, "privs") && + !checkPriv(playername, "password") && playername != g_settings->get("name")) { actionstream<<"Server: "<getName(), "interact")) return; /* u16 command @@ -2519,9 +2516,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) // Disallow moving items in elsewhere than player's inventory // if not allowed to interact - if((getPlayerPrivs(player) & PRIV_INTERACT) == 0 - && (!from_inv_is_current_player - || !to_inv_is_current_player)) + if(!checkPriv(player->getName(), "interact") && + (!from_inv_is_current_player || + !to_inv_is_current_player)) { infostream<<"Cannot move outside of player's inventory: " <<"No interact privilege"<getName(), "server")) { std::string owner_from = getInventoryOwner(ma->from_inv); if(owner_from != "" && owner_from != player->getName()) @@ -2565,13 +2562,13 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) setInventoryModified(da->from_inv); // Disallow dropping items if not allowed to interact - if((getPlayerPrivs(player) & PRIV_INTERACT) == 0) + if(!checkPriv(player->getName(), "interact")) { delete a; return; } // If player is not an admin, check for ownership - else if((getPlayerPrivs(player) & PRIV_SERVER) == 0) + else if(!checkPriv(player->getName(), "server")) { std::string owner_from = getInventoryOwner(da->from_inv); if(owner_from != "" && owner_from != player->getName()) @@ -2600,7 +2597,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) // (ca->craft_inv.name == player->getName()); // Disallow crafting if not allowed to interact - if((getPlayerPrivs(player) & PRIV_INTERACT) == 0) + if(!checkPriv(player->getName(), "interact")) { infostream<<"Cannot craft: " <<"No interact privilege"<getName(), "server")) { std::string owner_craft = getInventoryOwner(ca->craft_inv); if(owner_craft != "" && owner_craft != player->getName()) @@ -2667,10 +2664,6 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) // Whether to send to other players bool send_to_others = false; - // Local player gets all privileges regardless of - // what's set on their account. - u64 privs = getPlayerPrivs(player); - // Parse commands if(message[0] == L'/') { @@ -2688,8 +2681,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) paramstring, this, m_env, - player, - privs); + player); std::wstring reply(processServerCommand(ctx)); send_to_sender = ctx->flags & SEND_TO_SENDER; @@ -2705,16 +2697,13 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) } else { - if(privs & PRIV_SHOUT) - { + if(checkPriv(player->getName(), "shout")){ line += L"<"; line += name; line += L"> "; line += message; send_to_others = true; - } - else - { + } else { line += L"Server: You are not allowed to shout"; send_to_sender = true; } @@ -2803,15 +2792,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) std::string playername = player->getName(); - if(m_authmanager.exists(playername) == false) - { - infostream<<"Server: playername not found in authmanager"<getName()<<" changes password"<getName()<<" changes password"<getName()<<" tries to change password but " + <<"it fails"<getName(), "interact")) { infostream<<"Ignoring interaction from player "<getName() - <<" because privileges are "< Server::getPlayerEffectivePrivs(const std::string &name) { - try{ - return m_authmanager.getPrivs(name); - } - catch(AuthNotFoundException &e) - { - dstream<<"WARNING: Auth not found for "< privs; + scriptapi_get_auth(m_lua, name, NULL, &privs); + return privs; } -void Server::setPlayerAuthPrivs(const std::string &name, u64 privs) +bool Server::checkPriv(const std::string &name, const std::string &priv) { - try{ - return m_authmanager.setPrivs(name, privs); - } - catch(AuthNotFoundException &e) - { - dstream<<"WARNING: Auth not found for "<get("name")) - return PRIV_ALL; - return getPlayerAuthPrivs(name); -} - -void Server::setPlayerPassword(const std::string &name, const std::wstring &password) -{ - // Add player to auth manager - if(m_authmanager.exists(name) == false) - { - infostream<<"Server: adding player "<get("default_privs"))); - } - // Change password and save - m_authmanager.setPassword(name, translatePassword(name, password)); - m_authmanager.save(); + std::set privs = getPlayerEffectivePrivs(name); + return (privs.count(priv) != 0); } // Saves g_settings to configpath given at initialization @@ -4698,14 +4644,6 @@ void Server::handlePeerChanges() } } -u64 Server::getPlayerPrivs(Player *player) -{ - if(player==NULL) - return 0; - std::string playername = player->getName(); - return getPlayerEffectivePrivs(playername); -} - void dedicated_server_loop(Server &server, bool &kill) { DSTACK(__FUNCTION_NAME); diff --git a/src/server.h b/src/server.h index d40c7c73e..4b04044e1 100644 --- a/src/server.h +++ b/src/server.h @@ -27,7 +27,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "porting.h" #include "map.h" #include "inventory.h" -#include "auth.h" #include "ban.h" #include "gamedef.h" #include "serialization.h" // For SER_FMT_VER_INVALID @@ -500,15 +499,10 @@ public: s32 playSound(const SimpleSoundSpec &spec, const ServerSoundParams ¶ms); void stopSound(s32 handle); - // Thread-safe - u64 getPlayerAuthPrivs(const std::string &name); - void setPlayerAuthPrivs(const std::string &name, u64 privs); - u64 getPlayerEffectivePrivs(const std::string &name); + // Envlock + conlock + std::set getPlayerEffectivePrivs(const std::string &name); + bool checkPriv(const std::string &name, const std::string &priv); - // Changes a player's password, password must be given as plaintext - // If the player doesn't exist, a new entry is added to the auth manager - void setPlayerPassword(const std::string &name, const std::wstring &password); - // Saves g_settings to configpath given at initialization void saveConfig(); @@ -670,8 +664,6 @@ private: void handlePeerChange(PeerChange &c); void handlePeerChanges(); - u64 getPlayerPrivs(Player *player); - /* Variables */ @@ -710,9 +702,6 @@ private: // Connected clients (behind the con mutex) core::map m_clients; - // User authentication - AuthManager m_authmanager; - // Bann checking BanManager m_banmanager; diff --git a/src/servercommand.cpp b/src/servercommand.cpp index 48ada56fe..64c43d4f5 100644 --- a/src/servercommand.cpp +++ b/src/servercommand.cpp @@ -38,98 +38,6 @@ void cmd_me(std::wostringstream &os, ctx->flags |= SEND_TO_OTHERS | SEND_NO_PREFIX; } -void cmd_privs(std::wostringstream &os, - ServerCommandContext *ctx) -{ - if(ctx->parms.size() == 1) - { - // Show our own real privs, without any adjustments - // made for admin status - os<server->getPlayerAuthPrivs(ctx->player->getName()))); - return; - } - - if((ctx->privs & PRIV_PRIVS) == 0) - { - os<env->getPlayer(wide_to_narrow(ctx->parms[1]).c_str()); - if(tp == NULL) - { - os<server->getPlayerAuthPrivs(tp->getName()))); -} - -void cmd_grantrevoke(std::wostringstream &os, - ServerCommandContext *ctx) -{ - if(ctx->parms.size() != 3) - { - os<privs & PRIV_PRIVS) == 0) - { - os<parms[2])); - if(newprivs == PRIV_INVALID) - { - os<env->getPlayer(wide_to_narrow(ctx->parms[1]).c_str()); - if(tp == NULL) - { - os<parms[1]); - u64 privs = ctx->server->getPlayerAuthPrivs(playername); - - if(ctx->parms[0] == L"grant"){ - privs |= newprivs; - actionstream<player->getName()<<" grants " - <parms[2])<<" to " - <player->getName()); - msg += L" granted you the privilege \""; - msg += ctx->parms[2]; - msg += L"\""; - ctx->server->notifyPlayer(playername.c_str(), msg); - } else { - privs &= ~newprivs; - actionstream<player->getName()<<" revokes " - <parms[2])<<" from " - <player->getName()); - msg += L" revoked from you the privilege \""; - msg += ctx->parms[2]; - msg += L"\""; - ctx->server->notifyPlayer(playername.c_str(), msg); - } - - ctx->server->setPlayerAuthPrivs(playername, privs); - - os<privs & PRIV_SETTIME) ==0) + + if(!ctx->server->checkPriv(ctx->player->getName(), "settime")) { os<privs & PRIV_SERVER) ==0) + if(!ctx->server->checkPriv(ctx->player->getName(), "server")) { os<privs & PRIV_SERVER) ==0) + if(!ctx->server->checkPriv(ctx->player->getName(), "server")) { os<privs & PRIV_TELEPORT) ==0) + if(!ctx->server->checkPriv(ctx->player->getName(), "teleport")) { os<privs & PRIV_BAN) == 0) + if(!ctx->server->checkPriv(ctx->player->getName(), "ban")) { os<privs & PRIV_PASSWORD) == 0) - { - os<parms[0] == L"setpassword") - { - if(ctx->parms.size() != 3) - { - os<parms[1]); - password = ctx->parms[2]; - - actionstream<player->getName()<<" sets password of " - <parms.size() != 2) - { - os<parms[1]); - password = L""; - - actionstream<player->getName()<<" clears password of" - <server->setPlayerPassword(playername, password); - - std::wostringstream msg; - msg<player->getName()<server->notifyPlayer(playername.c_str(), msg.str()); - - os<privs & PRIV_SERVER) ==0) + if(!ctx->server->checkPriv(ctx->player->getName(), "server")) { os<parms[0] == L"status") cmd_status(os, ctx); - else if(ctx->parms[0] == L"privs") - cmd_privs(os, ctx); - else if(ctx->parms[0] == L"grant" || ctx->parms[0] == L"revoke") - cmd_grantrevoke(os, ctx); else if(ctx->parms[0] == L"time") cmd_time(os, ctx); else if(ctx->parms[0] == L"shutdown") @@ -392,8 +244,6 @@ std::wstring processServerCommand(ServerCommandContext *ctx) cmd_teleport(os, ctx); else if(ctx->parms[0] == L"ban" || ctx->parms[0] == L"unban") cmd_banunban(os, ctx); - else if(ctx->parms[0] == L"setpassword" || ctx->parms[0] == L"clearpassword") - cmd_setclearpassword(os, ctx); else if(ctx->parms[0] == L"me") cmd_me(os, ctx); else if(ctx->parms[0] == L"clearobjects") diff --git a/src/servercommand.h b/src/servercommand.h index 648a57332..a3cf750da 100644 --- a/src/servercommand.h +++ b/src/servercommand.h @@ -36,9 +36,6 @@ struct ServerCommandContext Server* server; ServerEnvironment *env; Player* player; - // Effective privs for the player, which may be different to their - // stored ones - e.g. if they are named in the config as an admin. - u64 privs; u32 flags; ServerCommandContext( @@ -46,10 +43,9 @@ struct ServerCommandContext std::wstring paramstring, Server* server, ServerEnvironment *env, - Player* player, - u64 privs) + Player* player) : parms(parms), paramstring(paramstring), - server(server), env(env), player(player), privs(privs) + server(server), env(env), player(player) { }