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