Index: binaries/data/config/default.cfg =================================================================== --- binaries/data/config/default.cfg +++ binaries/data/config/default.cfg @@ -525,6 +525,7 @@ name_id = "0ad" [network] +allowlist = everyone ; Allow certain players to join the game. Possible values: everyone, buddies. duplicateplayernames = false ; Rename joining player to "User (2)" if "User" is already connected, otherwise prohibit join. lateobservers = everyone ; Allow observers to join the game after it started. Possible values: everyone, buddies, disabled. observerlimit = 8 ; Prevent further observer joins in running games if this limit is reached Index: binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js =================================================================== --- binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js +++ binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js @@ -165,6 +165,7 @@ case "not_server": return translate("Server is not running."); case "invalid_password": return translate("Password is invalid."); case "banned": return translate("You have been banned."); + case "not_allowed": return translate("You are not allowed to join this game. Ask the hoster how you can be added to the allow-list."); case "local_ip_failed": return translate("Failed to get local IP of the server (it was assumed to be on the same network)."); default: warn("Unknown connection failure reason: " + reason); Index: binaries/data/mods/public/gui/options/options.json =================================================================== --- binaries/data/mods/public/gui/options/options.json +++ binaries/data/mods/public/gui/options/options.json @@ -559,6 +559,16 @@ }, { "type": "dropdown", + "label": "Player joins (allow-list)", + "tooltip": "Allow everyone or buddies only to join the game.", + "config": "network.allowlist", + "list": [ + { "value": "everyone", "label": "Everyone" }, + { "value": "buddies", "label": "Buddies" } + ] + }, + { + "type": "dropdown", "label": "Late observer joins", "tooltip": "Allow everybody or buddies only to join the game as observer after it started.", "config": "network.lateobservers", Index: source/lobby/XmppClient.cpp =================================================================== --- source/lobby/XmppClient.cpp +++ source/lobby/XmppClient.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -975,6 +975,17 @@ m_client->send(response); return true; } + if (!g_NetServer->IsUserInAllowList(iq.from().username())) + { + glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id()); + ConnectionData* connectionData = new ConnectionData(); + connectionData->m_Error = "not_allowed"; + + response.addExtension(connectionData); + + m_client->send(response); + return true; + } if (!g_NetServer->CheckPasswordAndIncrement(iq.from().username(), cd->m_Password.to_string(), cd->m_ClientSalt.to_string())) { glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id()); @@ -1013,6 +1024,8 @@ response.addExtension(connectionData); + LOGMESSAGE("XmppClient: Sent connection data including public IP address to %s", iq.from().username()); + m_client->send(response); } Index: source/network/NetServer.h =================================================================== --- source/network/NetServer.h +++ source/network/NetServer.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -183,6 +183,14 @@ */ bool IsBanned(const std::string& username) const; + /** + * Check if user is allowed by the overall server policy (e.g. invite only). + * This function is used by XmppClient to check whether the username + * is allowed. If not then the server's public IP address is not sent to + * the lobby user + */ + static bool IsUserInAllowList(const std::string& username); + void SetPassword(const CStr& password); void SetControllerSecret(const std::string& secret); Index: source/network/NetServer.cpp =================================================================== --- source/network/NetServer.cpp +++ source/network/NetServer.cpp @@ -195,6 +195,35 @@ delete m_ServerTurnManager; } +bool CNetServer::IsUserInAllowList(const std::string& username) +{ + CStrW theUsername(wstring_from_utf8(username)); + CStrW usernameWithoutRating(theUsername.LowerCase().substr(0, theUsername.find(L" ("))); + // Check whether the allow-list is set to buddies only + CStr allowListSetting; + CFG_GET_VAL("network.allowlist", allowListSetting); + + if (allowListSetting != "buddies") + // All users are allowed. + return true; + + CStr buddies; + CFG_GET_VAL("lobby.buddies", buddies); + std::wstringstream buddiesStream(wstring_from_utf8(buddies)); + CStrW buddy; + while (std::getline(buddiesStream, buddy, L',')) + { + if (buddy.LowerCase() == usernameWithoutRating) + { + // User found in allow list. + return true; + } + } + + // User not found in allow list. + return false; +} + void CNetServerWorker::SetPassword(const CStr& hashedPassword) { m_Password = hashedPassword;