-- whitelist.lua -- Implements the whitelist-related commands, console commands, API and storage --- The SQLite handle to the whitelist database: local WhitelistDB --- Global flag whether whitelist is enabled: local g_IsWhitelistEnabled = false --- Loads the config from the DB -- If any value cannot be read, it is kept unchanged local function LoadConfig() -- Read the g_IsWhitelistEnabled value: WhitelistDB:ExecuteStatement( "SELECT Value FROM WhitelistConfig WHERE Name='isEnabled'", {}, function (a_Val) g_IsWhitelistEnabled = (a_Val["Value"] == "true") end ) end --- Saves the current config into the DB local function SaveConfig() -- Remove the value, if it exists: WhitelistDB:ExecuteStatement( "DELETE FROM WhitelistConfig WHERE Name='isEnabled'", {} ) -- Insert the current value: WhitelistDB:ExecuteStatement( "INSERT INTO WhitelistConfig(Name, Value) VALUES ('isEnabled', ?)", { tostring(g_IsWhitelistEnabled) } ) end --- API: Adds the specified player to the whitelist -- Resolves the player UUID, if needed, but only through cache, not to block -- Returns true on success, false and optional error message on failure function AddPlayerToWhitelist(a_PlayerName, a_WhitelistedBy) -- Check params: assert(type(a_PlayerName) == "string") assert(type(a_WhitelistedBy) == "string") -- Resolve the player name to OfflineUUID and possibly OnlineUUID (if server is in online mode): local UUID = "" if (cRoot:Get():GetServer():ShouldAuthenticate()) then UUID = cMojangAPI:GetUUIDFromPlayerName(a_PlayerName, true) -- If the UUID cannot be resolved, leave it as an empty string, it will be resolved on next startup / eventually end local OfflineUUID = cClientHandle:GenerateOfflineUUID(a_PlayerName) -- Insert into DB: return WhitelistDB:ExecuteStatement( "INSERT INTO WhitelistNames (Name, UUID, OfflineUUID, Timestamp, WhitelistedBy) VALUES (?, ?, ?, ?, ?)", { a_PlayerName, UUID, OfflineUUID, os.time(), a_WhitelistedBy, } ) end --- API: Checks if the player is whitelisted -- Returns true if whitelisted, false if not -- Uses UUID for the check, and the playername with an empty UUID for a secondary check function IsPlayerWhitelisted(a_PlayerUUID, a_PlayerName) -- Check params: assert(type(a_PlayerUUID) == "string") assert(type(a_PlayerName) == "string") local UUID = a_PlayerUUID if (UUID == "") then -- There is no UUID supplied for the player, do not search by the UUID by using a dummy impossible value: UUID = "DummyImpossibleValue" end -- Query the DB: local offlineUUID = cClientHandle:GenerateOfflineUUID(a_PlayerName) local isWhitelisted assert(WhitelistDB:ExecuteStatement( [[ SELECT Name FROM WhitelistNames WHERE (UUID = ?) OR (OfflineUUID = ?) OR ((UUID = '') AND (Name = ?)) ]], { UUID, offlineUUID, a_PlayerName }, function (a_Row) isWhitelisted = true end )) return isWhitelisted end --- API: Returns true if whitelist is enabled function IsWhitelistEnabled() return g_IsWhitelistEnabled end --- Returns a sorted array-table of all whitelisted players' names function ListWhitelistedPlayerNames() local res = {} WhitelistDB:ExecuteStatement( "SELECT Name FROM WhitelistNames ORDER BY Name", {}, function (a_Columns) table.insert(res, a_Columns["Name"]) end ) return res end --- Returns an array-table of all whitelisted players, sorted by player name -- Each item is a table with the Name, OnlineUUID, OfflineUUID, Date and WhitelistedBy values function ListWhitelistedPlayers() local res = {} WhitelistDB:ExecuteStatement( "SELECT * FROM WhitelistNames ORDER BY Name", {}, function (a_Columns) table.insert(res, a_Columns) end ) return res end --- API: Removes the specified player from the whitelist -- No action if the player is not whitelisted -- Returns true on success, false and optional error message on failure function RemovePlayerFromWhitelist(a_PlayerName) -- Check params: assert(type(a_PlayerName) == "string") -- Remove from the DB: return WhitelistDB:ExecuteStatement( "DELETE FROM WhitelistNames WHERE Name = ?", { a_PlayerName } ) end --- API: Disables the whitelist -- After this call, any player can connect to the server function WhitelistDisable() g_IsWhitelistEnabled = false SaveConfig() end --- API: Enables the whitelist -- After this call, only whitelisted players can connect to the server function WhitelistEnable() g_IsWhitelistEnabled = true SaveConfig() end --- Resolves the UUIDs for players that don't have their UUIDs in the DB -- This may happen when whitelisting a player who never connected to the server and thus is not yet cached in the UUID lookup local function ResolveUUIDs() -- If the server is offline, bail out: if not(cRoot:Get():GetServer():ShouldAuthenticate()) then return end -- Collect the names of players without their UUIDs: local NamesToResolve = {} WhitelistDB:ExecuteStatement( "SELECT Name From WhitelistNames WHERE UUID = ''", {}, function (a_Columns) table.insert(NamesToResolve, a_Columns["PlayerName"]) end ) if (#NamesToResolve == 0) then return; end -- Resolve the names: LOGINFO("Core: Resolving player UUIDs in the whitelist from Mojang servers. This may take a while...") local ResolvedNames = cMojangAPI:GetUUIDsFromPlayerNames(NamesToResolve) LOGINFO("Core: Resolving finished.") -- Update the names in the DB: for name, uuid in pairs(ResolvedNames) do WhitelistDB:ExecuteStatement( "UPDATE WhitelistNames SET UUID = ? WHERE PlayerName = ?", { uuid, name } ) end end --- If whitelist is disabled, sends a message to the specified player (or console if nil) local function NotifyWhitelistStatus(a_Player) -- Nothing to notify if the whitelist is enabled: if (g_IsWhitelistEnabled) then return end -- Send the notification msg to player / console: if (a_Player == nil) then LOG("Note: Whitelist is disabled. Use the \"whitelist on\" command to enable.") else a_Player:SendMessageInfo("Note: Whitelist is disabled. Use the \"/whitelist on\" command to enable.") end end --- If whitelist is empty, sends a notification to the specified player (or console if nil) -- Assumes that the whitelist is enabled local function NotifyWhitelistEmpty(a_Player) -- Check if whitelist is empty: local numWhitelisted local isSuccess, msg = WhitelistDB:ExecuteStatement( "SELECT COUNT(*) AS c FROM WhitelistNames", {}, function (a_Values) numWhitelisted = a_Values["c"] end ) if (not (isSuccess) or (type(numWhitelisted) ~= "number") or (numWhitelisted > 0)) then return end -- Send the notification msg to player / console: if (a_Player == nil) then LOGINFO("Note: Whitelist is empty. No player can connect to the server now. Use the \"whitelist add\" command to add players to whitelist.") else a_Player:SendMessageInfo("Note: Whitelist is empty. No player can connect to the server now. Use the \"/whitelist add\" command to add players to whitelist.") end end function HandleWhitelistAddCommand(a_Split, a_Player) -- Check params: if (a_Split[3] == nil) then SendMessage(a_Player, "Usage: " .. a_Split[1] .. " add ") return true end local playerName = a_Split[3] -- Add the player to the whitelist: local isSuccess, msg = AddPlayerToWhitelist(playerName, a_Player:GetName()); if not(isSuccess) then SendMessageFailure(a_Player, "Cannot whitelist " .. playerName .. ": " .. (msg or "")) return true end -- Notify success: LOGINFO(a_Player:GetName() .. " added " .. playerName .. " to whitelist.") SendMessageSuccess(a_Player, "Successfully added " .. playerName .. " to whitelist.") NotifyWhitelistStatus(a_Player) return true end function HandleWhitelistListCommand(a_Split, a_Player) if (IsWhitelistEnabled()) then a_Player:SendMessageSuccess("Whitelist is enabled") else a_Player:SendMessageSuccess("Whitelist is disabled") end local players = ListWhitelistedPlayerNames() table.sort(players) a_Player:SendMessageSuccess(table.concat(players, ", ")) return true end function HandleWhitelistOffCommand(a_Split, a_Player) g_IsWhitelistEnabled = true SaveConfig() a_Player:SendMessageSuccess("Whitelist is disabled.") return true end function HandleWhitelistOnCommand(a_Split, a_Player) g_IsWhitelistEnabled = true SaveConfig() a_Player:SendMessageSuccess("Whitelist is enabled.") NotifyWhitelistEmpty(a_Player) return true end function HandleWhitelistRemoveCommand(a_Split, a_Player) -- Check params: if ((a_Split[3] == nil) or (a_Split[4] ~= nil)) then SendMessage(a_Player, "Usage: " .. a_Split[1] .. " remove ") return true end local playerName = a_Split[3] -- Remove the player from the whitelist: local isSuccess, msg = RemovePlayerFromWhitelist(playerName) if not(isSuccess) then SendMessageFailure(a_Player, "Cannot unwhitelist " .. playerName .. ": " .. (msg or "")) return true end -- Notify success: LOGINFO(a_Player:GetName() .. " removed " .. playerName .. " from whitelist.") SendMessageSuccess(a_Player, "Removed " .. playerName .. " from whitelist.") NotifyWhitelistStatus(a_Player) return true end function HandleConsoleWhitelistAdd(a_Split) -- Check params: if (a_Split[3] == nil) then return true, "Usage: " .. a_Split[1] .. " add " end local playerName = table.concat(a_Split, " ", 3) -- Whitelist the player: local isSuccess, msg = AddPlayerToWhitelist(playerName, "") if not(isSuccess) then return true, "Cannot whitelist " .. playerName .. ": " .. (msg or "") end -- Notify success: NotifyWhitelistStatus() return true, "You added " .. playerName .. " to whitelist." end function HandleConsoleWhitelistList(a_Split) local status if (g_IsWhitelistEnabled) then status = "Whitelist is ENABLED.\n" else status = "Whitelist is DISABLED.\n" end local players = ListWhitelistedPlayerNames() if (players[1] == nil) then return true, status .. "The whitelist is empty." else return true, status .. "Whitelisted players: " .. table.concat(players, ", ") end end function HandleConsoleWhitelistOff(a_Split) WhitelistDisable() return true, "Whitelist is disabled" end function HandleConsoleWhitelistOn(a_Split) WhitelistEnable() NotifyWhitelistEmpty() return true, "Whitelist is enabled" end function HandleConsoleWhitelistRemove(a_Split) -- Check params: if ((a_Split[3] == nil) or (a_Split[4] ~= nil)) then return true, "Usage: " .. a_Split[1] .. " remove " end local playerName = a_Split[3] -- Unwhitelist the player: local isSuccess, msg = RemovePlayerFromWhitelist(playerName) if not(isSuccess) then return true, "Cannot unwhitelist " .. playerName .. ": " .. (msg or "") end -- Notify success: NotifyWhitelistStatus() return true, "You removed " .. playerName .. " from whitelist." end --- Opens the whitelist DB and checks that all the tables have the needed structure local function InitializeDB() -- Open the DB: local ErrMsg WhitelistDB, ErrMsg = NewSQLiteDB("whitelist.sqlite") if not(WhitelistDB) then LOGWARNING("Cannot open the whitelist database, whitelist not available. SQLite: " .. (ErrMsg or "")) error(ErrMsg) end -- Define the needed structure: local nameListColumns = { "Name", "UUID", "OfflineUUID", "Timestamp", "WhitelistedBy", } local configColumns = { "Name TEXT PRIMARY KEY", "Value" } -- Check structure: if ( not(WhitelistDB:CreateDBTable("WhitelistNames", nameListColumns)) or not(WhitelistDB:CreateDBTable("WhitelistConfig", configColumns)) ) then LOGWARNING("Cannot initialize the whitelist database, whitelist not available.") error("Whitelist DB failure") end -- Load the config: LoadConfig() end --- Callback for the HOOK_PLAYER_JOINED hook -- Kicks the player if they are whitelisted by UUID or Name -- Also sets the UUID for the player in the DB, if not present local function OnPlayerJoined(a_Player) local UUID = a_Player:GetUUID() local Name = a_Player:GetName() -- Update the UUID in the DB, if empty: assert(WhitelistDB:ExecuteStatement( "UPDATE WhitelistNames SET UUID = ? WHERE ((UUID = '') AND (Name = ?))", { UUID, Name } )) -- If whitelist is not enabled, bail out: if not(g_IsWhitelistEnabled) then return false end -- Kick if not whitelisted: local isWhitelisted = IsPlayerWhitelisted(UUID, Name) if not(isWhitelisted) then a_Player:GetClientHandle():Kick("You are not on the whitelist") return true end end --- Init function to be called upon plugin startup -- Opens the whitelist DB and refreshes the player names stored within function InitializeWhitelist() -- Initialize the Whitelist DB: InitializeDB() ResolveUUIDs() -- Make a note in the console if the whitelist is enabled and empty: if (g_IsWhitelistEnabled) then NotifyWhitelistEmpty() end -- Add a hook to filter out non-whitelisted players: cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_JOINED, OnPlayerJoined) end