1 /*
2  *  The ManaPlus Client
3  *  Copyright (C) 2016-2019  The ManaPlus Developers
4  *  Copyright (C) 2019-2021  Andrei Karas
5  *
6  *  This file is part of The ManaPlus Client.
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include "resources/db/groupdb.h"
23 
24 #include "configuration.h"
25 
26 #include "being/localplayer.h"
27 
28 #include "utils/checkutils.h"
29 
30 #ifdef TMWA_SUPPORT
31 #include "net/net.h"
32 #endif  // TMWA_SUPPORT
33 
34 #include "resources/beingcommon.h"
35 #include "resources/groupinfo.h"
36 
37 #include "debug.h"
38 
39 namespace
40 {
41     GroupDb::GroupInfos mGroups;
42     const GroupInfo mEmptyGroup;
43     bool mLoaded = false;
44 }  // namespace
45 
load()46 void GroupDb::load()
47 {
48     if (mLoaded)
49         unload();
50 
51     logger->log1("Initializing group database...");
52 
53     loadXmlFile(paths.getStringValue("groupsFile"), SkipError_false);
54     loadXmlFile(paths.getStringValue("groupsPatchFile"), SkipError_true);
55     loadXmlDir("groupsPatchDir", loadXmlFile)
56     mLoaded = true;
57 }
58 
59 #define servercommandFirst(name) \
60     if (str == #name) \
61         return ServerCommandType::name; \
62     else
63 #define servercommand(name) \
64     if (str == #name) \
65         return ServerCommandType::name; \
66     else
67 #define servercommand2(name1, name2) \
68     if (str == #name2) \
69         return ServerCommandType::name1; \
70     else
71 
parseCommand(const std::string & str,const int id)72 static ServerCommandTypeT parseCommand(const std::string &str,
73                                        const int id)
74 {
75 #include "resources/servercommands.inc"
76     {
77         reportAlways("GroupsDb: unknown command name: '%s' in group id '%d'.",
78             str.c_str(),
79             id)
80     }
81 
82     return ServerCommandType::Max;
83 }
84 
85 SERVERCOMMANDS_VOID
86 #undef servercommandFirst
87 #undef servercommand
88 #undef servercommand2
89 
90 #define serverpermissionFirst(name) \
91     if (str == #name) \
92         return ServerPermissionType::name; \
93     else
94 #define serverpermission(name) \
95     if (str == #name) \
96         return ServerPermissionType::name; \
97     else
98 
parsePermission(const std::string & str,const int id)99 static ServerPermissionTypeT parsePermission(const std::string &str,
100                                              const int id)
101 {
102 #include "resources/serverpermissions.inc"
103     {
104         reportAlways("GroupsDb: unknown permission name: "
105             "'%s' in group id '%d'.",
106             str.c_str(),
107             id)
108     }
109 
110     return ServerPermissionType::Max;
111 }
112 
113 SERVERPERMISSION_VOID
114 #undef serverpermissionFirst
115 #undef serverpermission
116 
parseUseFlag(const std::string & str,const int id)117 static ServerCommandEnable::Type parseUseFlag(const std::string &str,
118                                               const int id)
119 {
120     if (str == "self")
121     {
122         return ServerCommandEnable::Self;
123     }
124     else if (str == "other")
125     {
126         return ServerCommandEnable::Other;
127     }
128     else if (str == "both")
129     {
130         return ServerCommandEnable::Both;
131     }
132     else if (str == "false")
133     {
134         return ServerCommandEnable::No;
135     }
136     else
137     {
138         reportAlways("GroupsDb: unknown use flag: '%s' in group id '%d'."
139             "Possible values 'self', 'other', 'both'.",
140             str.c_str(),
141             id)
142         return ServerCommandEnable::No;
143     }
144 }
145 
146 static void loadCommands(XmlNodePtr rootNode,
147                          const int id,
148                          GroupInfo *groupInfo) A_NONNULL(3);
loadCommands(XmlNodePtr rootNode,const int id,GroupInfo * groupInfo)149 static void loadCommands(XmlNodePtr rootNode,
150                          const int id,
151                          GroupInfo *groupInfo)
152 {
153     for_each_xml_child_node(node, rootNode)
154     {
155         if (xmlNameEqual(node, "command"))
156         {
157             const std::string nameStr = XML::getProperty(node,
158                 "name",
159                 "");
160             const std::string useStr = XML::getProperty(node,
161                 "use",
162                 "");
163             ServerCommandTypeT name = parseCommand(nameStr, id);
164             if (name == ServerCommandType::Max)
165                 continue;
166             ServerCommandEnable::Type useFlag = parseUseFlag(useStr, id);
167             if (useFlag == ServerCommandEnable::No)
168                 continue;
169             groupInfo->mCommands[CAST_SIZE(name)] = useFlag;
170         }
171     }
172 }
173 
174 static void loadPermissions(XmlNodePtr rootNode,
175                             const int id,
176                             GroupInfo *groupInfo) A_NONNULL(3);
loadPermissions(XmlNodePtr rootNode,const int id,GroupInfo * groupInfo)177 static void loadPermissions(XmlNodePtr rootNode,
178                             const int id,
179                             GroupInfo *groupInfo)
180 {
181     for_each_xml_child_node(node, rootNode)
182     {
183         if (xmlNameEqual(node, "permission"))
184         {
185             const std::string nameStr = XML::getProperty(node,
186                 "name",
187                 "");
188             ServerPermissionTypeT perm = parsePermission(nameStr, id);
189             if (perm == ServerPermissionType::Max)
190                 continue;
191             if (!XML::getBoolProperty(node,
192                 "enable",
193                 true))
194             {
195                 continue;
196             }
197             groupInfo->mPermissions[CAST_SIZE(perm)] = Enable_true;
198         }
199     }
200 }
201 
202 static void loadSubNodes(XmlNodePtr groupNode,
203                          const int id,
204                          GroupInfo *groupInfo) A_NONNULL(3);
loadSubNodes(XmlNodePtr groupNode,const int id,GroupInfo * groupInfo)205 static void loadSubNodes(XmlNodePtr groupNode,
206                          const int id,
207                          GroupInfo *groupInfo)
208 {
209     for_each_xml_child_node(node, groupNode)
210     {
211         if (xmlNameEqual(node, "commands"))
212             loadCommands(node, id, groupInfo);
213         if (xmlNameEqual(node, "permissions"))
214             loadPermissions(node, id, groupInfo);
215     }
216 }
217 
218 static void parseInherit(XmlNodePtr groupNode,
219                          const int id,
220                          GroupInfo *groupInfo) A_NONNULL(3);
parseInherit(XmlNodePtr groupNode,const int id,GroupInfo * groupInfo)221 static void parseInherit(XmlNodePtr groupNode,
222                          const int id,
223                          GroupInfo *groupInfo)
224 {
225     const std::string inherit = XML::langProperty(groupNode,
226         "inherit",
227         "");
228     STD_VECTOR<int> parts;
229     splitToIntVector(parts, inherit, ',');
230     FOR_EACH (STD_VECTOR<int>::const_iterator, it, parts)
231     {
232         const int id2 = *it;
233         GroupDb::GroupInfos::const_iterator it2 = mGroups.find(id2);
234         if (it2 == mGroups.end())
235         {
236             reportAlways("Unknown inherit group id '%d' in group '%d'",
237                 id2,
238                 id)
239             continue;
240         }
241         GroupInfo *const groupInfo2 = (*it2).second;
242         for (size_t f = 0; f < CAST_SIZE(ServerCommandType::Max); f ++)
243         {
244             ServerCommandEnable::Type enable = groupInfo2->mCommands[f];
245             if (enable != ServerCommandEnable::No)
246                 groupInfo->mCommands[f] = enable;
247         }
248         for (size_t f = 0; f < CAST_SIZE(ServerPermissionType::Max); f ++)
249         {
250             if (groupInfo2->mPermissions[f] == Enable_true)
251                 groupInfo->mPermissions[f] = Enable_true;
252         }
253     }
254 }
255 
loadXmlFile(const std::string & fileName,const SkipError skipError)256 void GroupDb::loadXmlFile(const std::string &fileName,
257                           const SkipError skipError)
258 {
259     XML::Document doc(fileName,
260         UseVirtFs_true,
261         skipError);
262     XmlNodeConstPtrConst rootNode = doc.rootNode();
263 
264     if (rootNode == nullptr ||
265         !xmlNameEqual(rootNode, "groups"))
266     {
267         if (skipError == SkipError_true)
268         {
269             logger->log("GroupsDb: Error while loading %s!",
270                 fileName.c_str());
271         }
272         else
273         {
274             reportAlways("GroupsDb: Error while loading %s!",
275                 fileName.c_str())
276         }
277         return;
278     }
279 
280     for_each_xml_child_node(node, rootNode)
281     {
282         if (xmlNameEqual(node, "include"))
283         {
284             const std::string name = XML::getProperty(node, "name", "");
285             if (!name.empty())
286                 loadXmlFile(name, skipError);
287             continue;
288         }
289         if (xmlNameEqual(node, "group"))
290         {
291             const int id = XML::getProperty(node,
292                 "id",
293                 -1);
294             if (id < 0)
295             {
296                 reportAlways("Empty id field in GroupDb")
297                 continue;
298             }
299             GroupInfosIter it = mGroups.find(id);
300             GroupInfo *group = nullptr;
301             if (it != mGroups.end())
302             {
303                 reportAlways("GroupDb: group with id %d already added",
304                     id)
305                 group = (*it).second;
306             }
307             else
308             {
309                 group = new GroupInfo;
310                 mGroups[id] = group;
311             }
312             group->name = XML::langProperty(node,
313                 "name",
314                 "");
315             group->longName = XML::langProperty(node,
316                 "longName",
317                 "");
318             group->badge = XML::langProperty(node,
319                 "badge",
320                 paths.getStringValue("gmbadge"));
321             group->showBadge = XML::getBoolProperty(node,
322                 "showBadge",
323                 false);
324             group->highlightName = XML::getBoolProperty(node,
325                 "highlightName",
326                 false);
327             loadSubNodes(node, id, group);
328             parseInherit(node, id, group);
329         }
330     }
331 }
332 
unload()333 void GroupDb::unload()
334 {
335     logger->log1("Unloading group database...");
336     FOR_EACH (GroupInfosIter, it, mGroups)
337     {
338         delete (*it).second;
339     }
340     mGroups.clear();
341     mLoaded = false;
342 }
343 
getName(const int id)344 const std::string &GroupDb::getName(const int id)
345 {
346     GroupInfos::const_iterator it = mGroups.find(id);
347     if (it == mGroups.end())
348     {
349         reportAlways("Unknown group id requested: %d", id)
350         return mEmptyGroup.name;
351     }
352     return (*it).second->name;
353 }
354 
getLongName(const int id)355 const std::string &GroupDb::getLongName(const int id)
356 {
357     GroupInfos::const_iterator it = mGroups.find(id);
358     if (it == mGroups.end())
359     {
360         reportAlways("Unknown group id requested: %d", id)
361         return mEmptyGroup.longName;
362     }
363     return (*it).second->longName;
364 }
365 
getShowBadge(const int id)366 bool GroupDb::getShowBadge(const int id)
367 {
368     GroupInfos::const_iterator it = mGroups.find(id);
369     if (it == mGroups.end())
370     {
371         reportAlways("Unknown group id requested: %d", id)
372         return mEmptyGroup.showBadge;
373     }
374     return (*it).second->showBadge;
375 }
376 
getHighlightName(const int id)377 bool GroupDb::getHighlightName(const int id)
378 {
379     GroupInfos::const_iterator it = mGroups.find(id);
380     if (it == mGroups.end())
381     {
382         reportAlways("Unknown group id requested: %d", id)
383         return mEmptyGroup.highlightName;
384     }
385     return (*it).second->highlightName;
386 }
387 
getBadge(const int id)388 const std::string &GroupDb::getBadge(const int id)
389 {
390     GroupInfos::const_iterator it = mGroups.find(id);
391     if (it == mGroups.end())
392     {
393         reportAlways("Unknown group id requested: %d", id)
394         return mEmptyGroup.badge;
395     }
396     return (*it).second->badge;
397 }
398 
getGroup(const int id)399 const GroupInfo *GroupDb::getGroup(const int id)
400 {
401     GroupInfos::const_iterator it = mGroups.find(id);
402     if (it == mGroups.end())
403     {
404         reportAlways("Unknown group id requested: %d", id)
405         return &mEmptyGroup;
406     }
407     return (*it).second;
408 }
409 
isAllowCommand(const ServerCommandTypeT command)410 bool GroupDb::isAllowCommand(const ServerCommandTypeT command)
411 {
412     if (localPlayer == nullptr)
413         return false;
414     const int groupId = localPlayer->getGroupId();
415     const GroupInfo *const group = GroupDb::getGroup(groupId);
416 
417 #ifdef TMWA_SUPPORT
418     // allow any commands for legacy if group > 0
419     if (Net::getNetworkType() == ServerType::TMWATHENA &&
420         localPlayer->isGM())
421     {
422         return true;
423     }
424 #endif
425     if (group->mPermissions[CAST_SIZE(ServerPermissionType::all_commands)] ==
426         Enable_true)
427     {
428         return true;
429     }
430     const ServerCommandEnable::Type enabled =
431         group->mCommands[CAST_SIZE(command)];
432     return (enabled & ServerCommandEnable::Self) != 0;
433 }
434 
435 #ifdef UNITTESTS
getGroups()436 GroupDb::GroupInfos &GroupDb::getGroups()
437 {
438     return mGroups;
439 }
440 #endif  // UNITTESTS
441