Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg
+++ binaries/data/config/default.cfg
@@ -171,6 +171,10 @@
;;;;;;;;;;;;;;;;;;;;;;;;
+[acls]
+showoption = false ; Whether to show the checkbox during Host Game that would enable ACLs (Access Control Lists) in order to control permissions for network users. Possible values: true, false.
+server.enable = false ; Whether to enable ACLs. Possible values: true, false.
+
[adaptivefps]
session = 60 ; Throttle FPS in running games (prevents 100% CPU workload).
menu = 60 ; Throttle FPS in menus only.
Index: binaries/data/mods/public/gui/common/network.js
===================================================================
--- binaries/data/mods/public/gui/common/network.js
+++ binaries/data/mods/public/gui/common/network.js
@@ -79,6 +79,8 @@
case 13: return translate("Password is invalid.");
case 14: return translate("Could not find an unused port for the enet STUN client.");
case 15: return translate("Could not find the STUN endpoint.");
+ case 16: return translate("Invalid connection address.");
+ case 17: return translate("The hoster's Access Control List does not allow you to join this game. Ask the hoster what is necessary for you to change in order to be allowed to join the game.");
default:
warn("Unknown disconnect-reason ID received: " + id);
return sprintf(translate("\\[Invalid value %(id)s]"), { "id": id });
Index: binaries/data/mods/public/gui/gamesetup/Controllers/LobbyGameRegistration.js
===================================================================
--- binaries/data/mods/public/gui/gamesetup/Controllers/LobbyGameRegistration.js
+++ binaries/data/mods/public/gui/gamesetup/Controllers/LobbyGameRegistration.js
@@ -11,6 +11,7 @@
this.serverName = initData.serverName;
this.hasPassword = initData.hasPassword;
+ this.hasACLs = initData.hasACLs;
this.mods = JSON.stringify(Engine.GetEngineInfo().mods);
this.timer = undefined;
@@ -101,7 +102,8 @@
"maxnbp": g_GameSettings.playerCount.nbPlayers,
"players": clients.list,
"mods": this.mods,
- "hasPassword": this.hasPassword || ""
+ "hasPassword": this.hasPassword || "",
+ "hasACLs": this.hasACLs || ""
};
// Only send the stanza if one of these properties changed
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
@@ -18,6 +18,11 @@
*/
var g_ServerHasPassword = false;
+/**
+ * Identifier if server is using ACLs
+ */
+var g_ServerHasACLs = false;
+
var g_ServerId;
var g_IsRejoining = false;
@@ -52,6 +57,7 @@
let hasXmppClient = Engine.HasXmppClient();
Engine.GetGUIObjectByName("hostSTUNWrapper").hidden = !hasXmppClient;
Engine.GetGUIObjectByName("hostPasswordWrapper").hidden = !hasXmppClient;
+ Engine.GetGUIObjectByName("aclsWrapper").hidden = !(Engine.ConfigDB_GetValue("user", "acls.showoption") == "true");
if (hasXmppClient)
{
Engine.GetGUIObjectByName("hostPlayerName").caption = attribs.name;
@@ -59,6 +65,7 @@
sprintf(translate("%(name)s's game"), { "name": attribs.name });
Engine.GetGUIObjectByName("useSTUN").checked = Engine.ConfigDB_GetValue("user", "lobby.stun.enabled") == "true";
+ Engine.GetGUIObjectByName("useACLs").checked = Engine.ConfigDB_GetValue("user", "acls.server.enable") == "true";
}
switchSetupPage("pageHost");
@@ -165,6 +172,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("The hoster's Access Control List does not allow you to join this game. Ask the hoster what is necessary for you to change in order to be allowed to join the game.");
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);
@@ -285,7 +293,8 @@
}
Engine.SwitchGuiPage("page_gamesetup.xml", {
"serverName": g_ServerName,
- "hasPassword": g_ServerHasPassword
+ "hasPassword": g_ServerHasPassword,
+ "hasACLs": g_ServerHasACLs
});
return; // don't process any more messages - leave them for the game GUI loop
@@ -321,7 +330,7 @@
if (newPage == "pageJoin" || newPage == "pageHost")
{
let pageSize = multiplayerPages.size;
- let halfHeight = newPage == "pageJoin" ? 145 : Engine.HasXmppClient() ? 140 : 125;
+ let halfHeight = newPage == "pageJoin" ? 145 : 140;
pageSize.top = -halfHeight;
pageSize.bottom = halfHeight;
multiplayerPages.size = pageSize;
@@ -364,6 +373,8 @@
let useSTUN = Engine.HasXmppClient() && Engine.GetGUIObjectByName("useSTUN").checked;
+ let useACLs = Engine.GetGUIObjectByName("useACLs").checked;
+
try
{
Engine.StartNetworkHost(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), port, useSTUN, password, true);
@@ -381,6 +392,7 @@
g_ServerName = servername;
g_ServerHasPassword = !!password;
+ g_ServerHasACLs = !!useACLs;
if (Engine.HasXmppClient())
Engine.LobbySetPlayerPresence("playing");
Index: binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml
===================================================================
--- binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml
+++ binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml
@@ -8,7 +8,7 @@
-
+
onTick();
@@ -18,7 +18,7 @@
Multiplayer
-
+
Joining an existing game.
@@ -59,7 +59,7 @@
-
+
Set up your server to host.
@@ -91,11 +91,11 @@
-
+
Server Port:
-
+
Leave blank to use the default port.
this.caption = getValidPort(Engine.ConfigDB_GetValue("user", "multiplayerhosting.port"));
@@ -107,27 +107,36 @@
-
-
+
+
Server Password:
Leave blank to not require it.
-
+
this.caption = getDefaultPassword();
-
-
+
+
Engine.ConfigDB_CreateAndSaveValue("user", "lobby.stun.enabled", String(this.checked));
-
+
Use STUN to work around firewalls
+
+
+
+ Engine.ConfigDB_CreateAndSaveValue("user", "acls.server.enable", String(this.checked));
+
+
+ Use Access Control Lists (ACLs)
+
+
Index: binaries/data/mods/public/gui/lobby/LobbyPage/Game.js
===================================================================
--- binaries/data/mods/public/gui/lobby/LobbyPage/Game.js
+++ binaries/data/mods/public/gui/lobby/LobbyPage/Game.js
@@ -155,7 +155,12 @@
}
sortValues.private = newStanza.hasPassword;
- displayData.private = newStanza.hasPassword ? '[icon="icon_private"]' : '';
+ if (newStanza.hasPassword)
+ displayData.private = '[icon="icon_private"]';
+ else if (newStanza.hasACLs)
+ displayData.private = '[icon="icon_acl"]';
+ else
+ displayData.private = '';
this.stanza = newStanza;
this.sortValue = this.sortValues[sortKey];
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,12 +559,23 @@
},
{
"type": "dropdown",
+ "label": "Show option for Access Control Lists (ACLs)",
+ "tooltip": "Control whether the checkbox for ACLs is shown when hosting a game.",
+ "config": "acls.showoption",
+ "list": [
+ { "value": "false", "label": "Disabled" },
+ { "value": "true", "label": "Enabled" }
+ ]
+ },
+ {
+ "type": "dropdown",
"label": "Late observer joins",
"tooltip": "Allow everybody or buddies only to join the game as observer after it started.",
"config": "network.lateobservers",
"list": [
{ "value": "everyone", "label": "Everyone" },
{ "value": "buddies", "label": "Buddies" },
+ { "value": "acls", "label": "ACLs" },
{ "value": "disabled", "label": "Disabled" }
]
},
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
@@ -602,7 +602,7 @@
const char* stats[] = { "name", "hostUsername", "hostJID", "state", "hasPassword",
"nbp", "maxnbp", "players", "mapName", "niceMapName", "mapSize", "mapType",
- "victoryConditions", "startTime", "mods" };
+ "victoryConditions", "startTime", "mods", "hasACLs" };
for(const glooxwrapper::Tag* const& t : m_GameList)
{
@@ -962,6 +962,7 @@
response.addExtension(connectionData);
m_client->send(response);
+ LOGMESSAGE("XmppClient: Denied request for connection data (reason: not running a server) from %s", iq.from().username());
return true;
}
if (g_NetServer->IsBanned(iq.from().username()))
@@ -973,6 +974,20 @@
response.addExtension(connectionData);
m_client->send(response);
+ LOGMESSAGE("XmppClient: Denied request for connection data (reason: user banned) from %s", iq.from().username());
+ return true;
+ }
+ if (!g_NetServer->IsUsernameAllowedByACLs(iq.from().username(), "join"))
+ {
+ 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);
+ LOGMESSAGE("XmppClient: Denied request for connection data (reason: user not allowed by ACLs) from %s", iq.from().username());
+ LOGWARNING("%s was denied connection data due to ACLs", iq.from().username());
return true;
}
if (!g_NetServer->CheckPasswordAndIncrement(iq.from().username(), cd->m_Password.to_string(), cd->m_ClientSalt.to_string()))
@@ -984,6 +999,7 @@
response.addExtension(connectionData);
m_client->send(response);
+ LOGMESSAGE("XmppClient: Denied request for connection data (reason: user sent incorrect password) from %s", iq.from().username());
return true;
}
@@ -1013,6 +1029,8 @@
response.addExtension(connectionData);
+ LOGMESSAGE("XmppClient: Sent connection data including server IP address to %s", iq.from().username());
+
m_client->send(response);
}
Index: source/network/NetClient.cpp
===================================================================
--- source/network/NetClient.cpp
+++ source/network/NetClient.cpp
@@ -243,7 +243,7 @@
PushGuiMessage(
"type", "netstatus",
"status", "disconnected",
- "reason", static_cast(NDR_SERVER_REFUSED));
+ "reason", static_cast(NDR_INVALID_ADDRESS));
return false;
}
Index: source/network/NetHost.h
===================================================================
--- source/network/NetHost.h
+++ source/network/NetHost.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2019 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
@@ -74,9 +74,11 @@
NDR_GUID_FAILED,
NDR_INCORRECT_READY_TURN_COMMANDS,
NDR_INCORRECT_READY_TURN_SIMULATED,
- NDR_SERVER_REFUSED,
+ NDR_INCORRECT_PASSWORD,
NDR_STUN_PORT_FAILED,
- NDR_STUN_ENDPOINT_FAILED
+ NDR_STUN_ENDPOINT_FAILED,
+ NDR_INVALID_ADDRESS,
+ NDR_JOIN_PERMISSION_DENIED
};
class CNetHost
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,13 @@
*/
bool IsBanned(const std::string& username) const;
+ /**
+ * Check if the user is allowed the permission type based on server
+ * policy.
+ * (Same parameters as CNetServerWorker::IsUsernameAllowedByACLs)
+ **/
+ bool IsUsernameAllowedByACLs(const std::string& usernameWithoutRating, const CStr& permissionName);
+
void SetPassword(const CStr& password);
void SetControllerSecret(const std::string& secret);
@@ -227,10 +234,58 @@
void KickPlayer(const CStrW& playerName, const bool ban);
/**
+ * Remove the rating and convert to lowercase a username
+ * @param username string denoting the name of the user
+ * @param lobbyAuth true if the multiplayer lobby is in use
+ * @return string representing the simplified username
+ */
+ static CStrW SimpleUsername(const CStrW& username, const bool lobbyAuth);
+
+ /**
* Send a message to all clients who match one of the given states.
*/
bool Broadcast(const CNetMessage* message, const std::vector& targetStates);
+ /**
+ * Check if the user or GUID is allowed the permission type based on
+ * server policy.
+ * @param usernameWithoutRating the lowercase name of the user, without
+ * rating, to check in ACL rules
+ * @param guid the GUID of the user to check for controller GUID
+ * @param permissionName the name of the permission to check whether
+ * the user is granted such permission by the ACL
+ * rules. Examples: chat, join,
+ * mapflare, observerlate
+ **/
+ bool IsUserAllowedByACLs(const CStrW& username, const CStr& guid, const CStr& permissionName);
+
+
+ /**
+ * Check if the user is allowed the permission type based on server
+ * policy.
+ * @param usernameWithoutRating the lowercase name of the user, without
+ * rating, to check in ACL rules
+ * @param permissionName the name of the permission to check whether
+ * the user is granted such permission by the ACL
+ * rules.
+ */
+ static bool IsUsernameAllowedByACLs(const CStrW& usernameWithoutRating, const CStr& permissionName);
+
+ /**
+ * Check if the user is present in the server's list of buddies.
+ * @param usernameWithoutRating lowercase user name without rating
+ **/
+ static bool IsUserInBuddyList(const CStrW& usernameWithoutRating);
+
+ /**
+ * Check if the user is present in the list of members of the ACL group
+ * @param usernameWithoutRating lowercase user name without rating
+ * @param userListCfgName the name of the ConfigDB entry containing
+ * the list of users in the group. Example:
+ * "acls.groups.group1.members"
+ **/
+ static bool IsUserInCfgUserList(const CStrW& usernameWithoutRating, const CStr& userListCfgName);
+
private:
friend class CNetServer;
friend class CNetFileReceiveTask_ServerRejoin;
Index: source/network/NetServer.cpp
===================================================================
--- source/network/NetServer.cpp
+++ source/network/NetServer.cpp
@@ -857,6 +857,11 @@
Broadcast(&kickedMessage, { NSS_PREGAME, NSS_JOIN_SYNCING, NSS_INGAME });
}
+CStrW CNetServerWorker::SimpleUsername(const CStrW& username, const bool lobbyAuth)
+{
+ return lobbyAuth ? CStrW(username.substr(0, username.find(L" ("))).LowerCase() : username.LowerCase();
+}
+
void CNetServerWorker::AssignPlayer(int playerID, const CStr& guid)
{
// Remove anyone who's already assigned to this player
@@ -1003,7 +1008,20 @@
return true;
}
- // Check the password before anything else.
+ // Check the server join policy before anything else. Allow users with
+ // controller secret to join regardless of whether they are allowed by
+ // the ACLs or not
+ if (message->m_ControllerSecret != server.m_ControllerSecret &&
+ !server.IsUsernameAllowedByACLs(message->m_Name, "join"))
+ {
+ // LOGWARNING("Net server: user %s tried joining via direct connection but was not allowed due to ACL rules",
+ LOGWARNING("%s was denied direct join due to ACLs",
+ utf8_from_wstring(usernameWithoutRating.LowerCase()));
+ session->Disconnect(NDR_JOIN_PERMISSION_DENIED);
+ return true;
+ }
+
+ // Check the password
// NB: m_Name must match the client's salt, @see CNetClient::SetGamePassword
if (!server.CheckPassword(message->m_Password, message->m_Name.ToUTF8()))
{
@@ -1012,7 +1030,7 @@
// (or TODO a dedicated server and we do want to log anyways)
LOGERROR("Net server: user %s tried joining with the wrong password",
session->GetUserName().ToUTF8());
- session->Disconnect(NDR_SERVER_REFUSED);
+ session->Disconnect(NDR_INCORRECT_PASSWORD);
return true;
}
@@ -1090,20 +1108,9 @@
isRejoining = true;
}
else if (observerLateJoin == "buddies")
- {
- 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 == usernameWithoutRating)
- {
- isRejoining = true;
- break;
- }
- }
- }
+ isRejoining = IsUserInBuddyList(usernameWithoutRating);
+ else if (observerLateJoin == "acls")
+ isRejoining = server.IsUsernameAllowedByACLs(usernameWithoutRating, "observerlate");
}
if (!isRejoining)
@@ -1195,6 +1202,30 @@
if (!cheatsEnabled && (it == server.m_PlayerAssignments.end() || it->second.m_PlayerID != message->m_Player))
return true;
+ // Allow map flares only from users with permission from server ACLs
+ if (!message->m_Data.isNull() &&
+ JS_TypeOfValue(rq.cx, message->m_Data) == JSTYPE_OBJECT &&
+ Script::HasProperty(rq, message->m_Data, "type"))
+ {
+ CStr simulationMessageDataType;
+ if (Script::GetProperty(rq, message->m_Data, "type", simulationMessageDataType))
+ {
+ if (simulationMessageDataType == "map-flare")
+ {
+ CStrW usernameToFind = SimpleUsername(session->GetUserName(), server.m_LobbyAuth);
+ if (!server.IsUserAllowedByACLs(usernameToFind, session->GetGUID(), "mapflare"))
+ {
+ LOGMESSAGE("CNetServerWorker::OnSimulationMessage: user %s denied map flare due to server ACLs", utf8_from_wstring(usernameToFind));
+ CChatMessage message;
+ message.m_GUID = session->GetGUID();
+ message.m_Message = wstring_from_utf8("(Notice: Your map flare was not broadcast due to server ACLs.)");
+ session->SendMessage(&message);
+ return true;
+ }
+ }
+ }
+ }
+
// Send it back to all clients that have finished
// the loading screen (and the synchronization when rejoining)
server.Broadcast(message, { NSS_INGAME });
@@ -1242,6 +1273,18 @@
CNetServerSession* session = (CNetServerSession*)context;
CNetServerWorker& server = session->GetServer();
+ CStrW username = SimpleUsername(session->GetUserName(), server.m_LobbyAuth);
+
+ if (!server.IsUserAllowedByACLs(username, session->GetGUID(), "chat"))
+ {
+ CChatMessage message;
+ message.m_GUID = session->GetGUID();
+ message.m_Message = wstring_from_utf8("(Notice: Your message was not broadcast due to server ACLs.)");
+ session->SendMessage(&message);
+ return true;
+ }
+
+ // User is allowed by ACLs. Broadcast the message to the clients
CChatMessage* message = (CChatMessage*)event->GetParamRef();
message->m_GUID = session->GetGUID();
@@ -1617,8 +1660,121 @@
StunClient::SendHolePunchingMessages(*m_Host, ipStr, port);
}
+bool CNetServerWorker::IsUserAllowedByACLs(const CStrW& username, const CStr& guid, const CStr& permissionName)
+{
+ if (guid == m_ControllerGUID)
+ return true;
+ return IsUsernameAllowedByACLs(username, permissionName);
+}
+
+bool CNetServerWorker::IsUsernameAllowedByACLs(const CStrW& username, const CStr& permissionName)
+{
+ // Check whether the checkbox is shown that enables ACLs
+ CStr showACLs;
+ CFG_GET_VAL("acls.showoption", showACLs);
+
+ if (!(showACLs == "true"))
+ // In this case all users are allowed.
+ return true;
+ // Check whether the ACLs are enabled
+ CStr enableACLs;
+ CFG_GET_VAL("acls.server.enable", enableACLs);
+ if (!(enableACLs == "true"))
+ // In this case all users are allowed.
+ return true;
+
+ CStrW usernameWithoutRating(username.LowerCase().substr(0, username.find(L" (")));
+
+ // LOGMESSAGE("IsUsernameAllowedByACLs: Checking permission %s for user %s", permissionName, utf8_from_wstring(username));
+
+ // Start with an implicit allow all unprivileged permissions to all users
+ bool userAllowed{true};
+
+ for (uint32_t aclNum = 1; aclNum < 255; ++aclNum)
+ {
+ CStr aclPermissionGroup;
+ CStr aclPermissionGroupCfgName("acls.server.acl" + CStr::FromUInt(aclNum) + ".group");
+
+ CFG_GET_VAL(aclPermissionGroupCfgName, aclPermissionGroup);
+ // If the config entry is not found then stop looking for more
+ // TODO: Document this so that users know that rules must be numbered
+ // consecutively in order to be processed
+ if (aclPermissionGroup.length() == 0)
+ break;
+
+ CStr aclPermissionRuleCfgName("acls.server.acl" + CStr::FromUInt(aclNum) + ".rule");
+ CStr aclPermissionRule;
+ CFG_GET_VAL(aclPermissionRuleCfgName, aclPermissionRule);
+ // LOGMESSAGE("IsUsernameAllowedByACLs: acl %d from cfg entry %s is %s", aclNum, aclPermissionRuleCfgName, aclPermissionRule);
+
+ if (userAllowed && aclPermissionRule.Left(5) == "deny " &&
+ (aclPermissionRule.FindInsensitive("allunprivperms") != -1 ||
+ aclPermissionRule.FindInsensitive(permissionName) != -1))
+ {
+ // LOGMESSAGE("IsUsernameAllowedByACLs: acl is deny");
+ // Process the denied permissions.
+ if (aclPermissionGroup == "@all")
+ userAllowed = false;
+ else if (aclPermissionGroup == "@buddies" && IsUserInBuddyList(usernameWithoutRating))
+ userAllowed = false;
+ else
+ {
+ // Look for the user in the named ACL group member list, e.g. "group1"
+ CStr aclGroupMembers;
+ CStr aclGroupCfgName("acls.groups." + aclPermissionGroup + ".members");
+ userAllowed = !IsUserInCfgUserList(usernameWithoutRating, aclGroupCfgName);
+ }
+ }
+ else if (!userAllowed && aclPermissionRule.Left(6) == "allow " &&
+ (aclPermissionRule.FindInsensitive("allunprivperms") != -1 ||
+ aclPermissionRule.FindInsensitive(permissionName) != -1))
+ {
+ // LOGMESSAGE("IsUsernameAllowedByACLs: acl is allow");
+ // Process the allowed permissions.
+ if (aclPermissionGroup == "@all")
+ userAllowed = true;
+ else if (aclPermissionGroup == "@buddies")
+ userAllowed = IsUserInBuddyList(usernameWithoutRating);
+ else
+ {
+ // Look for the user in the named ACL group member list, e.g. "group2"
+ CStr groupAllowMembers;
+ CStr groupAllowCfgName("acls.groups." + aclPermissionGroup + ".members");
+ userAllowed = IsUserInCfgUserList(usernameWithoutRating, groupAllowCfgName);
+ }
+ }
+ }
+
+ // LOGMESSAGE("IsUsernameAllowedByACLs: acl result: userAllowed == %d", userAllowed);
+ return userAllowed;
+}
+
+bool CNetServerWorker::IsUserInBuddyList(const CStrW& usernameWithoutRating)
+{
+ return IsUserInCfgUserList(usernameWithoutRating, "lobby.buddies");
+}
+
+bool CNetServerWorker::IsUserInCfgUserList(const CStrW& usernameWithoutRating, const CStr& userListCfgName)
+{
+ CStr userList;
+ CFG_GET_VAL(userListCfgName, userList);
+ // LOGMESSAGE("IsUserInCfgUserList: acl group %s memberlist: %s", userListCfgName.c_str(), userList.c_str());
+ std::wstringstream groupStream(wstring_from_utf8(userList));
+
+ CStrW username;
+ while (std::getline(groupStream, username, L','))
+ {
+ if (username.LowerCase() == usernameWithoutRating)
+ {
+ // LOGMESSAGE("IsUserInCfgUserList: acl group %s, username %s matches usernameWithoutRating %s, returning true", userListCfgName.c_str(), utf8_from_wstring(username), utf8_from_wstring(usernameWithoutRating).c_str());
+ return true;
+ }
+ }
+ // LOGMESSAGE("IsUserInCfgUserList: acl group %s, usernameWithoutRating %s not found, returning false", userListCfgName.c_str(), utf8_from_wstring(usernameWithoutRating).c_str());
+ return false;
+}
CNetServer::CNetServer(bool useLobbyAuth) :
m_Worker(new CNetServerWorker(useLobbyAuth)),
@@ -1702,6 +1858,12 @@
return it != m_FailedAttempts.end() && it->second >= FAILED_PASSWORD_TRIES_BEFORE_BAN;
}
+bool CNetServer::IsUsernameAllowedByACLs(const std::string& username, const CStr& permissionName)
+{
+ CStrW theUsername(wstring_from_utf8(username));
+ return m_Worker->IsUsernameAllowedByACLs(theUsername, permissionName);
+}
+
void CNetServer::SetPassword(const CStr& password)
{
m_Password = password;