1#include "authmanager.h"
2
3#include <base/dbg.h>
4#include <base/hash_ctxt.h>
5#include <base/mem.h>
6#include <base/secure.h>
7#include <base/str.h>
8
9#include <engine/shared/config.h>
10
11#include <generated/protocol.h>
12
13#define ADMIN_IDENT "default_admin"
14#define MOD_IDENT "default_mod"
15#define HELPER_IDENT "default_helper"
16
17const char *CAuthManager::AuthLevelToRoleName(int AuthLevel)
18{
19 switch(AuthLevel)
20 {
21 case AUTHED_ADMIN:
22 return RoleName::ADMIN;
23 case AUTHED_MOD:
24 return RoleName::MODERATOR;
25 case AUTHED_HELPER:
26 return RoleName::HELPER;
27 }
28
29 dbg_assert_failed("Invalid auth level: %d", AuthLevel);
30}
31
32static int RoleNameToAuthLevel(const char *pRoleName)
33{
34 if(!str_comp(a: pRoleName, b: RoleName::ADMIN))
35 return AUTHED_ADMIN;
36 if(!str_comp(a: pRoleName, b: RoleName::MODERATOR))
37 return AUTHED_MOD;
38 if(!str_comp(a: pRoleName, b: RoleName::HELPER))
39 return AUTHED_HELPER;
40
41 dbg_assert_failed("Invalid role name: %s", pRoleName);
42}
43
44static MD5_DIGEST HashPassword(const char *pPassword, const unsigned char aSalt[SALT_BYTES])
45{
46 // Hash the password and the salt
47 MD5_CTX Md5;
48 md5_init(ctxt: &Md5);
49 md5_update(ctxt: &Md5, data: (unsigned char *)pPassword, data_len: str_length(str: pPassword));
50 md5_update(ctxt: &Md5, data: aSalt, SALT_BYTES);
51 return md5_finish(ctxt: &Md5);
52}
53
54CAuthManager::CAuthManager()
55{
56 m_aDefault[0] = -1;
57 m_aDefault[1] = -1;
58 m_aDefault[2] = -1;
59 m_Generated = false;
60
61 AddRole(pName: RoleName::ADMIN, Rank: RoleRank::ADMIN);
62 AddRole(pName: RoleName::MODERATOR, Rank: RoleRank::MODERATOR);
63 AddRole(pName: RoleName::HELPER, Rank: RoleRank::HELPER);
64}
65
66void CAuthManager::Init()
67{
68 size_t NumDefaultKeys = 0;
69 if(g_Config.m_SvRconPassword[0])
70 NumDefaultKeys++;
71 if(g_Config.m_SvRconModPassword[0])
72 NumDefaultKeys++;
73 if(g_Config.m_SvRconHelperPassword[0])
74 NumDefaultKeys++;
75
76 auto It = std::find_if(first: m_vKeys.begin(), last: m_vKeys.end(), pred: [](CKey Key) { return str_comp(a: Key.m_aIdent, b: DEFAULT_SAVED_RCON_USER) == 0; });
77 if(It != m_vKeys.end())
78 NumDefaultKeys++;
79
80 if(m_vKeys.size() == NumDefaultKeys && !g_Config.m_SvRconPassword[0])
81 {
82 secure_random_password(buffer: g_Config.m_SvRconPassword, length: sizeof(g_Config.m_SvRconPassword), pw_length: 6);
83 AddDefaultKey(pRoleName: RoleName::ADMIN, pPw: g_Config.m_SvRconPassword);
84 m_Generated = true;
85 }
86}
87
88int CAuthManager::AddKeyHash(const char *pIdent, MD5_DIGEST Hash, const unsigned char *pSalt, const char *pRoleName)
89{
90 if(FindKey(pIdent) >= 0)
91 return -1;
92
93 CKey Key;
94 str_copy(dst&: Key.m_aIdent, src: pIdent);
95 Key.m_Pw = Hash;
96 mem_copy(dest: Key.m_aSalt, source: pSalt, SALT_BYTES);
97 Key.m_pRole = FindRole(pName: pRoleName);
98 dbg_assert(Key.m_pRole != nullptr, "Invalid role name '%s'.", pRoleName);
99
100 m_vKeys.push_back(x: Key);
101 return m_vKeys.size() - 1;
102}
103
104int CAuthManager::AddKey(const char *pIdent, const char *pPw, const char *pRoleName)
105{
106 // Generate random salt
107 unsigned char aSalt[SALT_BYTES];
108 secure_random_fill(bytes: aSalt, SALT_BYTES);
109 return AddKeyHash(pIdent, Hash: HashPassword(pPassword: pPw, aSalt), pSalt: aSalt, pRoleName);
110}
111
112void CAuthManager::RemoveKey(int Slot)
113{
114 m_vKeys.erase(position: m_vKeys.begin() + Slot);
115 // Update indices of default keys
116 for(int &Default : m_aDefault)
117 {
118 if(Default == Slot)
119 {
120 Default = -1;
121 }
122 else if(Default > Slot)
123 {
124 --Default;
125 }
126 }
127}
128
129int CAuthManager::FindKey(const char *pIdent) const
130{
131 for(size_t i = 0; i < m_vKeys.size(); i++)
132 if(!str_comp(a: m_vKeys[i].m_aIdent, b: pIdent))
133 return i;
134
135 return -1;
136}
137
138bool CAuthManager::CheckKey(int Slot, const char *pPw) const
139{
140 if(Slot < 0 || Slot >= (int)m_vKeys.size())
141 return false;
142 return m_vKeys[Slot].m_Pw == HashPassword(pPassword: pPw, aSalt: m_vKeys[Slot].m_aSalt);
143}
144
145int CAuthManager::DefaultIndex(int AuthLevel) const
146{
147 return std::size(m_aDefault) - AuthLevel;
148}
149
150int CAuthManager::DefaultKey(const char *pRoleName) const
151{
152 if(!str_comp(a: pRoleName, b: RoleName::ADMIN))
153 return m_aDefault[DefaultIndex(AuthLevel: AUTHED_ADMIN)];
154 if(!str_comp(a: pRoleName, b: RoleName::MODERATOR))
155 return m_aDefault[DefaultIndex(AuthLevel: AUTHED_MOD)];
156 if(!str_comp(a: pRoleName, b: RoleName::HELPER))
157 return m_aDefault[DefaultIndex(AuthLevel: AUTHED_HELPER)];
158 return 0;
159}
160
161int CAuthManager::KeyLevel(int Slot) const
162{
163 if(Slot < 0 || Slot >= (int)m_vKeys.size())
164 return false;
165 return m_vKeys[Slot].m_pRole->Rank();
166}
167
168const char *CAuthManager::KeyIdent(int Slot) const
169{
170 if(Slot < 0 || Slot >= (int)m_vKeys.size())
171 return nullptr;
172 return m_vKeys[Slot].m_aIdent;
173}
174
175bool CAuthManager::IsValidIdent(const char *pIdent) const
176{
177 return str_length(str: pIdent) < (int)sizeof(CKey().m_aIdent);
178}
179
180void CAuthManager::UpdateKeyHash(int Slot, MD5_DIGEST Hash, const unsigned char *pSalt, const char *pRoleName)
181{
182 if(Slot < 0 || Slot >= (int)m_vKeys.size())
183 return;
184
185 CKey *pKey = &m_vKeys[Slot];
186 pKey->m_Pw = Hash;
187 mem_copy(dest: pKey->m_aSalt, source: pSalt, SALT_BYTES);
188 pKey->m_pRole = FindRole(pName: pRoleName);
189}
190
191void CAuthManager::UpdateKey(int Slot, const char *pPw, const char *pRoleName)
192{
193 if(Slot < 0 || Slot >= (int)m_vKeys.size())
194 return;
195
196 // Generate random salt
197 unsigned char aSalt[SALT_BYTES];
198 secure_random_fill(bytes: aSalt, SALT_BYTES);
199 UpdateKeyHash(Slot, Hash: HashPassword(pPassword: pPw, aSalt), pSalt: aSalt, pRoleName);
200}
201
202void CAuthManager::ListKeys(FListCallback pfnListCallback, void *pUser)
203{
204 for(auto &Key : m_vKeys)
205 pfnListCallback(Key.m_aIdent, Key.m_pRole->Name(), pUser);
206}
207
208void CAuthManager::AddDefaultKey(const char *pRoleName, const char *pPw)
209{
210 int Level = RoleNameToAuthLevel(pRoleName);
211 dbg_assert(AUTHED_HELPER <= Level && Level <= AUTHED_ADMIN, "default role with name '%s' not found.", pRoleName);
212
213 static const char s_aaIdents[3][sizeof(HELPER_IDENT)] = {ADMIN_IDENT, MOD_IDENT, HELPER_IDENT};
214 int Index = AUTHED_ADMIN - Level;
215 if(m_aDefault[Index] >= 0)
216 return; // already exists
217 m_aDefault[Index] = AddKey(pIdent: s_aaIdents[Index], pPw, pRoleName: AuthLevelToRoleName(AuthLevel: Level));
218}
219
220bool CAuthManager::IsGenerated() const
221{
222 return m_Generated;
223}
224
225int CAuthManager::NumNonDefaultKeys() const
226{
227 int DefaultCount = std::count_if(first: std::begin(arr: m_aDefault), last: std::end(arr: m_aDefault), pred: [](int Slot) {
228 return Slot >= 0;
229 });
230 return m_vKeys.size() - DefaultCount;
231}
232
233CRconRole *CAuthManager::FindRole(const char *pName)
234{
235 auto It = m_Roles.find(x: pName);
236 if(It == m_Roles.end())
237 return nullptr;
238 return &It->second;
239}
240
241bool CAuthManager::AddRole(const char *pName, int Rank)
242{
243 if(FindRole(pName))
244 return false;
245
246 m_Roles.insert(x: {pName, CRconRole(pName, Rank)});
247 return true;
248}
249