From 573fa4b334884274dee865b87ae844c017f23433 Mon Sep 17 00:00:00 2001 From: proller Date: Thu, 17 Dec 2015 20:58:34 +0300 Subject: [PATCH] allow use kv db with any name from lua. add auth kv backend (not used by default, to enable: auth_kv=1 ) --- builtin/game/fm_auth.lua | 215 +++++++++++++++++++++ builtin/game/init.lua | 6 +- builtin/key_value_storage.lua | 18 +- builtin/settingtypes.txt | 3 + src/environment.cpp | 21 +- src/environment.h | 6 +- src/fm_server.cpp | 5 +- src/key_value_storage.cpp | 2 + src/key_value_storage.h | 2 +- src/script/lua_api/l_key_value_storage.cpp | 30 ++- 10 files changed, 270 insertions(+), 38 deletions(-) create mode 100644 builtin/game/fm_auth.lua diff --git a/builtin/game/fm_auth.lua b/builtin/game/fm_auth.lua new file mode 100644 index 000000000..64bcee055 --- /dev/null +++ b/builtin/game/fm_auth.lua @@ -0,0 +1,215 @@ +-- +-- Authentication handler +-- + +function core.string_to_privs(str, delim) + if type(str) ~= "string" then return end + delim = delim or ',' + local privs = {} + for _, priv in pairs(string.split(str, delim)) do + privs[priv:trim()] = true + end + return privs +end + +core.auth_file_path = core.get_worldpath().."/auth.txt" +core.auth_table = {} +core.auth_prefix = "auth_" + +local function read_auth(name) + core.auth_table[name] = core.kv_get(core.auth_prefix .. name, "player_auth") + return core.auth_table[name] + --core.notify_authentication_modified(name) +end + +local function save_auth(name, data) + if not data then data = core.auth_table[name] end + return core.kv_put(core.auth_prefix .. name, data, "player_auth") +end + + +local hex={} +for i=0,255 do + hex[string.format("%0x",i)]=string.char(i) + hex[string.format("%0X",i)]=string.char(i) +end + +local function uri_decode(str) + str = string.gsub (str, "+", " ") + return (str:gsub('%%(%x%x)',hex)) +end + +function uri_encode (str) + str = string.gsub (str, "([^0-9a-zA-Z_ -])", function (c) return string.format ("%%%02X", string.byte(c)) end) + str = string.gsub (str, " ", "+") + return str +end + +local function auth_convert(force) + local newtable = {} + local file, errmsg = io.open(core.auth_file_path, 'rb') + if not file then + --core.log("info", core.auth_file_path.." could not be opened for reading ("..errmsg.."); assuming new world") + return + end + core.log("action", "Converting auth to kv " .. core.auth_file_path .. " ") + local n = 0 + for line in file:lines() do + n = n + 1 + if line ~= "" then + local fields = line:split(":", true) + local name, password, privilege_string, last_login = unpack(fields) + last_login = tonumber(last_login) + if not (name and password and privilege_string) then + print("Invalid line in auth.txt:" .. n .. " " .. dump(line)) + else + name = uri_decode(name); + local old = read_auth(name) --core.kv_get(core.auth_prefix .. name, "player_auth") + --print("readed " .. name .. " d=" .. core.serialize(old)) + if old and not force then + print("Player [" .. name .. "] already converted, skipping") + else + --print("Saving player " .. name .. " p="..password) + local privileges = core.string_to_privs(privilege_string) + local data = {name=name, password=password, privileges=privileges, last_login=last_login} + save_auth(name, data) + --print("save res= " .. core.serialize(read_auth(name))) + end + end + end + end + io.close(file) + os.rename(core.auth_file_path, core.auth_file_path..'.old') + core.auth_table = {} +end + +local converted = nil + +core.builtin_auth_handler = { + get_auth = function(name) + if not conveeted then auth_convert(); converted = 1 end -- here because env needed + assert(type(name) == "string") + read_auth(name) + + -- Figure out what password to use for a new player (singleplayer + -- always has an empty password, otherwise use default, which is + -- usually empty too) + local new_password_hash = "" + -- If not in authentication table, return nil + if not core.auth_table[name] then + return nil + end + -- Figure out what privileges the player should have. + -- Take a copy of the privilege table + local privileges = {} + for priv, _ in pairs(core.auth_table[name].privileges) do + privileges[priv] = true + end + -- If singleplayer, give all privileges except those marked as give_to_singleplayer = false + if core.is_singleplayer() then + for priv, def in pairs(core.registered_privileges) do + if def.give_to_singleplayer then + privileges[priv] = true + end + end + -- For the admin, give everything + elseif name == core.setting_get("name") then + for priv, def in pairs(core.registered_privileges) do + privileges[priv] = true + end + end + -- All done + return { + password = core.auth_table[name].password, + privileges = privileges, + -- Is set to nil if unknown + last_login = core.auth_table[name].last_login, + } + end, + create_auth = function(name, password) + assert(type(name) == "string") + assert(type(password) == "string") + core.log('info', "Built-in authentication handler adding player '"..name.."'") + local privs = core.setting_get("default_privs") + if core.setting_getbool("creative_mode") and core.setting_get("default_privs_creative") then + privs = core.setting_get("default_privs_creative") + end + core.auth_table[name] = { + password = password, + privileges = core.string_to_privs(privs), + last_login = os.time(), + } + save_auth(name) + end, + set_password = function(name, password) + assert(type(name) == "string") + assert(type(password) == "string") + read_auth(name) + if not core.auth_table[name] then + core.builtin_auth_handler.create_auth(name, password) + else + core.log('info', "Built-in authentication handler setting password of player '"..name.."'") + core.auth_table[name].password = password + save_auth(name) + end + return true + end, + set_privileges = function(name, privileges) + assert(type(name) == "string") + assert(type(privileges) == "table") + read_auth(name) + if not core.auth_table[name] then + core.builtin_auth_handler.create_auth(name, + core.get_password_hash(name, + core.setting_get("default_password"))) + end + core.auth_table[name].privileges = privileges + core.notify_authentication_modified(name) + save_auth(name) + end, + reload = function() + --read_auth_file() + return true + end, + record_login = function(name) + assert(type(name) == "string") + read_auth(name) + assert(core.auth_table[name]).last_login = os.time() + save_auth(name) + end, +} + +function core.register_authentication_handler(handler) + if core.registered_auth_handler then + error("Add-on authentication handler already registered by "..core.registered_auth_handler_modname) + end + core.registered_auth_handler = handler + core.registered_auth_handler_modname = core.get_current_modname() + handler.mod_origin = core.registered_auth_handler_modname +end + +function core.get_auth_handler() + return core.registered_auth_handler or core.builtin_auth_handler +end + +local function auth_pass(name) + return function(...) + local auth_handler = core.get_auth_handler() + if auth_handler[name] then + return auth_handler[name](...) + end + return false + end +end + +core.set_player_password = auth_pass("set_password") +core.set_player_privs = auth_pass("set_privileges") +core.auth_reload = auth_pass("reload") + + +local record_login = auth_pass("record_login") + +core.register_on_joinplayer(function(player) + record_login(player:get_player_name()) +end) + diff --git a/builtin/game/init.lua b/builtin/game/init.lua index 9ed4a5e88..03400dead 100644 --- a/builtin/game/init.lua +++ b/builtin/game/init.lua @@ -17,7 +17,11 @@ dofile(gamepath.."item_entity.lua") dofile(gamepath.."deprecated.lua") dofile(gamepath.."misc.lua") dofile(gamepath.."privileges.lua") -dofile(gamepath.."auth.lua") +if core.setting_getbool("auth_kv") then + dofile(gamepath.."fm_auth.lua") +else + dofile(gamepath.."auth.lua") +end dofile(gamepath.."stat.lua") dofile(gamepath.."chatcommands.lua") dofile(gamepath.."static_spawn.lua") diff --git a/builtin/key_value_storage.lua b/builtin/key_value_storage.lua index 39c740fe0..3b33d87ca 100644 --- a/builtin/key_value_storage.lua +++ b/builtin/key_value_storage.lua @@ -2,25 +2,27 @@ -- Key-value storage stuff -- -function core.kv_put(key, data) +function core.kv_put(key, data, db) local json = core.write_json(data) if not json then core.log("error", "kv_put: Error in json serialize key=".. key .. " luaized_data=" .. core.serialize(data)) return end - return core.kv_put_string(key, json) + --core.log("action", "core.kv_put: key=".. key .. " json=" .. json .. " db=" .. db) + return core.kv_put_string(key, json, db) end -function core.kv_get(key) - local data = core.kv_get_string(key) +function core.kv_get(key, db) + local data = core.kv_get_string(key, db) + --core.log("action", "core.kv_get: key=".. key .. " json=" .. (data or '') .. " db=" .. db) if data ~= nil then data = core.parse_json(data) end return data end -function core.kv_rename(key1, key2) - local data = core.kv_get_string(key1) - core.kv_delete(key1) - core.kv_put_string(key2, data) +function core.kv_rename(key1, key2, db) + local data = core.kv_get_string(key1, db) + core.kv_delete(key1, db) + core.kv_put_string(key2, data, db) end diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 14bbcd044..56dad7c9b 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1143,6 +1143,9 @@ selectionbox_color () string (0,0,0) [*Server] +# Save auth data to leveldb kv storage +auth_kv () bool false + # If true, blocks are cached (and generated if not before) before a player is spawned. cache_block_before_spawn () bool true diff --git a/src/environment.cpp b/src/environment.cpp index 2d83538db..a07fb931d 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -374,8 +374,6 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, m_script(scriptIface), m_gamedef(gamedef), m_circuit(m_script, map, gamedef->ndef(), path_world), - m_key_value_storage(path_world, "key_value_storage"), - m_players_storage(path_world, "players"), m_path_world(path_world), m_send_recommended_timer(0), m_active_objects_last(0), @@ -393,11 +391,6 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, m_use_weather = g_settings->getBool("weather"); m_use_weather_biome = g_settings->getBool("weather_biome"); - if (!m_key_value_storage.db) - errorstream << "Cant open KV storage: "<< m_key_value_storage.error << std::endl; - if (!m_players_storage.db) - errorstream << "Cant open players storage: "<< m_players_storage.error << std::endl; - // Init custom SAO v3f nullpos; //epixel::Creature* c = new epixel::Creature(NULL, nullpos, "", ""); @@ -449,9 +442,15 @@ ServerMap & ServerEnvironment::getServerMap() return *m_map; } -KeyValueStorage *ServerEnvironment::getKeyValueStorage() +KeyValueStorage &ServerEnvironment::getKeyValueStorage(std::string name) { - return &m_key_value_storage; + if (name.empty()) { + name = "key_value_storage"; + } + if (!m_key_value_storage.count(name)) { + m_key_value_storage.emplace(std::piecewise_construct, std::forward_as_tuple(name), std::forward_as_tuple(m_path_world, name)); + } + return m_key_value_storage.at(name); } bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize, v3s16 *p) @@ -516,7 +515,7 @@ void ServerEnvironment::savePlayer(RemotePlayer *player) return; Json::Value player_json; player_json << *player; - m_players_storage.put_json("p." + player->getName(), player_json); + getPlayerStorage().put_json("p." + player->getName(), player_json); } Player * ServerEnvironment::loadPlayer(const std::string &playername) @@ -532,7 +531,7 @@ Player * ServerEnvironment::loadPlayer(const std::string &playername) try { Json::Value player_json; - m_players_storage.get_json("p." + playername, player_json); + getPlayerStorage().get_json("p." + playername, player_json); verbosestream<<"Reading kv player "<> *player; diff --git a/src/environment.h b/src/environment.h index 67df081a7..2288125d0 100644 --- a/src/environment.h +++ b/src/environment.h @@ -290,7 +290,8 @@ public: //Player * getPlayer(u16 peer_id) { return Environment::getPlayer(peer_id); }; //Player * getPlayer(const std::string &name); - KeyValueStorage *getKeyValueStorage(); + KeyValueStorage &getKeyValueStorage(std::string name = "key_value_storage"); + KeyValueStorage &getPlayerStorage() { return getKeyValueStorage("player"); }; void kickAllPlayers(AccessDeniedCode reason, const std::string &str_reason, bool reconnect); @@ -477,8 +478,7 @@ private: Circuit m_circuit; // Key-value storage public: - KeyValueStorage m_key_value_storage; - KeyValueStorage m_players_storage; + std::unordered_map m_key_value_storage; private: // World path diff --git a/src/fm_server.cpp b/src/fm_server.cpp index e4807b81b..c3ee5e5ac 100644 --- a/src/fm_server.cpp +++ b/src/fm_server.cpp @@ -294,8 +294,7 @@ void Server::maintenance_start() { m_env->getServerMap().m_map_saving_enabled = false; m_env->getServerMap().m_map_loading_enabled = false; m_env->getServerMap().dbase->close(); - m_env->m_key_value_storage.close(); - m_env->m_players_storage.close(); + m_env->m_key_value_storage.clear(); stat.close(); actionstream << "Server: Starting maintenance: bases closed now." << std::endl; @@ -303,8 +302,6 @@ void Server::maintenance_start() { void Server::maintenance_end() { m_env->getServerMap().dbase->open(); - m_env->m_key_value_storage.open(); - m_env->m_players_storage.open(); stat.open(); m_env->getServerMap().m_map_saving_enabled = true; m_env->getServerMap().m_map_loading_enabled = true; diff --git a/src/key_value_storage.cpp b/src/key_value_storage.cpp index 454810483..5008e71fa 100644 --- a/src/key_value_storage.cpp +++ b/src/key_value_storage.cpp @@ -36,6 +36,7 @@ bool KeyValueStorage::open() { options.create_if_missing = true; std::lock_guard lock(mutex); auto status = leveldb::DB::Open(options, fullpath, &db); + //errorstream<<"KeyValueStorage::open() "<getKeyValueStorage()->put(key, data); + std::string key = luaL_checkstring(L, 1); + std::string data = luaL_checkstring(L, 2); + + std::string db; + if (lua_isstring(L, 3)) + db = luaL_checkstring(L, 3); + env->getKeyValueStorage(db).put(key, data); return 0; } @@ -44,9 +48,12 @@ int ModApiKeyValueStorage::l_kv_get_string(lua_State *L) { GET_ENV_PTR_NO_MAP_LOCK; - const char *key = luaL_checkstring(L, 1); + std::string key = luaL_checkstring(L, 1); + std::string db; + if (lua_isstring(L, 2)) + db = luaL_checkstring(L, 2); std::string data; - if(env->getKeyValueStorage()->get(key, data)) { + if(env->getKeyValueStorage(db).get(key, data)) { lua_pushstring(L, data.c_str()); return 1; } else { @@ -58,8 +65,11 @@ int ModApiKeyValueStorage::l_kv_delete(lua_State *L) { GET_ENV_PTR_NO_MAP_LOCK; - const char *key = luaL_checkstring(L, 1); - env->getKeyValueStorage()->del(key); + std::string key = luaL_checkstring(L, 1); + std::string db; + if (lua_isstring(L, 2)) + db = luaL_checkstring(L, 2); + env->getKeyValueStorage(db).del(key); return 0; } @@ -73,7 +83,7 @@ int ModApiKeyValueStorage::l_stat_get(lua_State *L) { GET_ENV_PTR_NO_MAP_LOCK; - const char *key = luaL_checkstring(L, 1); + std::string key = luaL_checkstring(L, 1); lua_pushnumber(L, getServer(L)->stat.get(key)); return 1; } @@ -82,9 +92,9 @@ int ModApiKeyValueStorage::l_stat_add(lua_State *L) { GET_ENV_PTR_NO_MAP_LOCK; - const char *key = luaL_checkstring(L, 1); + std::string key = luaL_checkstring(L, 1); - const char *name = ""; + std::string name; if(lua_isstring(L, 2)) name = lua_tostring(L, 1);