1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3
4#include "server.h"
5
6#include "databases/connection.h"
7#include "databases/connection_pool.h"
8#include "register.h"
9
10#include <base/bytes.h>
11#include <base/fs.h>
12#include <base/io.h>
13#include <base/logger.h>
14#include <base/secure.h>
15
16#include <engine/config.h>
17#include <engine/console.h>
18#include <engine/engine.h>
19#include <engine/http.h>
20#include <engine/map.h>
21#include <engine/server.h>
22#include <engine/server/authmanager.h>
23#include <engine/shared/compression.h>
24#include <engine/shared/config.h>
25#include <engine/shared/console.h>
26#include <engine/shared/demo.h>
27#include <engine/shared/econ.h>
28#include <engine/shared/fifo.h>
29#include <engine/shared/filecollection.h>
30#include <engine/shared/host_lookup.h>
31#include <engine/shared/json.h>
32#include <engine/shared/jsonwriter.h>
33#include <engine/shared/linereader.h>
34#include <engine/shared/masterserver.h>
35#include <engine/shared/netban.h>
36#include <engine/shared/network.h>
37#include <engine/shared/packer.h>
38#include <engine/shared/protocol.h>
39#include <engine/shared/protocol7.h>
40#include <engine/shared/protocol_ex.h>
41#include <engine/shared/rust_version.h>
42#include <engine/shared/snapshot.h>
43#include <engine/storage.h>
44
45#include <game/version.h>
46
47#include <zlib.h>
48
49#include <algorithm>
50#include <chrono>
51#include <vector>
52
53using namespace std::chrono_literals;
54
55#if defined(CONF_PLATFORM_ANDROID)
56extern std::vector<std::string> FetchAndroidServerCommandQueue();
57#endif
58
59void CServerBan::InitServerBan(IConsole *pConsole, IStorage *pStorage, CServer *pServer)
60{
61 CNetBan::Init(pConsole, pStorage);
62
63 m_pServer = pServer;
64
65 Console()->Register(pName: "ban", pParams: "s[ip|id] ?i[minutes] r[reason]", Flags: CFGFLAG_SERVER | CFGFLAG_STORE, pfnFunc: ConBanExt, pUser: this, pHelp: "Ban player with ip/client id for x minutes for any reason");
66 Console()->Register(pName: "ban_region", pParams: "s[region] s[ip|id] ?i[minutes] r[reason]", Flags: CFGFLAG_SERVER | CFGFLAG_STORE, pfnFunc: ConBanRegion, pUser: this, pHelp: "Ban player in a region");
67 Console()->Register(pName: "ban_region_range", pParams: "s[region] s[first ip] s[last ip] ?i[minutes] r[reason]", Flags: CFGFLAG_SERVER | CFGFLAG_STORE, pfnFunc: ConBanRegionRange, pUser: this, pHelp: "Ban range in a region");
68}
69
70template<class T>
71int CServerBan::BanExt(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason, bool VerbatimReason)
72{
73 // validate address
74 if(Server()->m_RconClientId >= 0 && Server()->m_RconClientId < MAX_CLIENTS &&
75 Server()->m_aClients[Server()->m_RconClientId].m_State != CServer::CClient::STATE_EMPTY)
76 {
77 if(NetMatch(pData, Server()->ClientAddr(ClientId: Server()->m_RconClientId)))
78 {
79 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "ban error (you can't ban yourself)");
80 return -1;
81 }
82
83 for(int i = 0; i < MAX_CLIENTS; ++i)
84 {
85 if(i == Server()->m_RconClientId || Server()->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY)
86 continue;
87
88 if(Server()->GetAuthedState(ClientId: i) >= Server()->m_RconAuthLevel && NetMatch(pData, Server()->ClientAddr(ClientId: i)))
89 {
90 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "ban error (command denied)");
91 return -1;
92 }
93 }
94 }
95 else if(Server()->m_RconClientId == IServer::RCON_CID_VOTE)
96 {
97 for(int i = 0; i < MAX_CLIENTS; ++i)
98 {
99 if(Server()->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY)
100 continue;
101
102 if(Server()->IsRconAuthed(ClientId: i) && NetMatch(pData, Server()->ClientAddr(ClientId: i)))
103 {
104 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "ban error (command denied)");
105 return -1;
106 }
107 }
108 }
109
110 int Result = Ban(pBanPool, pData, Seconds, pReason, VerbatimReason);
111 if(Result != 0)
112 return Result;
113
114 // drop banned clients
115 typename T::CDataType Data = *pData;
116 for(int i = 0; i < MAX_CLIENTS; ++i)
117 {
118 if(Server()->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY)
119 continue;
120
121 if(NetMatch(&Data, Server()->ClientAddr(ClientId: i)))
122 {
123 CNetHash NetHash(&Data);
124 char aBuf[256];
125 MakeBanInfo(pBanPool->Find(&Data, &NetHash), aBuf, sizeof(aBuf), MSGTYPE_PLAYER);
126 Server()->m_NetServer.Drop(ClientId: i, pReason: aBuf);
127 }
128 }
129
130 return Result;
131}
132
133int CServerBan::BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason, bool VerbatimReason)
134{
135 return BanExt(pBanPool: &m_BanAddrPool, pData: pAddr, Seconds, pReason, VerbatimReason);
136}
137
138int CServerBan::BanRange(const CNetRange *pRange, int Seconds, const char *pReason)
139{
140 if(pRange->IsValid())
141 return BanExt(pBanPool: &m_BanRangePool, pData: pRange, Seconds, pReason, VerbatimReason: true);
142
143 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "ban failed (invalid range)");
144 return -1;
145}
146
147void CServerBan::ConBanExt(IConsole::IResult *pResult, void *pUser)
148{
149 CServerBan *pThis = static_cast<CServerBan *>(pUser);
150
151 const char *pStr = pResult->GetString(Index: 0);
152 int Minutes = pResult->NumArguments() > 1 ? std::clamp(val: pResult->GetInteger(Index: 1), lo: 0, hi: 525600) : 10;
153 const char *pReason = pResult->NumArguments() > 2 ? pResult->GetString(Index: 2) : "Follow the server rules. Type /rules into the chat.";
154
155 if(str_isallnum(str: pStr))
156 {
157 int ClientId = str_toint(str: pStr);
158 if(ClientId < 0 || ClientId >= MAX_CLIENTS || pThis->Server()->m_aClients[ClientId].m_State == CServer::CClient::STATE_EMPTY)
159 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "ban error (invalid client id)");
160 else
161 pThis->BanAddr(pAddr: pThis->Server()->ClientAddr(ClientId), Seconds: Minutes * 60, pReason, VerbatimReason: false);
162 }
163 else
164 {
165 NETADDR Addr;
166 if(net_addr_from_str(addr: &Addr, string: pStr) == 0)
167 pThis->BanAddr(pAddr: &Addr, Seconds: Minutes * 60, pReason, VerbatimReason: false);
168 else
169 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "ban error (invalid network address)");
170 }
171}
172
173void CServerBan::ConBanRegion(IConsole::IResult *pResult, void *pUser)
174{
175 const char *pRegion = pResult->GetString(Index: 0);
176 if(str_comp_nocase(a: pRegion, b: g_Config.m_SvRegionName))
177 return;
178
179 pResult->RemoveArgument(Index: 0);
180 ConBanExt(pResult, pUser);
181}
182
183void CServerBan::ConBanRegionRange(IConsole::IResult *pResult, void *pUser)
184{
185 CServerBan *pServerBan = static_cast<CServerBan *>(pUser);
186
187 const char *pRegion = pResult->GetString(Index: 0);
188 if(str_comp_nocase(a: pRegion, b: g_Config.m_SvRegionName))
189 return;
190
191 pResult->RemoveArgument(Index: 0);
192 ConBanRange(pResult, pUser: static_cast<CNetBan *>(pServerBan));
193}
194
195// Not thread-safe!
196class CRconClientLogger : public ILogger
197{
198 CServer *m_pServer;
199 int m_ClientId;
200
201public:
202 CRconClientLogger(CServer *pServer, int ClientId) :
203 m_pServer(pServer),
204 m_ClientId(ClientId)
205 {
206 }
207 void Log(const CLogMessage *pMessage) override;
208};
209
210void CRconClientLogger::Log(const CLogMessage *pMessage)
211{
212 if(m_Filter.Filters(pMessage))
213 {
214 return;
215 }
216 m_pServer->SendRconLogLine(ClientId: m_ClientId, pMessage);
217}
218
219void CServer::CClient::Reset()
220{
221 // reset input
222 for(auto &Input : m_aInputs)
223 Input.m_GameTick = -1;
224 m_CurrentInput = 0;
225 mem_zero(block: &m_LastPreInput, size: sizeof(m_LastPreInput));
226 mem_zero(block: &m_LatestInput, size: sizeof(m_LatestInput));
227
228 m_Snapshots.PurgeAll();
229 m_LastAckedSnapshot = -1;
230 m_LastInputTick = -1;
231 m_SnapRate = CClient::SNAPRATE_INIT;
232 m_Score = -1;
233 m_NextMapChunk = 0;
234 m_Flags = 0;
235 m_RedirectDropTime = 0;
236
237 std::fill(first: std::begin(arr&: m_aIdMap), last: std::end(arr&: m_aIdMap), value: -1);
238 std::fill(first: std::begin(arr&: m_aReverseIdMap), last: std::end(arr&: m_aReverseIdMap), value: -1);
239}
240
241CServer::CServer()
242{
243 m_pConfig = &g_Config;
244 for(int i = 0; i < MAX_CLIENTS; i++)
245 m_aDemoRecorder[i] = CDemoRecorder(&m_SnapshotDelta, true);
246 m_aDemoRecorder[RECORDER_MANUAL] = CDemoRecorder(&m_SnapshotDelta, false);
247 m_aDemoRecorder[RECORDER_AUTO] = CDemoRecorder(&m_SnapshotDelta, false);
248
249 m_pGameServer = nullptr;
250
251 m_CurrentGameTick = MIN_TICK;
252 m_RunServer = UNINITIALIZED;
253
254 m_aShutdownReason[0] = 0;
255
256 for(int i = 0; i < NUM_MAP_TYPES; i++)
257 {
258 m_apCurrentMapData[i] = nullptr;
259 m_aCurrentMapSize[i] = 0;
260 }
261
262 m_MapReload = false;
263 m_SameMapReload = false;
264 m_ReloadedWhenEmpty = false;
265 m_aMapDownloadUrl[0] = '\0';
266
267 m_RconClientId = IServer::RCON_CID_SERV;
268 m_RconAuthLevel = AUTHED_ADMIN;
269
270 m_ServerInfoFirstRequest = 0;
271 m_ServerInfoNumRequests = 0;
272
273#ifdef CONF_FAMILY_UNIX
274 m_ConnLoggingSocketCreated = false;
275#endif
276
277 m_pConnectionPool = new CDbConnectionPool();
278 m_pRegister = nullptr;
279
280 m_aErrorShutdownReason[0] = 0;
281
282 Init();
283}
284
285CServer::~CServer()
286{
287 for(auto &pCurrentMapData : m_apCurrentMapData)
288 {
289 free(ptr: pCurrentMapData);
290 }
291
292 if(m_RunServer != UNINITIALIZED)
293 {
294 for(auto &Client : m_aClients)
295 {
296 free(ptr: Client.m_pPersistentData);
297 }
298 }
299 free(ptr: m_pPersistentData);
300
301 delete m_pRegister;
302 delete m_pConnectionPool;
303}
304
305const char *CServer::DnsblStateStr(EDnsblState State)
306{
307 switch(State)
308 {
309 case EDnsblState::NONE:
310 return "n/a";
311 case EDnsblState::PENDING:
312 return "pending";
313 case EDnsblState::BLACKLISTED:
314 return "black";
315 case EDnsblState::WHITELISTED:
316 return "white";
317 }
318
319 dbg_assert_failed("Invalid dnsbl State: %d", static_cast<int>(State));
320}
321
322IConsole::EAccessLevel CServer::ConsoleAccessLevel(int ClientId) const
323{
324 int AuthLevel = GetAuthedState(ClientId);
325 switch(AuthLevel)
326 {
327 case AUTHED_ADMIN:
328 return IConsole::EAccessLevel::ADMIN;
329 case AUTHED_MOD:
330 return IConsole::EAccessLevel::MODERATOR;
331 case AUTHED_HELPER:
332 return IConsole::EAccessLevel::HELPER;
333 };
334
335 dbg_assert_failed("Invalid AuthLevel: %d", AuthLevel);
336}
337
338bool CServer::IsClientNameAvailable(int ClientId, const char *pNameRequest)
339{
340 // check for empty names
341 if(!pNameRequest[0])
342 return false;
343
344 // check for names starting with /, as they can be abused to make people
345 // write chat commands
346 if(pNameRequest[0] == '/')
347 return false;
348
349 // make sure that two clients don't have the same name
350 for(int i = 0; i < MAX_CLIENTS; i++)
351 {
352 if(i != ClientId && m_aClients[i].m_State >= CClient::STATE_READY)
353 {
354 if(str_utf8_comp_confusable(str1: pNameRequest, str2: m_aClients[i].m_aName) == 0)
355 return false;
356 }
357 }
358
359 return true;
360}
361
362bool CServer::SetClientNameImpl(int ClientId, const char *pNameRequest, bool Set)
363{
364 dbg_assert(0 <= ClientId && ClientId < MAX_CLIENTS, "Invalid ClientId: %d", ClientId);
365 if(m_aClients[ClientId].m_State < CClient::STATE_READY)
366 return false;
367
368 const CNameBan *pBanned = m_NameBans.IsBanned(pName: pNameRequest);
369 if(pBanned)
370 {
371 if(m_aClients[ClientId].m_State == CClient::STATE_READY && Set)
372 {
373 char aBuf[256];
374 if(pBanned->m_aReason[0])
375 {
376 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Kicked (your name is banned: %s)", pBanned->m_aReason);
377 }
378 else
379 {
380 str_copy(dst&: aBuf, src: "Kicked (your name is banned)");
381 }
382 Kick(ClientId, pReason: aBuf);
383 }
384 return false;
385 }
386
387 // trim the name
388 char aTrimmedName[MAX_NAME_LENGTH];
389 str_copy(dst&: aTrimmedName, src: str_utf8_skip_whitespaces(str: pNameRequest));
390 str_utf8_trim_right(param: aTrimmedName);
391
392 char aNameTry[MAX_NAME_LENGTH];
393 str_copy(dst&: aNameTry, src: aTrimmedName);
394
395 if(!IsClientNameAvailable(ClientId, pNameRequest: aNameTry))
396 {
397 // auto rename
398 for(int i = 1;; i++)
399 {
400 str_format(buffer: aNameTry, buffer_size: sizeof(aNameTry), format: "(%d)%s", i, aTrimmedName);
401 if(IsClientNameAvailable(ClientId, pNameRequest: aNameTry))
402 break;
403 }
404 }
405
406 bool Changed = str_comp(a: m_aClients[ClientId].m_aName, b: aNameTry) != 0;
407
408 if(Set && Changed)
409 {
410 // set the client name
411 str_copy(dst&: m_aClients[ClientId].m_aName, src: aNameTry);
412 GameServer()->TeehistorianRecordPlayerName(ClientId, pName: m_aClients[ClientId].m_aName);
413 }
414
415 return Changed;
416}
417
418bool CServer::SetClientClanImpl(int ClientId, const char *pClanRequest, bool Set)
419{
420 dbg_assert(0 <= ClientId && ClientId < MAX_CLIENTS, "Invalid ClientId: %d", ClientId);
421 if(m_aClients[ClientId].m_State < CClient::STATE_READY)
422 return false;
423
424 const CNameBan *pBanned = m_NameBans.IsBanned(pName: pClanRequest);
425 if(pBanned)
426 {
427 if(m_aClients[ClientId].m_State == CClient::STATE_READY && Set)
428 {
429 char aBuf[256];
430 if(pBanned->m_aReason[0])
431 {
432 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Kicked (your clan is banned: %s)", pBanned->m_aReason);
433 }
434 else
435 {
436 str_copy(dst&: aBuf, src: "Kicked (your clan is banned)");
437 }
438 Kick(ClientId, pReason: aBuf);
439 }
440 return false;
441 }
442
443 // trim the clan
444 char aTrimmedClan[MAX_CLAN_LENGTH];
445 str_copy(dst&: aTrimmedClan, src: str_utf8_skip_whitespaces(str: pClanRequest));
446 str_utf8_trim_right(param: aTrimmedClan);
447
448 bool Changed = str_comp(a: m_aClients[ClientId].m_aClan, b: aTrimmedClan) != 0;
449
450 if(Set)
451 {
452 // set the client clan
453 str_copy(dst&: m_aClients[ClientId].m_aClan, src: aTrimmedClan);
454 }
455
456 return Changed;
457}
458
459bool CServer::WouldClientNameChange(int ClientId, const char *pNameRequest)
460{
461 return SetClientNameImpl(ClientId, pNameRequest, Set: false);
462}
463
464bool CServer::WouldClientClanChange(int ClientId, const char *pClanRequest)
465{
466 return SetClientClanImpl(ClientId, pClanRequest, Set: false);
467}
468
469void CServer::SetClientName(int ClientId, const char *pName)
470{
471 SetClientNameImpl(ClientId, pNameRequest: pName, Set: true);
472}
473
474void CServer::SetClientClan(int ClientId, const char *pClan)
475{
476 SetClientClanImpl(ClientId, pClanRequest: pClan, Set: true);
477}
478
479void CServer::SetClientCountry(int ClientId, int Country)
480{
481 if(ClientId < 0 || ClientId >= MAX_CLIENTS || m_aClients[ClientId].m_State < CClient::STATE_READY)
482 return;
483
484 m_aClients[ClientId].m_Country = Country;
485}
486
487void CServer::SetClientScore(int ClientId, std::optional<int> Score)
488{
489 if(ClientId < 0 || ClientId >= MAX_CLIENTS || m_aClients[ClientId].m_State < CClient::STATE_READY)
490 return;
491
492 if(m_aClients[ClientId].m_Score != Score)
493 ExpireServerInfo();
494
495 m_aClients[ClientId].m_Score = Score;
496}
497
498void CServer::SetClientFlags(int ClientId, int Flags)
499{
500 if(ClientId < 0 || ClientId >= MAX_CLIENTS || m_aClients[ClientId].m_State < CClient::STATE_READY)
501 return;
502
503 m_aClients[ClientId].m_Flags = Flags;
504}
505
506void CServer::Kick(int ClientId, const char *pReason)
507{
508 if(ClientId < 0 || ClientId >= MAX_CLIENTS || m_aClients[ClientId].m_State == CClient::STATE_EMPTY)
509 {
510 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server", pStr: "invalid client id to kick");
511 return;
512 }
513 else if(m_RconClientId == ClientId)
514 {
515 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server", pStr: "you can't kick yourself");
516 return;
517 }
518 else if(GetAuthedState(ClientId) > m_RconAuthLevel)
519 {
520 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server", pStr: "kick command denied");
521 return;
522 }
523
524 m_NetServer.Drop(ClientId, pReason);
525}
526
527void CServer::Ban(int ClientId, int Seconds, const char *pReason, bool VerbatimReason)
528{
529 m_NetServer.NetBan()->BanAddr(pAddr: ClientAddr(ClientId), Seconds, pReason, VerbatimReason);
530}
531
532void CServer::ReconnectClient(int ClientId)
533{
534 dbg_assert(0 <= ClientId && ClientId < MAX_CLIENTS, "Invalid ClientId: %d", ClientId);
535 dbg_assert(m_aClients[ClientId].m_State != CClient::STATE_EMPTY, "Client slot empty: %d", ClientId);
536
537 if(GetClientVersion(ClientId) < VERSION_DDNET_RECONNECT)
538 {
539 RedirectClient(ClientId, Port: m_NetServer.Address().port);
540 return;
541 }
542 log_info("server", "telling client to reconnect, cid=%d", ClientId);
543
544 CMsgPacker Msg(NETMSG_RECONNECT, true);
545 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_FLUSH, ClientId);
546
547 if(m_aClients[ClientId].m_State >= CClient::STATE_READY)
548 {
549 GameServer()->OnClientDrop(ClientId, pReason: "reconnect");
550 }
551
552 m_aClients[ClientId].m_RedirectDropTime = time_get() + time_freq() * 10;
553 m_aClients[ClientId].m_State = CClient::STATE_REDIRECTED;
554}
555
556void CServer::RedirectClient(int ClientId, int Port)
557{
558 dbg_assert(0 <= ClientId && ClientId < MAX_CLIENTS, "Invalid ClientId: %d", ClientId);
559 dbg_assert(m_aClients[ClientId].m_State != CClient::STATE_EMPTY, "Client slot empty: %d", ClientId);
560
561 bool SupportsRedirect = GetClientVersion(ClientId) >= VERSION_DDNET_REDIRECT;
562
563 log_info("server", "redirecting client, cid=%d port=%d supported=%d", ClientId, Port, SupportsRedirect);
564
565 if(!SupportsRedirect)
566 {
567 char aBuf[128];
568 bool SamePort = Port == this->Port();
569 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Redirect unsupported: please connect to port %d", Port);
570 Kick(ClientId, pReason: SamePort ? "Redirect unsupported: please reconnect" : aBuf);
571 return;
572 }
573
574 CMsgPacker Msg(NETMSG_REDIRECT, true);
575 Msg.AddInt(i: Port);
576 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_FLUSH, ClientId);
577
578 if(m_aClients[ClientId].m_State >= CClient::STATE_READY)
579 {
580 GameServer()->OnClientDrop(ClientId, pReason: "redirect");
581 }
582
583 m_aClients[ClientId].m_RedirectDropTime = time_get() + time_freq() * 10;
584 m_aClients[ClientId].m_State = CClient::STATE_REDIRECTED;
585}
586
587int64_t CServer::TickStartTime(int Tick)
588{
589 return m_GameStartTime + (time_freq() * Tick) / TickSpeed();
590}
591
592int CServer::Init()
593{
594 for(auto &Client : m_aClients)
595 {
596 Client.m_State = CClient::STATE_EMPTY;
597 Client.m_aName[0] = 0;
598 Client.m_aClan[0] = 0;
599 Client.m_Country = CountryCode::DEFAULT;
600 Client.m_Snapshots.Init();
601 Client.m_Traffic = 0;
602 Client.m_TrafficSince = 0;
603 Client.m_ShowIps = false;
604 Client.m_DebugDummy = false;
605 Client.m_AuthKey = -1;
606 Client.m_Latency = 0;
607 Client.m_Sixup = false;
608 Client.m_RedirectDropTime = 0;
609 }
610
611 m_CurrentGameTick = MIN_TICK;
612
613 m_AnnouncementLastLine = -1;
614 std::fill(first: std::begin(arr&: m_aPrevStates), last: std::end(arr&: m_aPrevStates), value: 0);
615
616 return 0;
617}
618
619bool CServer::StrHideIps(const char *pInput, char *pOutputWithIps, size_t OutputWithIpsSize, char *pOutputWithoutIps, size_t OutputWithoutIpsSize)
620{
621 const char *pStart = str_find(haystack: pInput, needle: "<{");
622 const char *pEnd = pStart == nullptr ? nullptr : str_find(haystack: pStart + 2, needle: "}>");
623 pOutputWithIps[0] = '\0';
624 pOutputWithoutIps[0] = '\0';
625
626 if(pStart == nullptr || pEnd == nullptr)
627 {
628 str_copy(dst: pOutputWithIps, src: pInput, dst_size: OutputWithIpsSize);
629 str_copy(dst: pOutputWithoutIps, src: pInput, dst_size: OutputWithoutIpsSize);
630 return false;
631 }
632
633 str_append(dst: pOutputWithIps, src: pInput, dst_size: std::min(a: (size_t)(pStart - pInput + 1), b: OutputWithIpsSize));
634 str_append(dst: pOutputWithIps, src: pStart + 2, dst_size: std::min(a: (size_t)(pEnd - pInput - 1), b: OutputWithIpsSize));
635 str_append(dst: pOutputWithIps, src: pEnd + 2, dst_size: OutputWithIpsSize);
636
637 str_append(dst: pOutputWithoutIps, src: pInput, dst_size: std::min(a: (size_t)(pStart - pInput + 1), b: OutputWithoutIpsSize));
638 str_append(dst: pOutputWithoutIps, src: "XXX", dst_size: OutputWithoutIpsSize);
639 str_append(dst: pOutputWithoutIps, src: pEnd + 2, dst_size: OutputWithoutIpsSize);
640 return true;
641}
642
643void CServer::SendLogLine(const CLogMessage *pMessage)
644{
645 if(pMessage->m_Level <= IConsole::ToLogLevelFilter(ConsoleLevel: g_Config.m_ConsoleOutputLevel))
646 {
647 SendRconLogLine(ClientId: -1, pMessage);
648 }
649 if(pMessage->m_Level <= IConsole::ToLogLevelFilter(ConsoleLevel: g_Config.m_EcOutputLevel))
650 {
651 m_Econ.Send(ClientId: -1, pLine: pMessage->m_aLine);
652 }
653}
654
655void CServer::SetRconCid(int ClientId)
656{
657 m_RconClientId = ClientId;
658}
659
660int CServer::GetAuthedState(int ClientId) const
661{
662 if(ClientId == IConsole::CLIENT_ID_UNSPECIFIED)
663 return AUTHED_ADMIN;
664 if(ClientId == IConsole::CLIENT_ID_GAME)
665 return AUTHED_ADMIN;
666 if(ClientId == IConsole::CLIENT_ID_NO_GAME)
667 return AUTHED_ADMIN;
668 dbg_assert(ClientId >= 0 && ClientId < MAX_CLIENTS, "Invalid ClientId: %d", ClientId);
669 dbg_assert(m_aClients[ClientId].m_State != CServer::CClient::STATE_EMPTY, "Client slot %d is empty", ClientId);
670 return m_AuthManager.KeyLevel(Slot: m_aClients[ClientId].m_AuthKey);
671}
672
673bool CServer::IsRconAuthed(int ClientId) const
674{
675 return GetAuthedState(ClientId) != AUTHED_NO;
676}
677
678bool CServer::IsRconAuthedAdmin(int ClientId) const
679{
680 return GetAuthedState(ClientId) == AUTHED_ADMIN;
681}
682
683const char *CServer::GetAuthName(int ClientId) const
684{
685 dbg_assert(ClientId >= 0 && ClientId < MAX_CLIENTS, "Invalid ClientId: %d", ClientId);
686 dbg_assert(m_aClients[ClientId].m_State != CServer::CClient::STATE_EMPTY, "Client slot %d is empty", ClientId);
687 int Key = m_aClients[ClientId].m_AuthKey;
688 dbg_assert(Key != -1, "Client not authed");
689 return m_AuthManager.KeyIdent(Slot: Key);
690}
691
692bool CServer::HasAuthHidden(int ClientId) const
693{
694 dbg_assert(ClientId >= 0 && ClientId < MAX_CLIENTS, "Invalid ClientId: %d", ClientId);
695 return m_aClients[ClientId].m_AuthHidden;
696}
697
698bool CServer::GetClientInfo(int ClientId, CClientInfo *pInfo) const
699{
700 dbg_assert(ClientId >= 0 && ClientId < MAX_CLIENTS, "Invalid ClientId: %d", ClientId);
701 dbg_assert(pInfo != nullptr, "pInfo cannot be null");
702
703 if(m_aClients[ClientId].m_State == CClient::STATE_INGAME)
704 {
705 pInfo->m_pName = m_aClients[ClientId].m_aName;
706 pInfo->m_Latency = m_aClients[ClientId].m_Latency;
707 pInfo->m_GotDDNetVersion = m_aClients[ClientId].m_DDNetVersionSettled;
708 pInfo->m_DDNetVersion = m_aClients[ClientId].m_DDNetVersion >= 0 ? m_aClients[ClientId].m_DDNetVersion : VERSION_VANILLA;
709 if(m_aClients[ClientId].m_GotDDNetVersionPacket)
710 {
711 pInfo->m_pConnectionId = &m_aClients[ClientId].m_ConnectionId;
712 pInfo->m_pDDNetVersionStr = m_aClients[ClientId].m_aDDNetVersionStr;
713 }
714 else
715 {
716 pInfo->m_pConnectionId = nullptr;
717 pInfo->m_pDDNetVersionStr = nullptr;
718 }
719 return true;
720 }
721 return false;
722}
723
724void CServer::SetClientDDNetVersion(int ClientId, int DDNetVersion)
725{
726 dbg_assert(ClientId >= 0 && ClientId < MAX_CLIENTS, "Invalid ClientId: %d", ClientId);
727
728 if(m_aClients[ClientId].m_State == CClient::STATE_INGAME)
729 {
730 m_aClients[ClientId].m_DDNetVersion = DDNetVersion;
731 m_aClients[ClientId].m_DDNetVersionSettled = true;
732 }
733}
734
735const NETADDR *CServer::ClientAddr(int ClientId) const
736{
737 dbg_assert(ClientId >= 0 && ClientId < MAX_CLIENTS, "Invalid ClientId: %d", ClientId);
738 dbg_assert(m_aClients[ClientId].m_State != CServer::CClient::STATE_EMPTY, "Client slot %d is empty", ClientId);
739 if(m_aClients[ClientId].m_DebugDummy)
740 {
741 return &m_aClients[ClientId].m_DebugDummyAddr;
742 }
743 return m_NetServer.ClientAddr(ClientId);
744}
745
746const std::array<char, NETADDR_MAXSTRSIZE> &CServer::ClientAddrStringImpl(int ClientId, bool IncludePort) const
747{
748 dbg_assert(ClientId >= 0 && ClientId < MAX_CLIENTS, "Invalid ClientId: %d", ClientId);
749 dbg_assert(m_aClients[ClientId].m_State != CServer::CClient::STATE_EMPTY, "Client slot %d is empty", ClientId);
750 if(m_aClients[ClientId].m_DebugDummy)
751 {
752 return IncludePort ? m_aClients[ClientId].m_aDebugDummyAddrString : m_aClients[ClientId].m_aDebugDummyAddrStringNoPort;
753 }
754 return m_NetServer.ClientAddrString(ClientId, IncludePort);
755}
756
757const char *CServer::ClientName(int ClientId) const
758{
759 if(ClientId < 0 || ClientId >= MAX_CLIENTS || m_aClients[ClientId].m_State == CServer::CClient::STATE_EMPTY)
760 return "(invalid)";
761 if(m_aClients[ClientId].m_State == CServer::CClient::STATE_INGAME || m_aClients[ClientId].m_State == CServer::CClient::STATE_REDIRECTED)
762 return m_aClients[ClientId].m_aName;
763 else
764 return "(connecting)";
765}
766
767const char *CServer::ClientClan(int ClientId) const
768{
769 if(ClientId < 0 || ClientId >= MAX_CLIENTS || m_aClients[ClientId].m_State == CServer::CClient::STATE_EMPTY)
770 return "";
771 if(m_aClients[ClientId].m_State == CServer::CClient::STATE_INGAME)
772 return m_aClients[ClientId].m_aClan;
773 else
774 return "";
775}
776
777int CServer::ClientCountry(int ClientId) const
778{
779 if(ClientId < 0 || ClientId >= MAX_CLIENTS || m_aClients[ClientId].m_State == CServer::CClient::STATE_EMPTY)
780 return -1;
781 if(m_aClients[ClientId].m_State == CServer::CClient::STATE_INGAME)
782 return m_aClients[ClientId].m_Country;
783 else
784 return -1;
785}
786
787bool CServer::ClientSlotEmpty(int ClientId) const
788{
789 return ClientId >= 0 && ClientId < MAX_CLIENTS && m_aClients[ClientId].m_State == CServer::CClient::STATE_EMPTY;
790}
791
792bool CServer::ClientIngame(int ClientId) const
793{
794 return ClientId >= 0 && ClientId < MAX_CLIENTS && m_aClients[ClientId].m_State == CServer::CClient::STATE_INGAME;
795}
796
797int CServer::Port() const
798{
799 return m_NetServer.Address().port;
800}
801
802int CServer::MaxClients() const
803{
804 return m_RunServer == UNINITIALIZED ? 0 : m_NetServer.MaxClients();
805}
806
807int CServer::ClientCount() const
808{
809 int ClientCount = 0;
810 for(const auto &Client : m_aClients)
811 {
812 if(Client.m_State != CClient::STATE_EMPTY)
813 {
814 ClientCount++;
815 }
816 }
817
818 return ClientCount;
819}
820
821int CServer::DistinctClientCount() const
822{
823 const NETADDR *apAddresses[MAX_CLIENTS];
824 for(int i = 0; i < MAX_CLIENTS; i++)
825 {
826 // connecting clients with spoofed ips can clog slots without being ingame
827 apAddresses[i] = ClientIngame(ClientId: i) ? ClientAddr(ClientId: i) : nullptr;
828 }
829
830 int ClientCount = 0;
831 for(int i = 0; i < MAX_CLIENTS; i++)
832 {
833 if(apAddresses[i] == nullptr)
834 {
835 continue;
836 }
837 ClientCount++;
838 for(int j = 0; j < i; j++)
839 {
840 if(apAddresses[j] != nullptr && !net_addr_comp_noport(a: apAddresses[i], b: apAddresses[j]))
841 {
842 ClientCount--;
843 break;
844 }
845 }
846 }
847 return ClientCount;
848}
849
850int CServer::GetClientVersion(int ClientId) const
851{
852 // Assume latest client version for server demos
853 if(ClientId == SERVER_DEMO_CLIENT)
854 return DDNET_VERSION_NUMBER;
855
856 CClientInfo Info;
857 if(GetClientInfo(ClientId, pInfo: &Info))
858 return Info.m_DDNetVersion;
859 return VERSION_NONE;
860}
861
862static inline bool RepackMsg(const CMsgPacker *pMsg, CPacker &Packer, bool Sixup)
863{
864 int MsgId = pMsg->m_MsgId;
865 Packer.Reset();
866
867 if(Sixup && !pMsg->m_NoTranslate)
868 {
869 if(pMsg->m_System)
870 {
871 if(MsgId >= OFFSET_UUID)
872 ;
873 else if(MsgId >= NETMSG_MAP_CHANGE && MsgId <= NETMSG_MAP_DATA)
874 ;
875 else if(MsgId >= NETMSG_CON_READY && MsgId <= NETMSG_INPUTTIMING)
876 MsgId += 1;
877 else if(MsgId == NETMSG_RCON_LINE)
878 MsgId = protocol7::NETMSG_RCON_LINE;
879 else if(MsgId >= NETMSG_PING && MsgId <= NETMSG_PING_REPLY)
880 MsgId += 4;
881 else if(MsgId >= NETMSG_RCON_CMD_ADD && MsgId <= NETMSG_RCON_CMD_REM)
882 MsgId -= 11;
883 else
884 {
885 log_error("net", "DROP send sys %d", MsgId);
886 return false;
887 }
888 }
889 else
890 {
891 if(MsgId >= 0 && MsgId < OFFSET_UUID)
892 MsgId = Msg_SixToSeven(a: MsgId);
893
894 if(MsgId < 0)
895 return false;
896 }
897 }
898
899 if(MsgId < OFFSET_UUID)
900 {
901 Packer.AddInt(i: (MsgId << 1) | (pMsg->m_System ? 1 : 0));
902 }
903 else
904 {
905 Packer.AddInt(i: pMsg->m_System ? 1 : 0); // NETMSG_EX, NETMSGTYPE_EX
906 g_UuidManager.PackUuid(Id: MsgId, pPacker: &Packer);
907 }
908 Packer.AddRaw(pData: pMsg->Data(), Size: pMsg->Size());
909
910 return true;
911}
912
913int CServer::SendMsg(CMsgPacker *pMsg, int Flags, int ClientId)
914{
915 CNetChunk Packet;
916 mem_zero(block: &Packet, size: sizeof(CNetChunk));
917 if(Flags & MSGFLAG_VITAL)
918 Packet.m_Flags |= NETSENDFLAG_VITAL;
919 if(Flags & MSGFLAG_FLUSH)
920 Packet.m_Flags |= NETSENDFLAG_FLUSH;
921
922 if(ClientId < 0)
923 {
924 CPacker Pack6, Pack7;
925 if(!RepackMsg(pMsg, Packer&: Pack6, Sixup: false))
926 return -1;
927 if(!RepackMsg(pMsg, Packer&: Pack7, Sixup: true))
928 return -1;
929
930 // write message to demo recorders
931 if(!(Flags & MSGFLAG_NORECORD))
932 {
933 for(auto &Recorder : m_aDemoRecorder)
934 if(Recorder.IsRecording())
935 Recorder.RecordMessage(pData: Pack6.Data(), Size: Pack6.Size());
936 }
937
938 if(!(Flags & MSGFLAG_NOSEND))
939 {
940 for(int i = 0; i < MAX_CLIENTS; i++)
941 {
942 if(m_aClients[i].m_State == CClient::STATE_INGAME)
943 {
944 CPacker *pPack = m_aClients[i].m_Sixup ? &Pack7 : &Pack6;
945 Packet.m_pData = pPack->Data();
946 Packet.m_DataSize = pPack->Size();
947 Packet.m_ClientId = i;
948 if(Antibot()->OnEngineServerMessage(ClientId: i, pData: Packet.m_pData, Size: Packet.m_DataSize, Flags))
949 {
950 continue;
951 }
952 m_NetServer.Send(pChunk: &Packet);
953 }
954 }
955 }
956 }
957 else
958 {
959 CPacker Pack;
960 if(!RepackMsg(pMsg, Packer&: Pack, Sixup: m_aClients[ClientId].m_Sixup))
961 return -1;
962
963 Packet.m_ClientId = ClientId;
964 Packet.m_pData = Pack.Data();
965 Packet.m_DataSize = Pack.Size();
966
967 if(Antibot()->OnEngineServerMessage(ClientId, pData: Packet.m_pData, Size: Packet.m_DataSize, Flags))
968 {
969 return 0;
970 }
971
972 // write message to demo recorders
973 if(!(Flags & MSGFLAG_NORECORD))
974 {
975 if(m_aDemoRecorder[ClientId].IsRecording())
976 m_aDemoRecorder[ClientId].RecordMessage(pData: Pack.Data(), Size: Pack.Size());
977 if(m_aDemoRecorder[RECORDER_MANUAL].IsRecording())
978 m_aDemoRecorder[RECORDER_MANUAL].RecordMessage(pData: Pack.Data(), Size: Pack.Size());
979 if(m_aDemoRecorder[RECORDER_AUTO].IsRecording())
980 m_aDemoRecorder[RECORDER_AUTO].RecordMessage(pData: Pack.Data(), Size: Pack.Size());
981 }
982
983 if(!(Flags & MSGFLAG_NOSEND))
984 m_NetServer.Send(pChunk: &Packet);
985 }
986
987 return 0;
988}
989
990void CServer::SendMsgRaw(int ClientId, const void *pData, int Size, int Flags)
991{
992 CNetChunk Packet;
993 mem_zero(block: &Packet, size: sizeof(CNetChunk));
994 Packet.m_ClientId = ClientId;
995 Packet.m_pData = pData;
996 Packet.m_DataSize = Size;
997 Packet.m_Flags = 0;
998 if(Flags & MSGFLAG_VITAL)
999 {
1000 Packet.m_Flags |= NETSENDFLAG_VITAL;
1001 }
1002 if(Flags & MSGFLAG_FLUSH)
1003 {
1004 Packet.m_Flags |= NETSENDFLAG_FLUSH;
1005 }
1006 m_NetServer.Send(pChunk: &Packet);
1007}
1008
1009void CServer::DoSnapshot()
1010{
1011 bool IsGlobalSnap = Config()->m_SvHighBandwidth || (m_CurrentGameTick % 2) == 0;
1012
1013 if(m_aDemoRecorder[RECORDER_MANUAL].IsRecording() || m_aDemoRecorder[RECORDER_AUTO].IsRecording())
1014 {
1015 // create snapshot for demo recording
1016 CSnapshotBuffer Data;
1017
1018 // build snap and possibly add some messages
1019 m_SnapshotBuilder.Init();
1020 GameServer()->OnSnap(ClientId: -1, GlobalSnap: IsGlobalSnap, RecordingDemo: true);
1021 int SnapshotSize = m_SnapshotBuilder.Finish(pBuffer: &Data);
1022
1023 // write snapshot
1024 if(m_aDemoRecorder[RECORDER_MANUAL].IsRecording())
1025 m_aDemoRecorder[RECORDER_MANUAL].RecordSnapshot(Tick: Tick(), pData: Data.AsSnapshot(), Size: SnapshotSize);
1026 if(m_aDemoRecorder[RECORDER_AUTO].IsRecording())
1027 m_aDemoRecorder[RECORDER_AUTO].RecordSnapshot(Tick: Tick(), pData: Data.AsSnapshot(), Size: SnapshotSize);
1028 }
1029
1030 // create snapshots for all clients
1031 for(int i = 0; i < MaxClients(); i++)
1032 {
1033 // client must be ingame to receive snapshots
1034 if(m_aClients[i].m_State != CClient::STATE_INGAME)
1035 continue;
1036
1037 // don't send snapshots to clients that haven't identified as DDNet-based yet, can crash them.
1038 if(!m_aClients[i].m_Sixup && m_aClients[i].m_DDNetVersion < VERSION_DDNET_OLD)
1039 continue;
1040
1041 // this client is trying to recover, don't spam snapshots
1042 if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_RECOVER && (Tick() % TickSpeed()) != 0)
1043 continue;
1044
1045 // this client is trying to recover, don't spam snapshots
1046 if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_INIT && (Tick() % 10) != 0)
1047 continue;
1048
1049 // only allow clients with forced high bandwidth on spectate to receive snapshots on non-global ticks
1050 if(!IsGlobalSnap && !(m_aClients[i].m_ForceHighBandwidthOnSpectate && GameServer()->IsClientHighBandwidth(ClientId: i)))
1051 continue;
1052
1053 {
1054 m_SnapshotBuilder.Init(Sixup: m_aClients[i].m_Sixup);
1055
1056 // only snap events on global ticks
1057 GameServer()->OnSnap(ClientId: i, GlobalSnap: IsGlobalSnap, RecordingDemo: m_aDemoRecorder[i].IsRecording());
1058
1059 // finish snapshot
1060 CSnapshotBuffer Data;
1061 int SnapshotSize = m_SnapshotBuilder.Finish(pBuffer: &Data);
1062
1063 if(m_aDemoRecorder[i].IsRecording())
1064 {
1065 // write snapshot
1066 m_aDemoRecorder[i].RecordSnapshot(Tick: Tick(), pData: Data.AsSnapshot(), Size: SnapshotSize);
1067 }
1068
1069 int Crc = Data.AsSnapshot()->Crc();
1070
1071 // Remove old snapshots. Only the last acked snapshot
1072 // is still needed as delta base, keep at most 3
1073 // seconds worth for clients that aren't acking.
1074 //
1075 // This also works for the sentinel value -1 of
1076 // `m_LastAckedSnapshot` (before the first ack):
1077 // the max then falls back to the 3 second cap.
1078 m_aClients[i].m_Snapshots.PurgeUntil(Tick: std::max(a: m_CurrentGameTick - TickSpeed() * 3, b: m_aClients[i].m_LastAckedSnapshot));
1079
1080 // save the snapshot
1081 m_aClients[i].m_Snapshots.Add(Tick: m_CurrentGameTick, Tagtime: time_get(), DataSize: SnapshotSize, pData: Data.AsSnapshot(), AltDataSize: 0, pAltData: nullptr);
1082
1083 // find snapshot that we can perform delta against
1084 int DeltaTick = -1;
1085 const CSnapshot *pDeltashot = CSnapshot::EmptySnapshot();
1086 {
1087 int DeltashotSize;
1088 if(m_aClients[i].m_LastAckedSnapshot >= MIN_TICK)
1089 {
1090 DeltashotSize = m_aClients[i].m_Snapshots.Get(Tick: m_aClients[i].m_LastAckedSnapshot, pTagtime: nullptr, ppData: &pDeltashot, ppAltData: nullptr);
1091 }
1092 else
1093 {
1094 DeltashotSize = -1;
1095 }
1096 if(DeltashotSize >= 0)
1097 {
1098 DeltaTick = m_aClients[i].m_LastAckedSnapshot;
1099 }
1100 else
1101 {
1102 // no acked package found, force client to recover rate
1103 if(m_aClients[i].m_SnapRate == CClient::SNAPRATE_FULL)
1104 m_aClients[i].m_SnapRate = CClient::SNAPRATE_RECOVER;
1105 }
1106 }
1107
1108 // create delta
1109 CSnapshotDelta *const pSnapshotDelta = IsSixup(ClientId: i) ? &m_SnapshotDeltaSixup : &m_SnapshotDelta;
1110 char aDeltaData[CSnapshot::MAX_SIZE];
1111 int DeltaSize = pSnapshotDelta->CreateDelta(pFrom: pDeltashot, pTo: Data.AsSnapshot(), pDstData: aDeltaData);
1112
1113 if(DeltaSize)
1114 {
1115 // compress it
1116 const int MaxSize = MAX_SNAPSHOT_PACKSIZE;
1117
1118 char aCompData[CSnapshot::MAX_SIZE];
1119 SnapshotSize = CVariableInt::Compress(pSrc: aDeltaData, SrcSize: DeltaSize, pDst: aCompData, DstSize: sizeof(aCompData));
1120 int NumPackets = (SnapshotSize + MaxSize - 1) / MaxSize;
1121
1122 for(int n = 0, Left = SnapshotSize; Left > 0; n++)
1123 {
1124 int Chunk = Left < MaxSize ? Left : MaxSize;
1125 Left -= Chunk;
1126
1127 if(NumPackets == 1)
1128 {
1129 CMsgPacker Msg(NETMSG_SNAPSINGLE, true);
1130 Msg.AddInt(i: m_CurrentGameTick);
1131 Msg.AddInt(i: m_CurrentGameTick - DeltaTick);
1132 Msg.AddInt(i: Crc);
1133 Msg.AddInt(i: Chunk);
1134 Msg.AddRaw(pData: &aCompData[n * MaxSize], Size: Chunk);
1135 SendMsg(pMsg: &Msg, Flags: MSGFLAG_FLUSH, ClientId: i);
1136 }
1137 else
1138 {
1139 CMsgPacker Msg(NETMSG_SNAP, true);
1140 Msg.AddInt(i: m_CurrentGameTick);
1141 Msg.AddInt(i: m_CurrentGameTick - DeltaTick);
1142 Msg.AddInt(i: NumPackets);
1143 Msg.AddInt(i: n);
1144 Msg.AddInt(i: Crc);
1145 Msg.AddInt(i: Chunk);
1146 Msg.AddRaw(pData: &aCompData[n * MaxSize], Size: Chunk);
1147 SendMsg(pMsg: &Msg, Flags: MSGFLAG_FLUSH, ClientId: i);
1148 }
1149 }
1150 }
1151 else
1152 {
1153 CMsgPacker Msg(NETMSG_SNAPEMPTY, true);
1154 Msg.AddInt(i: m_CurrentGameTick);
1155 Msg.AddInt(i: m_CurrentGameTick - DeltaTick);
1156 SendMsg(pMsg: &Msg, Flags: MSGFLAG_FLUSH, ClientId: i);
1157 }
1158 }
1159 }
1160
1161 if(IsGlobalSnap)
1162 {
1163 GameServer()->OnPostGlobalSnap();
1164 }
1165}
1166
1167int CServer::ClientRejoinCallback(int ClientId, void *pUser)
1168{
1169 CServer *pThis = (CServer *)pUser;
1170
1171 pThis->m_aClients[ClientId].m_AuthKey = -1;
1172 pThis->m_aClients[ClientId].m_pRconCmdToSend = nullptr;
1173 pThis->m_aClients[ClientId].m_MaplistEntryToSend = CClient::MAPLIST_UNINITIALIZED;
1174 pThis->m_aClients[ClientId].m_DDNetVersion = VERSION_NONE;
1175 pThis->m_aClients[ClientId].m_GotDDNetVersionPacket = false;
1176 pThis->m_aClients[ClientId].m_DDNetVersionSettled = false;
1177
1178 pThis->m_aClients[ClientId].Reset();
1179
1180 pThis->GameServer()->TeehistorianRecordPlayerRejoin(ClientId);
1181 pThis->Antibot()->OnEngineClientDrop(ClientId, pReason: "rejoin");
1182 pThis->Antibot()->OnEngineClientJoin(ClientId);
1183
1184 pThis->SendMap(ClientId);
1185
1186 return 0;
1187}
1188
1189int CServer::NewClientNoAuthCallback(int ClientId, void *pUser)
1190{
1191 CServer *pThis = (CServer *)pUser;
1192
1193 pThis->m_aClients[ClientId].m_DnsblState = EDnsblState::NONE;
1194
1195 pThis->m_aClients[ClientId].m_State = CClient::STATE_CONNECTING;
1196 pThis->m_aClients[ClientId].m_aName[0] = 0;
1197 pThis->m_aClients[ClientId].m_aClan[0] = 0;
1198 pThis->m_aClients[ClientId].m_Country = CountryCode::DEFAULT;
1199 pThis->m_aClients[ClientId].m_AuthKey = -1;
1200 pThis->m_aClients[ClientId].m_AuthTries = 0;
1201 pThis->m_aClients[ClientId].m_AuthHidden = false;
1202 pThis->m_aClients[ClientId].m_pRconCmdToSend = nullptr;
1203 pThis->m_aClients[ClientId].m_MaplistEntryToSend = CClient::MAPLIST_UNINITIALIZED;
1204 pThis->m_aClients[ClientId].m_ShowIps = false;
1205 pThis->m_aClients[ClientId].m_DebugDummy = false;
1206 pThis->m_aClients[ClientId].m_ForceHighBandwidthOnSpectate = false;
1207 pThis->m_aClients[ClientId].m_DDNetVersion = VERSION_NONE;
1208 pThis->m_aClients[ClientId].m_GotDDNetVersionPacket = false;
1209 pThis->m_aClients[ClientId].m_DDNetVersionSettled = false;
1210 pThis->m_aClients[ClientId].Reset();
1211
1212 pThis->GameServer()->TeehistorianRecordPlayerJoin(ClientId, Sixup: false);
1213 pThis->Antibot()->OnEngineClientJoin(ClientId);
1214
1215 pThis->SendCapabilities(ClientId);
1216 pThis->SendMap(ClientId);
1217#if defined(CONF_FAMILY_UNIX)
1218 pThis->SendConnLoggingCommand(Cmd: OPEN_SESSION, pAddr: pThis->ClientAddr(ClientId));
1219#endif
1220 return 0;
1221}
1222
1223int CServer::NewClientCallback(int ClientId, void *pUser, bool Sixup)
1224{
1225 CServer *pThis = (CServer *)pUser;
1226 pThis->m_aClients[ClientId].m_State = CClient::STATE_PREAUTH;
1227 pThis->m_aClients[ClientId].m_DnsblState = EDnsblState::NONE;
1228 pThis->m_aClients[ClientId].m_aName[0] = 0;
1229 pThis->m_aClients[ClientId].m_aClan[0] = 0;
1230 pThis->m_aClients[ClientId].m_Country = CountryCode::DEFAULT;
1231 pThis->m_aClients[ClientId].m_AuthKey = -1;
1232 pThis->m_aClients[ClientId].m_AuthTries = 0;
1233 pThis->m_aClients[ClientId].m_AuthHidden = false;
1234 pThis->m_aClients[ClientId].m_pRconCmdToSend = nullptr;
1235 pThis->m_aClients[ClientId].m_MaplistEntryToSend = CClient::MAPLIST_UNINITIALIZED;
1236 pThis->m_aClients[ClientId].m_Traffic = 0;
1237 pThis->m_aClients[ClientId].m_TrafficSince = 0;
1238 pThis->m_aClients[ClientId].m_ShowIps = false;
1239 pThis->m_aClients[ClientId].m_DebugDummy = false;
1240 pThis->m_aClients[ClientId].m_ForceHighBandwidthOnSpectate = false;
1241 pThis->m_aClients[ClientId].m_DDNetVersion = VERSION_NONE;
1242 pThis->m_aClients[ClientId].m_GotDDNetVersionPacket = false;
1243 pThis->m_aClients[ClientId].m_DDNetVersionSettled = false;
1244 pThis->m_aClients[ClientId].Reset();
1245 pThis->m_aClients[ClientId].m_Sixup = Sixup;
1246
1247 pThis->GameServer()->TeehistorianRecordPlayerJoin(ClientId, Sixup);
1248 pThis->Antibot()->OnEngineClientJoin(ClientId);
1249
1250#if defined(CONF_FAMILY_UNIX)
1251 pThis->SendConnLoggingCommand(Cmd: OPEN_SESSION, pAddr: pThis->ClientAddr(ClientId));
1252#endif
1253 return 0;
1254}
1255
1256void CServer::InitDnsbl(int ClientId)
1257{
1258 NETADDR Addr = *ClientAddr(ClientId);
1259
1260 //TODO: support ipv6
1261 if(Addr.type != NETTYPE_IPV4)
1262 return;
1263
1264 // build dnsbl host lookup
1265 char aBuf[256];
1266 if(Config()->m_SvDnsblKey[0] == '\0')
1267 {
1268 // without key
1269 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d.%d.%d.%d.%s", Addr.ip[3], Addr.ip[2], Addr.ip[1], Addr.ip[0], Config()->m_SvDnsblHost);
1270 }
1271 else
1272 {
1273 // with key
1274 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s.%d.%d.%d.%d.%s", Config()->m_SvDnsblKey, Addr.ip[3], Addr.ip[2], Addr.ip[1], Addr.ip[0], Config()->m_SvDnsblHost);
1275 }
1276
1277 m_aClients[ClientId].m_pDnsblLookup = std::make_shared<CHostLookup>(args&: aBuf, args: NETTYPE_IPV4);
1278 Engine()->AddJob(pJob: m_aClients[ClientId].m_pDnsblLookup);
1279 m_aClients[ClientId].m_DnsblState = EDnsblState::PENDING;
1280}
1281
1282#ifdef CONF_FAMILY_UNIX
1283void CServer::SendConnLoggingCommand(CONN_LOGGING_CMD Cmd, const NETADDR *pAddr)
1284{
1285 if(!Config()->m_SvConnLoggingServer[0] || !m_ConnLoggingSocketCreated)
1286 return;
1287
1288 // pack the data and send it
1289 unsigned char aData[23] = {0};
1290 aData[0] = Cmd;
1291 mem_copy(dest: &aData[1], source: &pAddr->type, size: 4);
1292 mem_copy(dest: &aData[5], source: pAddr->ip, size: 16);
1293 mem_copy(dest: &aData[21], source: &pAddr->port, size: 2);
1294
1295 net_unix_send(sock: m_ConnLoggingSocket, addr: &m_ConnLoggingDestAddr, data: aData, size: sizeof(aData));
1296}
1297#endif
1298
1299int CServer::DelClientCallback(int ClientId, const char *pReason, void *pUser)
1300{
1301 CServer *pThis = (CServer *)pUser;
1302
1303 char aBuf[256];
1304 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "client dropped. cid=%d addr=<{%s}> reason='%s'", ClientId, pThis->ClientAddrString(ClientId, IncludePort: true), pReason);
1305 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_ADDINFO, pFrom: "server", pStr: aBuf);
1306
1307#if defined(CONF_FAMILY_UNIX)
1308 // Make copy of address because the client slot will be empty at the end of the function
1309 const NETADDR Addr = *pThis->ClientAddr(ClientId);
1310#endif
1311
1312 // notify the mod about the drop
1313 if(pThis->m_aClients[ClientId].m_State >= CClient::STATE_READY)
1314 pThis->GameServer()->OnClientDrop(ClientId, pReason);
1315
1316 pThis->m_aClients[ClientId].m_State = CClient::STATE_EMPTY;
1317 pThis->m_aClients[ClientId].m_aName[0] = 0;
1318 pThis->m_aClients[ClientId].m_aClan[0] = 0;
1319 pThis->m_aClients[ClientId].m_Country = CountryCode::DEFAULT;
1320 pThis->m_aClients[ClientId].m_AuthKey = -1;
1321 pThis->m_aClients[ClientId].m_AuthTries = 0;
1322 pThis->m_aClients[ClientId].m_AuthHidden = false;
1323 pThis->m_aClients[ClientId].m_pRconCmdToSend = nullptr;
1324 pThis->m_aClients[ClientId].m_MaplistEntryToSend = CClient::MAPLIST_UNINITIALIZED;
1325 pThis->m_aClients[ClientId].m_Traffic = 0;
1326 pThis->m_aClients[ClientId].m_TrafficSince = 0;
1327 pThis->m_aClients[ClientId].m_ShowIps = false;
1328 pThis->m_aClients[ClientId].m_DebugDummy = false;
1329 pThis->m_aClients[ClientId].m_ForceHighBandwidthOnSpectate = false;
1330 pThis->m_aPrevStates[ClientId] = CClient::STATE_EMPTY;
1331 pThis->m_aClients[ClientId].m_Snapshots.PurgeAll();
1332 pThis->m_aClients[ClientId].m_Sixup = false;
1333 pThis->m_aClients[ClientId].m_RedirectDropTime = 0;
1334 pThis->m_aClients[ClientId].m_HasPersistentData = false;
1335
1336 pThis->GameServer()->TeehistorianRecordPlayerDrop(ClientId, pReason);
1337 pThis->Antibot()->OnEngineClientDrop(ClientId, pReason);
1338#if defined(CONF_FAMILY_UNIX)
1339 pThis->SendConnLoggingCommand(Cmd: CLOSE_SESSION, pAddr: &Addr);
1340#endif
1341 return 0;
1342}
1343
1344void CServer::SendRconType(int ClientId, bool UsernameReq)
1345{
1346 CMsgPacker Msg(NETMSG_RCONTYPE, true);
1347 Msg.AddInt(i: UsernameReq);
1348 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId);
1349}
1350
1351void CServer::SendCapabilities(int ClientId)
1352{
1353 CMsgPacker Msg(NETMSG_CAPABILITIES, true);
1354 Msg.AddInt(i: SERVERCAP_CURVERSION); // version
1355 Msg.AddInt(i: SERVERCAPFLAG_DDNET | SERVERCAPFLAG_CHATTIMEOUTCODE | SERVERCAPFLAG_ANYPLAYERFLAG | SERVERCAPFLAG_PINGEX | SERVERCAPFLAG_ALLOWDUMMY | SERVERCAPFLAG_SYNCWEAPONINPUT); // flags
1356 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId);
1357}
1358
1359void CServer::SendMap(int ClientId)
1360{
1361 int MapType = IsSixup(ClientId) ? MAP_TYPE_SIXUP : MAP_TYPE_SIX;
1362 {
1363 CMsgPacker Msg(NETMSG_MAP_DETAILS, true);
1364 Msg.AddString(pStr: GameServer()->Map()->BaseName(), Limit: 0);
1365 Msg.AddRaw(pData: &m_aCurrentMapSha256[MapType].data, Size: sizeof(m_aCurrentMapSha256[MapType].data));
1366 Msg.AddInt(i: m_aCurrentMapCrc[MapType]);
1367 Msg.AddInt(i: m_aCurrentMapSize[MapType]);
1368 if(m_aMapDownloadUrl[0])
1369 {
1370 Msg.AddString(pStr: m_aMapDownloadUrl, Limit: 0);
1371 }
1372 else
1373 {
1374 Msg.AddString(pStr: "", Limit: 0);
1375 }
1376 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId);
1377 }
1378 {
1379 CMsgPacker Msg(NETMSG_MAP_CHANGE, true);
1380 Msg.AddString(pStr: GameServer()->Map()->BaseName(), Limit: 0);
1381 Msg.AddInt(i: m_aCurrentMapCrc[MapType]);
1382 Msg.AddInt(i: m_aCurrentMapSize[MapType]);
1383 if(MapType == MAP_TYPE_SIXUP)
1384 {
1385 Msg.AddInt(i: Config()->m_SvMapWindow);
1386 Msg.AddInt(i: NET_MAX_CHUNK_SIZE - 128);
1387 Msg.AddRaw(pData: m_aCurrentMapSha256[MapType].data, Size: sizeof(m_aCurrentMapSha256[MapType].data));
1388 }
1389 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_FLUSH, ClientId);
1390 }
1391
1392 m_aClients[ClientId].m_NextMapChunk = 0;
1393}
1394
1395void CServer::SendMapData(int ClientId, int Chunk)
1396{
1397 int MapType = IsSixup(ClientId) ? MAP_TYPE_SIXUP : MAP_TYPE_SIX;
1398 unsigned int ChunkSize = NET_MAX_CHUNK_SIZE - 128;
1399 unsigned int Offset = Chunk * ChunkSize;
1400 int Last = 0;
1401
1402 // drop faulty map data requests
1403 if(Chunk < 0 || Offset > m_aCurrentMapSize[MapType])
1404 return;
1405
1406 if(Offset + ChunkSize >= m_aCurrentMapSize[MapType])
1407 {
1408 ChunkSize = m_aCurrentMapSize[MapType] - Offset;
1409 Last = 1;
1410 }
1411
1412 CMsgPacker Msg(NETMSG_MAP_DATA, true);
1413 if(MapType == MAP_TYPE_SIX)
1414 {
1415 Msg.AddInt(i: Last);
1416 Msg.AddInt(i: m_aCurrentMapCrc[MAP_TYPE_SIX]);
1417 Msg.AddInt(i: Chunk);
1418 Msg.AddInt(i: ChunkSize);
1419 }
1420 Msg.AddRaw(pData: &m_apCurrentMapData[MapType][Offset], Size: ChunkSize);
1421 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_FLUSH, ClientId);
1422
1423 if(Config()->m_Debug)
1424 {
1425 char aBuf[256];
1426 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "sending chunk %d with size %d", Chunk, ChunkSize);
1427 Console()->Print(Level: IConsole::OUTPUT_LEVEL_DEBUG, pFrom: "server", pStr: aBuf);
1428 }
1429}
1430
1431void CServer::SendMapReload(int ClientId)
1432{
1433 CMsgPacker Msg(NETMSG_MAP_RELOAD, true);
1434 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_FLUSH, ClientId);
1435}
1436
1437void CServer::SendConnectionReady(int ClientId)
1438{
1439 CMsgPacker Msg(NETMSG_CON_READY, true);
1440 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_FLUSH, ClientId);
1441}
1442
1443void CServer::SendRconLine(int ClientId, const char *pLine)
1444{
1445 CMsgPacker Msg(NETMSG_RCON_LINE, true);
1446 Msg.AddString(pStr: pLine, Limit: 512);
1447 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId);
1448}
1449
1450void CServer::SendRconLogLine(int ClientId, const CLogMessage *pMessage)
1451{
1452 char aLine[sizeof(CLogMessage().m_aLine)];
1453 char aLineWithoutIps[sizeof(CLogMessage().m_aLine)];
1454 StrHideIps(pInput: pMessage->m_aLine, pOutputWithIps: aLine, OutputWithIpsSize: sizeof(aLine), pOutputWithoutIps: aLineWithoutIps, OutputWithoutIpsSize: sizeof(aLineWithoutIps));
1455
1456 if(ClientId == -1)
1457 {
1458 for(int i = 0; i < MAX_CLIENTS; i++)
1459 {
1460 if(m_aClients[i].m_State != CClient::STATE_EMPTY && IsRconAuthedAdmin(ClientId: i))
1461 SendRconLine(ClientId: i, pLine: m_aClients[i].m_ShowIps ? aLine : aLineWithoutIps);
1462 }
1463 }
1464 else
1465 {
1466 if(m_aClients[ClientId].m_State != CClient::STATE_EMPTY)
1467 SendRconLine(ClientId, pLine: m_aClients[ClientId].m_ShowIps ? aLine : aLineWithoutIps);
1468 }
1469}
1470
1471void CServer::SendRconCmdAdd(const IConsole::ICommandInfo *pCommandInfo, int ClientId)
1472{
1473 CMsgPacker Msg(NETMSG_RCON_CMD_ADD, true);
1474 Msg.AddString(pStr: pCommandInfo->Name(), Limit: IConsole::TEMPCMD_NAME_LENGTH);
1475 Msg.AddString(pStr: pCommandInfo->Help(), Limit: IConsole::TEMPCMD_HELP_LENGTH);
1476 Msg.AddString(pStr: pCommandInfo->Params(), Limit: IConsole::TEMPCMD_PARAMS_LENGTH);
1477 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId);
1478}
1479
1480void CServer::SendRconCmdRem(const IConsole::ICommandInfo *pCommandInfo, int ClientId)
1481{
1482 CMsgPacker Msg(NETMSG_RCON_CMD_REM, true);
1483 Msg.AddString(pStr: pCommandInfo->Name(), Limit: IConsole::TEMPCMD_NAME_LENGTH);
1484 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId);
1485}
1486
1487void CServer::SendRconCmdGroupStart(int ClientId)
1488{
1489 CMsgPacker Msg(NETMSG_RCON_CMD_GROUP_START, true);
1490 Msg.AddInt(i: NumRconCommands(ClientId));
1491 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId);
1492}
1493
1494void CServer::SendRconCmdGroupEnd(int ClientId)
1495{
1496 CMsgPacker Msg(NETMSG_RCON_CMD_GROUP_END, true);
1497 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId);
1498}
1499
1500int CServer::NumRconCommands(int ClientId)
1501{
1502 int Num = 0;
1503 for(const IConsole::ICommandInfo *pCmd = Console()->FirstCommandInfo(ClientId, FlagMask: CFGFLAG_SERVER);
1504 pCmd; pCmd = Console()->NextCommandInfo(pInfo: pCmd, ClientId, FlagMask: CFGFLAG_SERVER))
1505 {
1506 Num++;
1507 }
1508 return Num;
1509}
1510
1511void CServer::UpdateClientRconCommands(int ClientId)
1512{
1513 CClient &Client = m_aClients[ClientId];
1514 if(Client.m_State != CClient::STATE_INGAME ||
1515 !IsRconAuthed(ClientId) ||
1516 Client.m_pRconCmdToSend == nullptr)
1517 {
1518 return;
1519 }
1520
1521 for(int i = 0; i < MAX_RCONCMD_SEND && Client.m_pRconCmdToSend; ++i)
1522 {
1523 SendRconCmdAdd(pCommandInfo: Client.m_pRconCmdToSend, ClientId);
1524 Client.m_pRconCmdToSend = Console()->NextCommandInfo(pInfo: Client.m_pRconCmdToSend, ClientId, FlagMask: CFGFLAG_SERVER);
1525 if(Client.m_pRconCmdToSend == nullptr)
1526 {
1527 SendRconCmdGroupEnd(ClientId);
1528 }
1529 }
1530}
1531
1532CServer::CMaplistEntry::CMaplistEntry(const char *pName)
1533{
1534 str_copy(dst&: m_aName, src: pName);
1535}
1536
1537bool CServer::CMaplistEntry::operator<(const CMaplistEntry &Other) const
1538{
1539 return str_comp_filenames(a: m_aName, b: Other.m_aName) < 0;
1540}
1541
1542void CServer::SendMaplistGroupStart(int ClientId)
1543{
1544 CMsgPacker Msg(NETMSG_MAPLIST_GROUP_START, true);
1545 Msg.AddInt(i: m_vMaplistEntries.size());
1546 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId);
1547}
1548
1549void CServer::SendMaplistGroupEnd(int ClientId)
1550{
1551 CMsgPacker Msg(NETMSG_MAPLIST_GROUP_END, true);
1552 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId);
1553}
1554
1555void CServer::UpdateClientMaplistEntries(int ClientId)
1556{
1557 CClient &Client = m_aClients[ClientId];
1558 if(Client.m_State != CClient::STATE_INGAME ||
1559 !IsRconAuthed(ClientId) ||
1560 Client.m_Sixup ||
1561 Client.m_pRconCmdToSend != nullptr || // wait for command sending
1562 Client.m_MaplistEntryToSend == CClient::MAPLIST_DISABLED ||
1563 Client.m_MaplistEntryToSend == CClient::MAPLIST_DONE)
1564 {
1565 return;
1566 }
1567
1568 if(Client.m_MaplistEntryToSend == CClient::MAPLIST_UNINITIALIZED)
1569 {
1570 static const char *const MAP_COMMANDS[] = {"sv_map", "change_map"};
1571 const IConsole::EAccessLevel AccessLevel = ConsoleAccessLevel(ClientId);
1572 const bool MapCommandAllowed = std::any_of(first: std::begin(arr: MAP_COMMANDS), last: std::end(arr: MAP_COMMANDS), pred: [&](const char *pMapCommand) {
1573 const IConsole::ICommandInfo *pInfo = Console()->GetCommandInfo(pName: pMapCommand, FlagMask: CFGFLAG_SERVER, Temp: false);
1574 dbg_assert(pInfo != nullptr, "Map command not found");
1575 return AccessLevel <= pInfo->GetAccessLevel();
1576 });
1577 if(MapCommandAllowed)
1578 {
1579 Client.m_MaplistEntryToSend = 0;
1580 SendMaplistGroupStart(ClientId);
1581 }
1582 else
1583 {
1584 Client.m_MaplistEntryToSend = CClient::MAPLIST_DISABLED;
1585 return;
1586 }
1587 }
1588
1589 if((size_t)Client.m_MaplistEntryToSend < m_vMaplistEntries.size())
1590 {
1591 CMsgPacker Msg(NETMSG_MAPLIST_ADD, true);
1592 int Limit = NET_MAX_CHUNK_SIZE - 128;
1593 while((size_t)Client.m_MaplistEntryToSend < m_vMaplistEntries.size())
1594 {
1595 // Space for null termination not included in Limit
1596 const int SizeBefore = Msg.Size();
1597 Msg.AddString(pStr: m_vMaplistEntries[Client.m_MaplistEntryToSend].m_aName, Limit: Limit - 1, AllowTruncation: false);
1598 if(Msg.Error())
1599 {
1600 break;
1601 }
1602 Limit -= Msg.Size() - SizeBefore;
1603 if(Limit <= 1)
1604 {
1605 break;
1606 }
1607 ++Client.m_MaplistEntryToSend;
1608 }
1609 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId);
1610 }
1611
1612 if((size_t)Client.m_MaplistEntryToSend >= m_vMaplistEntries.size())
1613 {
1614 SendMaplistGroupEnd(ClientId);
1615 Client.m_MaplistEntryToSend = CClient::MAPLIST_DONE;
1616 }
1617}
1618
1619static inline int MsgFromSixup(int Msg, bool System)
1620{
1621 if(System)
1622 {
1623 if(Msg == NETMSG_INFO)
1624 ;
1625 else if(Msg >= 14 && Msg <= 15)
1626 Msg += 11;
1627 else if(Msg >= 18 && Msg <= 28)
1628 Msg = NETMSG_READY + Msg - 18;
1629 else if(Msg < OFFSET_UUID)
1630 return -1;
1631 }
1632
1633 return Msg;
1634}
1635
1636bool CServer::CheckReservedSlotAuth(int ClientId, const char *pPassword)
1637{
1638 if(Config()->m_SvReservedSlotsPass[0] && !str_comp(a: Config()->m_SvReservedSlotsPass, b: pPassword))
1639 {
1640 log_info("server", "ClientId=%d joining reserved slot with reserved slots password", ClientId);
1641 return true;
1642 }
1643
1644 // "^([^:]*):(.*)$"
1645 if(Config()->m_SvReservedSlotsAuthLevel != 4)
1646 {
1647 char aName[sizeof(Config()->m_Password)];
1648 const char *pInnerPassword = str_next_token(str: pPassword, delim: ":", buffer: aName, buffer_size: sizeof(aName));
1649 if(!pInnerPassword)
1650 {
1651 return false;
1652 }
1653 int Slot = m_AuthManager.FindKey(pIdent: aName);
1654 if(m_AuthManager.CheckKey(Slot, pPw: pInnerPassword + 1) && m_AuthManager.KeyLevel(Slot) >= Config()->m_SvReservedSlotsAuthLevel)
1655 {
1656 log_info("server", "ClientId=%d joining reserved slot with key='%s'", ClientId, m_AuthManager.KeyIdent(Slot));
1657 return true;
1658 }
1659 }
1660
1661 return false;
1662}
1663
1664void CServer::ProcessClientPacket(CNetChunk *pPacket)
1665{
1666 int ClientId = pPacket->m_ClientId;
1667 CUnpacker Unpacker;
1668 Unpacker.Reset(pData: pPacket->m_pData, Size: pPacket->m_DataSize);
1669 CMsgPacker Packer(NETMSG_EX, true);
1670
1671 // unpack msgid and system flag
1672 int Msg;
1673 bool Sys;
1674 CUuid Uuid;
1675
1676 int Result = UnpackMessageId(pId: &Msg, pSys: &Sys, pUuid: &Uuid, pUnpacker: &Unpacker, pPacker: &Packer);
1677 if(Result == UNPACKMESSAGE_ERROR)
1678 {
1679 return;
1680 }
1681
1682 if(m_aClients[ClientId].m_Sixup && (Msg = MsgFromSixup(Msg, System: Sys)) < 0)
1683 {
1684 return;
1685 }
1686
1687 if(Config()->m_SvNetlimit && Msg != NETMSG_REQUEST_MAP_DATA)
1688 {
1689 int64_t Now = time_get();
1690 int64_t Diff = Now - m_aClients[ClientId].m_TrafficSince;
1691 double Alpha = Config()->m_SvNetlimitAlpha / 100.0;
1692 double Limit = (double)(Config()->m_SvNetlimit * 1024) / time_freq();
1693
1694 if(m_aClients[ClientId].m_Traffic > Limit)
1695 {
1696 m_NetServer.NetBan()->BanAddr(pAddr: &pPacket->m_Address, Seconds: 600, pReason: "Stressing network", VerbatimReason: false);
1697 return;
1698 }
1699 if(Diff > 100)
1700 {
1701 m_aClients[ClientId].m_Traffic = (Alpha * ((double)pPacket->m_DataSize / Diff)) + (1.0 - Alpha) * m_aClients[ClientId].m_Traffic;
1702 m_aClients[ClientId].m_TrafficSince = Now;
1703 }
1704 }
1705
1706 if(Result == UNPACKMESSAGE_ANSWER)
1707 {
1708 SendMsg(pMsg: &Packer, Flags: MSGFLAG_VITAL, ClientId);
1709 }
1710
1711 {
1712 bool VitalFlag = (pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0;
1713 bool NonVitalMsg = Sys && (Msg == NETMSG_INPUT || Msg == NETMSG_PING || Msg == NETMSG_PINGEX);
1714 if(!VitalFlag && !NonVitalMsg)
1715 {
1716 if(g_Config.m_Debug)
1717 {
1718 log_debug(
1719 "server",
1720 "strange message ClientId=%d msg=%d data_size=%d (missing vital flag)",
1721 ClientId,
1722 Msg,
1723 pPacket->m_DataSize);
1724 }
1725 return;
1726 }
1727 }
1728
1729 if(Sys)
1730 {
1731 // system message
1732 if(Msg == NETMSG_CLIENTVER)
1733 {
1734 CUuid *pConnectionId = (CUuid *)Unpacker.GetRaw(Size: sizeof(*pConnectionId));
1735 int DDNetVersion = Unpacker.GetInt();
1736 const char *pDDNetVersionStr = Unpacker.GetString(SanitizeType: CUnpacker::SANITIZE_CC);
1737 if(Unpacker.Error())
1738 return;
1739
1740 OnNetMsgClientVer(ClientId, pConnectionId, DDNetVersion, pDDNetVersionStr);
1741 }
1742 else if(Msg == NETMSG_INFO)
1743 {
1744 const char *pVersion = Unpacker.GetString(SanitizeType: CUnpacker::SANITIZE_CC);
1745 if(Unpacker.Error())
1746 return;
1747 const char *pPassword = Unpacker.GetString(SanitizeType: CUnpacker::SANITIZE_CC);
1748 if(Unpacker.Error())
1749 pPassword = nullptr;
1750
1751 OnNetMsgInfo(ClientId, pVersion, pPasswordOrNullptr: pPassword);
1752 }
1753 else if(Msg == NETMSG_REQUEST_MAP_DATA)
1754 {
1755 if(m_aClients[ClientId].m_State < CClient::STATE_CONNECTING)
1756 return;
1757
1758 if(m_aClients[ClientId].m_Sixup)
1759 {
1760 for(int i = 0; i < Config()->m_SvMapWindow; i++)
1761 {
1762 SendMapData(ClientId, Chunk: m_aClients[ClientId].m_NextMapChunk++);
1763 }
1764 return;
1765 }
1766
1767 int Chunk = Unpacker.GetInt();
1768 if(Unpacker.Error())
1769 {
1770 return;
1771 }
1772 if(Chunk != m_aClients[ClientId].m_NextMapChunk || !Config()->m_SvFastDownload)
1773 {
1774 SendMapData(ClientId, Chunk);
1775 return;
1776 }
1777
1778 if(Chunk == 0)
1779 {
1780 for(int i = 0; i < Config()->m_SvMapWindow; i++)
1781 {
1782 SendMapData(ClientId, Chunk: i);
1783 }
1784 }
1785 SendMapData(ClientId, Chunk: Config()->m_SvMapWindow + m_aClients[ClientId].m_NextMapChunk);
1786 m_aClients[ClientId].m_NextMapChunk++;
1787 }
1788 else if(Msg == NETMSG_READY)
1789 {
1790 OnNetMsgReady(ClientId);
1791 }
1792 else if(Msg == NETMSG_ENTERGAME)
1793 {
1794 OnNetMsgEnterGame(ClientId);
1795 }
1796 else if(Msg == NETMSG_INPUT)
1797 {
1798 if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0)
1799 {
1800 return;
1801 }
1802 if(m_aClients[ClientId].m_State != CClient::STATE_INGAME)
1803 {
1804 return;
1805 }
1806
1807 const int LastAckedSnapshot = Unpacker.GetInt();
1808 if(Unpacker.Error() ||
1809 LastAckedSnapshot < -1 ||
1810 LastAckedSnapshot > Tick())
1811 {
1812 return;
1813 }
1814
1815 int IntendedTick = Unpacker.GetInt();
1816 if(Unpacker.Error() ||
1817 IntendedTick < MIN_TICK ||
1818 IntendedTick > MAX_TICK)
1819 {
1820 return;
1821 }
1822
1823 const int Size = Unpacker.GetInt();
1824 if(Unpacker.Error() ||
1825 Size % (int)sizeof(int32_t) != 0 ||
1826 Size / (int)sizeof(int32_t) < MIN_INPUT_SIZE ||
1827 Size / (int)sizeof(int32_t) > MAX_INPUT_SIZE)
1828 {
1829 return;
1830 }
1831
1832 m_aClients[ClientId].m_LastAckedSnapshot = LastAckedSnapshot;
1833 if(m_aClients[ClientId].m_LastAckedSnapshot >= MIN_TICK)
1834 {
1835 m_aClients[ClientId].m_SnapRate = CClient::SNAPRATE_FULL;
1836
1837 int64_t TagTime;
1838 if(m_aClients[ClientId].m_Snapshots.Get(Tick: m_aClients[ClientId].m_LastAckedSnapshot, pTagtime: &TagTime, ppData: nullptr, ppAltData: nullptr) >= 0)
1839 {
1840 m_aClients[ClientId].m_Latency = (int)(((time_get() - TagTime) * 1000) / time_freq());
1841 }
1842 }
1843
1844 // add message to report the input timing
1845 // skip packets that are old
1846 if(IntendedTick > m_aClients[ClientId].m_LastInputTick)
1847 {
1848 const int TimeLeft = (TickStartTime(Tick: IntendedTick) - time_get()) / (time_freq() / 1000);
1849
1850 CMsgPacker Msgp(NETMSG_INPUTTIMING, true);
1851 Msgp.AddInt(i: IntendedTick);
1852 Msgp.AddInt(i: TimeLeft);
1853 SendMsg(pMsg: &Msgp, Flags: 0, ClientId);
1854 }
1855 m_aClients[ClientId].m_LastInputTick = IntendedTick;
1856
1857 IntendedTick = std::max(a: IntendedTick, b: Tick() + 1);
1858
1859 CClient::CInput *pInput = &m_aClients[ClientId].m_aInputs[m_aClients[ClientId].m_CurrentInput];
1860 pInput->m_GameTick = IntendedTick;
1861 for(int i = 0; i < Size / (int)sizeof(int32_t); i++)
1862 {
1863 pInput->m_aData[i] = Unpacker.GetInt();
1864 }
1865 if(Unpacker.Error())
1866 {
1867 return;
1868 }
1869
1870 if(g_Config.m_SvPreInput &&
1871 IntendedTick <= Tick() + 4 * TickSpeed() + 1)
1872 {
1873 // send preinputs of ClientId to valid clients
1874 bool aPreInputClients[MAX_CLIENTS] = {};
1875 GameServer()->PreInputClients(ClientId, pClients: aPreInputClients);
1876
1877 CNetMsg_Sv_PreInput PreInput = {};
1878 mem_zero(block: &PreInput, size: sizeof(PreInput));
1879 CNetObj_PlayerInput *pInputData = (CNetObj_PlayerInput *)&pInput->m_aData;
1880
1881 PreInput.m_Direction = pInputData->m_Direction;
1882 PreInput.m_Jump = pInputData->m_Jump;
1883 PreInput.m_Fire = pInputData->m_Fire;
1884 PreInput.m_Hook = pInputData->m_Hook;
1885 PreInput.m_WantedWeapon = pInputData->m_WantedWeapon;
1886 PreInput.m_NextWeapon = pInputData->m_NextWeapon;
1887 PreInput.m_PrevWeapon = pInputData->m_PrevWeapon;
1888
1889 if(mem_comp(a: &m_aClients[ClientId].m_LastPreInput, b: &PreInput, size: sizeof(CNetMsg_Sv_PreInput)) != 0)
1890 {
1891 m_aClients[ClientId].m_LastPreInput = PreInput;
1892
1893 PreInput.m_Owner = ClientId;
1894 PreInput.m_IntendedTick = IntendedTick;
1895
1896 // target angle isn't updated all the time to save bandwidth
1897 PreInput.m_TargetX = pInputData->m_TargetX;
1898 PreInput.m_TargetY = pInputData->m_TargetY;
1899
1900 for(int Id = 0; Id < MAX_CLIENTS; Id++)
1901 {
1902 if(!aPreInputClients[Id])
1903 continue;
1904 if(m_aClients[Id].m_SnapRate != CClient::SNAPRATE_FULL)
1905 continue;
1906
1907 if(!Translate(Target&: PreInput.m_Owner, ClientId: Id))
1908 continue;
1909
1910 SendPackMsg(pMsg: &PreInput, Flags: MSGFLAG_FLUSH | MSGFLAG_NORECORD, ClientId: Id);
1911 // Reset for others after translating and sending
1912 PreInput.m_Owner = ClientId;
1913 }
1914 }
1915 }
1916
1917 GameServer()->OnClientPrepareInput(ClientId, pInput: pInput->m_aData);
1918 mem_copy(dest: m_aClients[ClientId].m_LatestInput.m_aData, source: pInput->m_aData, size: sizeof(m_aClients[ClientId].m_LatestInput.m_aData));
1919
1920 m_aClients[ClientId].m_CurrentInput++;
1921 m_aClients[ClientId].m_CurrentInput %= 200;
1922
1923 // call the mod with the fresh input data
1924 GameServer()->OnClientDirectInput(ClientId, pInput: m_aClients[ClientId].m_LatestInput.m_aData);
1925 }
1926 else if(Msg == NETMSG_RCON_CMD)
1927 {
1928 const char *pCmd = Unpacker.GetString();
1929 if(Unpacker.Error())
1930 return;
1931
1932 OnNetMsgRconCmd(ClientId, pCmd);
1933 }
1934 else if(Msg == NETMSG_RCON_AUTH)
1935 {
1936 const char *pName = "";
1937 if(!IsSixup(ClientId))
1938 pName = Unpacker.GetString(SanitizeType: CUnpacker::SANITIZE_CC); // login name, now used
1939 const char *pPw = Unpacker.GetString(SanitizeType: CUnpacker::SANITIZE_CC);
1940 bool SendRconCmds = true;
1941 if(!IsSixup(ClientId))
1942 SendRconCmds = Unpacker.GetInt() != 0;
1943 if(Unpacker.Error())
1944 return;
1945
1946 OnNetMsgRconAuth(ClientId, pName, pPw, SendRconCmds);
1947 }
1948 else if(Msg == NETMSG_PING)
1949 {
1950 CMsgPacker Msgp(NETMSG_PING_REPLY, true);
1951 int Vital = (pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 ? MSGFLAG_VITAL : 0;
1952 SendMsg(pMsg: &Msgp, Flags: MSGFLAG_FLUSH | Vital, ClientId);
1953 }
1954 else if(Msg == NETMSG_PINGEX)
1955 {
1956 CUuid *pId = (CUuid *)Unpacker.GetRaw(Size: sizeof(*pId));
1957 if(Unpacker.Error())
1958 {
1959 return;
1960 }
1961 CMsgPacker Msgp(NETMSG_PONGEX, true);
1962 Msgp.AddRaw(pData: pId, Size: sizeof(*pId));
1963 int Vital = (pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 ? MSGFLAG_VITAL : 0;
1964 SendMsg(pMsg: &Msgp, Flags: MSGFLAG_FLUSH | Vital, ClientId);
1965 }
1966 else
1967 {
1968 if(Config()->m_Debug)
1969 {
1970 constexpr int MaxDumpedDataSize = 32;
1971 char aBuf[MaxDumpedDataSize * 3 + 1];
1972 str_hex(dst: aBuf, dst_size: sizeof(aBuf), data: pPacket->m_pData, data_size: std::min(a: pPacket->m_DataSize, b: MaxDumpedDataSize));
1973
1974 char aBufMsg[256];
1975 str_format(buffer: aBufMsg, buffer_size: sizeof(aBufMsg), format: "strange message ClientId=%d msg=%d data_size=%d", ClientId, Msg, pPacket->m_DataSize);
1976 Console()->Print(Level: IConsole::OUTPUT_LEVEL_DEBUG, pFrom: "server", pStr: aBufMsg);
1977 Console()->Print(Level: IConsole::OUTPUT_LEVEL_DEBUG, pFrom: "server", pStr: aBuf);
1978 }
1979 }
1980 }
1981 else if(m_aClients[ClientId].m_State >= CClient::STATE_READY)
1982 {
1983 // game message
1984 GameServer()->OnMessage(MsgId: Msg, pUnpacker: &Unpacker, ClientId);
1985 }
1986}
1987
1988void CServer::OnNetMsgClientVer(int ClientId, CUuid *pConnectionId, int DDNetVersion, const char *pDDNetVersionStr)
1989{
1990 if(m_aClients[ClientId].m_State != CClient::STATE_PREAUTH)
1991 return;
1992 if(DDNetVersion < 0)
1993 return;
1994
1995 m_aClients[ClientId].m_ConnectionId = *pConnectionId;
1996 m_aClients[ClientId].m_DDNetVersion = DDNetVersion;
1997 str_copy(dst&: m_aClients[ClientId].m_aDDNetVersionStr, src: pDDNetVersionStr);
1998 m_aClients[ClientId].m_DDNetVersionSettled = true;
1999 m_aClients[ClientId].m_GotDDNetVersionPacket = true;
2000 m_aClients[ClientId].m_State = CClient::STATE_AUTH;
2001}
2002
2003void CServer::OnNetMsgInfo(int ClientId, const char *pVersion, const char *pPasswordOrNullptr)
2004{
2005 if((m_aClients[ClientId].m_State != CClient::STATE_PREAUTH && m_aClients[ClientId].m_State != CClient::STATE_AUTH))
2006 return;
2007
2008 if(str_comp(a: pVersion, b: GameServer()->NetVersion()) != 0 && str_comp(a: pVersion, b: "0.7 802f1be60a05665f") != 0)
2009 {
2010 // wrong version
2011 char aReason[256];
2012 str_format(buffer: aReason, buffer_size: sizeof(aReason), format: "Wrong version. Server is running '%s' and client '%s'", GameServer()->NetVersion(), pVersion);
2013 m_NetServer.Drop(ClientId, pReason: aReason);
2014 return;
2015 }
2016
2017 const char *pPassword = pPasswordOrNullptr;
2018 if(!pPassword)
2019 return;
2020
2021 if(Config()->m_Password[0] != 0 && str_comp(a: Config()->m_Password, b: pPassword) != 0)
2022 {
2023 // wrong password
2024 m_NetServer.Drop(ClientId, pReason: "Wrong password");
2025 return;
2026 }
2027
2028 int NumConnectedClients = 0;
2029 for(int i = 0; i < MaxClients(); ++i)
2030 {
2031 if(m_aClients[i].m_State != CClient::STATE_EMPTY)
2032 {
2033 NumConnectedClients++;
2034 }
2035 }
2036
2037 // reserved slot
2038 if(NumConnectedClients > MaxClients() - Config()->m_SvReservedSlots && !CheckReservedSlotAuth(ClientId, pPassword))
2039 {
2040 m_NetServer.Drop(ClientId, pReason: "This server is full");
2041 return;
2042 }
2043
2044 m_aClients[ClientId].m_State = CClient::STATE_CONNECTING;
2045 SendRconType(ClientId, UsernameReq: m_AuthManager.NumNonDefaultKeys() > 0);
2046 SendCapabilities(ClientId);
2047 SendMap(ClientId);
2048}
2049
2050void CServer::OnNetMsgReady(int ClientId)
2051{
2052 if(m_aClients[ClientId].m_State == CClient::STATE_CONNECTING)
2053 {
2054 log_debug(
2055 "server",
2056 "player is ready. ClientId=%d addr=<{%s}> secure=%s",
2057 ClientId,
2058 ClientAddrString(ClientId, true),
2059 m_NetServer.HasSecurityToken(ClientId) ? "yes" : "no");
2060
2061 void *pPersistentData = nullptr;
2062 if(m_aClients[ClientId].m_HasPersistentData)
2063 {
2064 pPersistentData = m_aClients[ClientId].m_pPersistentData;
2065 m_aClients[ClientId].m_HasPersistentData = false;
2066 }
2067 m_aClients[ClientId].m_State = CClient::STATE_READY;
2068 GameServer()->OnClientConnected(ClientId, pPersistentData);
2069 }
2070
2071 // Make rejoining session possible before timeout protection triggers
2072 // https://github.com/ddnet/ddnet/pull/301
2073 SendConnectionReady(ClientId);
2074}
2075
2076void CServer::OnNetMsgEnterGame(int ClientId)
2077{
2078 if(m_aClients[ClientId].m_State != CClient::STATE_READY)
2079 return;
2080 if(!GameServer()->IsClientReady(ClientId))
2081 return;
2082
2083 log_info(
2084 "server",
2085 "player has entered the game. ClientId=%d addr=<{%s}> sixup=%d",
2086 ClientId,
2087 ClientAddrString(ClientId, true),
2088 IsSixup(ClientId));
2089 m_aClients[ClientId].m_State = CClient::STATE_INGAME;
2090 if(!IsSixup(ClientId))
2091 {
2092 SendServerInfo(pAddr: ClientAddr(ClientId), Token: -1, Type: SERVERINFO_EXTENDED, SendClients: false);
2093 }
2094 else
2095 {
2096 CMsgPacker ServerInfoMessage(protocol7::NETMSG_SERVERINFO, true, true);
2097 GetServerInfoSixup(pPacker: &ServerInfoMessage, SendClients: false);
2098 SendMsg(pMsg: &ServerInfoMessage, Flags: MSGFLAG_VITAL | MSGFLAG_FLUSH, ClientId);
2099 }
2100 GameServer()->OnClientEnter(ClientId);
2101}
2102
2103void CServer::OnNetMsgRconCmd(int ClientId, const char *pCmd)
2104{
2105 if(!str_comp(a: pCmd, b: "crashmeplx"))
2106 {
2107 int Version = m_aClients[ClientId].m_DDNetVersion;
2108 if(GameServer()->PlayerExists(ClientId) && Version < VERSION_DDNET_OLD)
2109 {
2110 m_aClients[ClientId].m_DDNetVersion = VERSION_DDNET_OLD;
2111 }
2112 }
2113 else if(IsRconAuthed(ClientId))
2114 {
2115 if(GameServer()->PlayerExists(ClientId))
2116 {
2117 log_info("server", "ClientId=%d key='%s' rcon='%s'", ClientId, GetAuthName(ClientId), pCmd);
2118 m_RconClientId = ClientId;
2119 m_RconAuthLevel = GetAuthedState(ClientId);
2120 {
2121 CRconClientLogger Logger(this, ClientId);
2122 CLogScope Scope(&Logger);
2123 Console()->ExecuteLineFlag(pStr: pCmd, FlasgMask: CFGFLAG_SERVER, ClientId);
2124 }
2125 m_RconClientId = IServer::RCON_CID_SERV;
2126 m_RconAuthLevel = AUTHED_ADMIN;
2127 }
2128 }
2129}
2130
2131void CServer::OnNetMsgRconAuth(int ClientId, const char *pName, const char *pPw, bool SendRconCmds)
2132{
2133 int AuthLevel = -1;
2134 int KeySlot = -1;
2135
2136 if(!pName[0])
2137 {
2138 if(m_AuthManager.CheckKey(Slot: (KeySlot = m_AuthManager.DefaultKey(pRoleName: RoleName::ADMIN)), pPw))
2139 AuthLevel = AUTHED_ADMIN;
2140 else if(m_AuthManager.CheckKey(Slot: (KeySlot = m_AuthManager.DefaultKey(pRoleName: RoleName::MODERATOR)), pPw))
2141 AuthLevel = AUTHED_MOD;
2142 else if(m_AuthManager.CheckKey(Slot: (KeySlot = m_AuthManager.DefaultKey(pRoleName: RoleName::HELPER)), pPw))
2143 AuthLevel = AUTHED_HELPER;
2144 }
2145 else
2146 {
2147 KeySlot = m_AuthManager.FindKey(pIdent: pName);
2148 if(m_AuthManager.CheckKey(Slot: KeySlot, pPw))
2149 AuthLevel = m_AuthManager.KeyLevel(Slot: KeySlot);
2150 }
2151
2152 if(AuthLevel != -1)
2153 {
2154 if(GetAuthedState(ClientId) != AuthLevel)
2155 {
2156 if(!IsSixup(ClientId))
2157 {
2158 CMsgPacker Msgp(NETMSG_RCON_AUTH_STATUS, true);
2159 Msgp.AddInt(i: 1); //authed
2160 Msgp.AddInt(i: 1); //cmdlist
2161 SendMsg(pMsg: &Msgp, Flags: MSGFLAG_VITAL, ClientId);
2162 }
2163 else
2164 {
2165 CMsgPacker Msgp(protocol7::NETMSG_RCON_AUTH_ON, true, true);
2166 SendMsg(pMsg: &Msgp, Flags: MSGFLAG_VITAL, ClientId);
2167 }
2168
2169 m_aClients[ClientId].m_AuthKey = KeySlot;
2170 if(SendRconCmds)
2171 {
2172 m_aClients[ClientId].m_pRconCmdToSend = Console()->FirstCommandInfo(ClientId, FlagMask: CFGFLAG_SERVER);
2173 SendRconCmdGroupStart(ClientId);
2174 if(m_aClients[ClientId].m_pRconCmdToSend == nullptr)
2175 {
2176 SendRconCmdGroupEnd(ClientId);
2177 }
2178 }
2179
2180 const char *pIdent = m_AuthManager.KeyIdent(Slot: KeySlot);
2181 switch(AuthLevel)
2182 {
2183 case AUTHED_ADMIN:
2184 {
2185 SendRconLine(ClientId, pLine: "Admin authentication successful. Full remote console access granted.");
2186 log_info("server", "ClientId=%d authed with key='%s' (admin)", ClientId, pIdent);
2187 break;
2188 }
2189 case AUTHED_MOD:
2190 {
2191 SendRconLine(ClientId, pLine: "Moderator authentication successful. Limited remote console access granted.");
2192 log_info("server", "ClientId=%d authed with key='%s' (moderator)", ClientId, pIdent);
2193 break;
2194 }
2195 case AUTHED_HELPER:
2196 {
2197 SendRconLine(ClientId, pLine: "Helper authentication successful. Limited remote console access granted.");
2198 log_info("server", "ClientId=%d authed with key='%s' (helper)", ClientId, pIdent);
2199 break;
2200 }
2201 }
2202
2203 // DDRace
2204 GameServer()->OnSetAuthed(ClientId, Level: AuthLevel);
2205 }
2206 }
2207 else if(Config()->m_SvRconMaxTries)
2208 {
2209 m_aClients[ClientId].m_AuthTries++;
2210 char aBuf[128];
2211 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Wrong password %d/%d.", m_aClients[ClientId].m_AuthTries, Config()->m_SvRconMaxTries);
2212 SendRconLine(ClientId, pLine: aBuf);
2213 if(m_aClients[ClientId].m_AuthTries >= Config()->m_SvRconMaxTries)
2214 {
2215 if(!Config()->m_SvRconBantime)
2216 m_NetServer.Drop(ClientId, pReason: "Too many remote console authentication tries");
2217 else
2218 m_ServerBan.BanAddr(pAddr: ClientAddr(ClientId), Seconds: Config()->m_SvRconBantime * 60, pReason: "Too many remote console authentication tries", VerbatimReason: false);
2219 }
2220 }
2221 else
2222 {
2223 SendRconLine(ClientId, pLine: "Wrong password.");
2224 }
2225}
2226
2227bool CServer::RateLimitServerInfoConnless()
2228{
2229 bool SendClients = true;
2230 if(Config()->m_SvServerInfoPerSecond)
2231 {
2232 SendClients = m_ServerInfoNumRequests <= Config()->m_SvServerInfoPerSecond;
2233 const int64_t Now = Tick();
2234
2235 if(Now <= m_ServerInfoFirstRequest + TickSpeed())
2236 {
2237 m_ServerInfoNumRequests++;
2238 }
2239 else
2240 {
2241 m_ServerInfoNumRequests = 1;
2242 m_ServerInfoFirstRequest = Now;
2243 }
2244 }
2245
2246 return SendClients;
2247}
2248
2249void CServer::SendServerInfoConnless(const NETADDR *pAddr, int Token, int Type)
2250{
2251 SendServerInfo(pAddr, Token, Type, SendClients: RateLimitServerInfoConnless());
2252}
2253
2254static inline int GetCacheIndex(int Type, bool SendClient)
2255{
2256 if(Type == SERVERINFO_INGAME)
2257 Type = SERVERINFO_VANILLA;
2258 else if(Type == SERVERINFO_EXTENDED_MORE)
2259 Type = SERVERINFO_EXTENDED;
2260
2261 return Type * 2 + SendClient;
2262}
2263
2264CServer::CCache::CCache()
2265{
2266 m_vCache.clear();
2267}
2268
2269CServer::CCache::~CCache()
2270{
2271 Clear();
2272}
2273
2274CServer::CCache::CCacheChunk::CCacheChunk(const void *pData, int Size)
2275{
2276 m_vData.assign(first: (const uint8_t *)pData, last: (const uint8_t *)pData + Size);
2277}
2278
2279void CServer::CCache::AddChunk(const void *pData, int Size)
2280{
2281 m_vCache.emplace_back(args&: pData, args&: Size);
2282}
2283
2284void CServer::CCache::Clear()
2285{
2286 m_vCache.clear();
2287}
2288
2289void CServer::CacheServerInfo(CCache *pCache, int Type, bool SendClients)
2290{
2291 pCache->Clear();
2292
2293 // One chance to improve the protocol!
2294 CPacker p;
2295 char aBuf[128];
2296
2297 // count the players
2298 int PlayerCount = 0, ClientCount = 0;
2299 for(int i = 0; i < MAX_CLIENTS; i++)
2300 {
2301 if(m_aClients[i].IncludedInServerInfo())
2302 {
2303 if(GameServer()->IsClientPlayer(ClientId: i))
2304 PlayerCount++;
2305
2306 ClientCount++;
2307 }
2308 }
2309
2310 p.Reset();
2311
2312#define ADD_RAW(p, x) (p).AddRaw(x, sizeof(x))
2313#define ADD_INT(p, x) \
2314 do \
2315 { \
2316 str_format(aBuf, sizeof(aBuf), "%d", x); \
2317 (p).AddString(aBuf, 0); \
2318 } while(0)
2319
2320 p.AddString(pStr: GameServer()->Version(), Limit: 32);
2321 if(Type != SERVERINFO_VANILLA)
2322 {
2323 p.AddString(pStr: Config()->m_SvName, Limit: 256);
2324 }
2325 else
2326 {
2327 if(m_NetServer.MaxClients() <= VANILLA_MAX_CLIENTS)
2328 {
2329 p.AddString(pStr: Config()->m_SvName, Limit: 64);
2330 }
2331 else
2332 {
2333 const int MaxClients = std::max(a: ClientCount, b: m_NetServer.MaxClients() - Config()->m_SvReservedSlots);
2334 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s [%d/%d]", Config()->m_SvName, ClientCount, MaxClients);
2335 p.AddString(pStr: aBuf, Limit: 64);
2336 }
2337 }
2338 p.AddString(pStr: GameServer()->Map()->BaseName(), Limit: 32);
2339
2340 if(Type == SERVERINFO_EXTENDED)
2341 {
2342 ADD_INT(p, m_aCurrentMapCrc[MAP_TYPE_SIX]);
2343 ADD_INT(p, m_aCurrentMapSize[MAP_TYPE_SIX]);
2344 }
2345
2346 // gametype
2347 p.AddString(pStr: GameServer()->GameType(), Limit: 16);
2348
2349 // flags
2350 ADD_INT(p, Config()->m_Password[0] ? SERVER_FLAG_PASSWORD : 0);
2351
2352 int MaxClients = m_NetServer.MaxClients();
2353 // How many clients the used serverinfo protocol supports, has to be tracked
2354 // separately to make sure we don't subtract the reserved slots from it
2355 int MaxClientsProtocol = MAX_CLIENTS;
2356 if(Type == SERVERINFO_VANILLA || Type == SERVERINFO_INGAME)
2357 {
2358 if(ClientCount >= VANILLA_MAX_CLIENTS)
2359 {
2360 if(ClientCount < MaxClients)
2361 ClientCount = VANILLA_MAX_CLIENTS - 1;
2362 else
2363 ClientCount = VANILLA_MAX_CLIENTS;
2364 }
2365 MaxClientsProtocol = VANILLA_MAX_CLIENTS;
2366 if(PlayerCount > ClientCount)
2367 PlayerCount = ClientCount;
2368 }
2369
2370 ADD_INT(p, PlayerCount); // num players
2371 ADD_INT(p, std::min(MaxClientsProtocol, std::max(MaxClients - std::max(Config()->m_SvSpectatorSlots, Config()->m_SvReservedSlots), PlayerCount))); // max players
2372 ADD_INT(p, ClientCount); // num clients
2373 ADD_INT(p, std::min(MaxClientsProtocol, std::max(MaxClients - Config()->m_SvReservedSlots, ClientCount))); // max clients
2374
2375 if(Type == SERVERINFO_EXTENDED)
2376 p.AddString(pStr: "", Limit: 0); // extra info, reserved
2377
2378 const void *pPrefix = p.Data();
2379 int PrefixSize = p.Size();
2380
2381 CPacker q;
2382 int ChunksStored = 0;
2383 int PlayersStored = 0;
2384
2385#define SAVE(size) \
2386 do \
2387 { \
2388 pCache->AddChunk(q.Data(), size); \
2389 ChunksStored++; \
2390 } while(0)
2391
2392#define RESET() \
2393 do \
2394 { \
2395 q.Reset(); \
2396 q.AddRaw(pPrefix, PrefixSize); \
2397 } while(0)
2398
2399 RESET();
2400
2401 if(Type == SERVERINFO_64_LEGACY)
2402 q.AddInt(i: PlayersStored); // offset
2403
2404 if(!SendClients)
2405 {
2406 SAVE(q.Size());
2407 return;
2408 }
2409
2410 if(Type == SERVERINFO_EXTENDED)
2411 {
2412 pPrefix = "";
2413 PrefixSize = 0;
2414 }
2415
2416 int Remaining;
2417 switch(Type)
2418 {
2419 case SERVERINFO_EXTENDED: Remaining = -1; break;
2420 case SERVERINFO_64_LEGACY: Remaining = 24; break;
2421 case SERVERINFO_VANILLA: Remaining = VANILLA_MAX_CLIENTS; break;
2422 case SERVERINFO_INGAME: Remaining = VANILLA_MAX_CLIENTS; break;
2423 default: dbg_assert_failed("Invalid Type: %d", Type);
2424 }
2425
2426 // Use the following strategy for sending:
2427 // For vanilla, send the first 16 players.
2428 // For legacy 64p, send 24 players per packet.
2429 // For extended, send as much players as possible.
2430
2431 for(int i = 0; i < MAX_CLIENTS; i++)
2432 {
2433 if(m_aClients[i].IncludedInServerInfo())
2434 {
2435 if(Remaining == 0)
2436 {
2437 if(Type == SERVERINFO_VANILLA || Type == SERVERINFO_INGAME)
2438 break;
2439
2440 // Otherwise we're SERVERINFO_64_LEGACY.
2441 SAVE(q.Size());
2442 RESET();
2443 q.AddInt(i: PlayersStored); // offset
2444 Remaining = 24;
2445 }
2446 if(Remaining > 0)
2447 {
2448 Remaining--;
2449 }
2450
2451 int PreviousSize = q.Size();
2452
2453 q.AddString(pStr: ClientName(ClientId: i), Limit: MAX_NAME_LENGTH); // client name
2454 q.AddString(pStr: ClientClan(ClientId: i), Limit: MAX_CLAN_LENGTH); // client clan
2455
2456 ADD_INT(q, m_aClients[i].m_Country); // client country (ISO 3166-1 numeric)
2457
2458 int Score;
2459 if(m_aClients[i].m_Score.has_value())
2460 {
2461 Score = m_aClients[i].m_Score.value();
2462 if(Score == -FinishTime::NOT_FINISHED_TIMESCORE)
2463 Score = FinishTime::NOT_FINISHED_TIMESCORE - 1;
2464 else if(Score == 0) // 0 time isn't displayed otherwise.
2465 Score = -1;
2466 else
2467 Score = -Score;
2468 }
2469 else
2470 {
2471 Score = FinishTime::NOT_FINISHED_TIMESCORE;
2472 }
2473
2474 ADD_INT(q, Score); // client score
2475 ADD_INT(q, GameServer()->IsClientPlayer(i) ? 1 : 0); // is player?
2476 if(Type == SERVERINFO_EXTENDED)
2477 q.AddString(pStr: "", Limit: 0); // extra info, reserved
2478
2479 if(Type == SERVERINFO_EXTENDED)
2480 {
2481 if(q.Size() >= NET_MAX_CONNLESS_PAYLOAD - 18) // 8 bytes for type, 10 bytes for the largest token
2482 {
2483 // Retry current player.
2484 i--;
2485 SAVE(PreviousSize);
2486 RESET();
2487 ADD_INT(q, ChunksStored);
2488 q.AddString(pStr: "", Limit: 0); // extra info, reserved
2489 continue;
2490 }
2491 }
2492 PlayersStored++;
2493 }
2494 }
2495
2496 SAVE(q.Size());
2497#undef SAVE
2498#undef RESET
2499#undef ADD_RAW
2500#undef ADD_INT
2501}
2502
2503void CServer::CacheServerInfoSixup(CCache *pCache, bool SendClients, int MaxConsideredClients)
2504{
2505 pCache->Clear();
2506
2507 CPacker Packer;
2508 Packer.Reset();
2509
2510 // Could be moved to a separate function and cached
2511 // count the players
2512 int PlayerCount = 0, ClientCount = 0, ClientCountAll = 0;
2513 for(int i = 0; i < MAX_CLIENTS; i++)
2514 {
2515 if(m_aClients[i].IncludedInServerInfo())
2516 {
2517 ClientCountAll++;
2518 if(i < MaxConsideredClients)
2519 {
2520 if(GameServer()->IsClientPlayer(ClientId: i))
2521 PlayerCount++;
2522
2523 ClientCount++;
2524 }
2525 }
2526 }
2527
2528 char aVersion[32];
2529 str_format(buffer: aVersion, buffer_size: sizeof(aVersion), format: "0.7↔%s", GameServer()->Version());
2530 Packer.AddString(pStr: aVersion, Limit: 32);
2531 if(!SendClients || ClientCountAll == ClientCount)
2532 {
2533 Packer.AddString(pStr: Config()->m_SvName, Limit: 64);
2534 }
2535 else
2536 {
2537 char aName[64];
2538 str_format(buffer: aName, buffer_size: sizeof(aName), format: "%s [%d/%d]", Config()->m_SvName, ClientCountAll, m_NetServer.MaxClients() - Config()->m_SvReservedSlots);
2539 Packer.AddString(pStr: aName, Limit: 64);
2540 }
2541 Packer.AddString(pStr: Config()->m_SvHostname, Limit: 128);
2542 Packer.AddString(pStr: GameServer()->Map()->BaseName(), Limit: 32);
2543
2544 // gametype
2545 Packer.AddString(pStr: GameServer()->GameType(), Limit: 16);
2546
2547 // flags
2548 int Flags = SERVER_FLAG_TIMESCORE;
2549 if(Config()->m_Password[0]) // password set
2550 Flags |= SERVER_FLAG_PASSWORD;
2551 Packer.AddInt(i: Flags);
2552
2553 int MaxClients = m_NetServer.MaxClients();
2554 Packer.AddInt(i: Config()->m_SvSkillLevel); // server skill level
2555 Packer.AddInt(i: PlayerCount); // num players
2556 Packer.AddInt(i: std::max(a: MaxClients - std::max(a: Config()->m_SvSpectatorSlots, b: Config()->m_SvReservedSlots), b: PlayerCount)); // max players
2557 Packer.AddInt(i: ClientCount); // num clients
2558 Packer.AddInt(i: std::max(a: MaxClients - Config()->m_SvReservedSlots, b: ClientCount)); // max clients
2559
2560 if(SendClients)
2561 {
2562 for(int i = 0; i < MaxConsideredClients; i++)
2563 {
2564 if(m_aClients[i].IncludedInServerInfo())
2565 {
2566 Packer.AddString(pStr: ClientName(ClientId: i), Limit: MAX_NAME_LENGTH); // client name
2567 Packer.AddString(pStr: ClientClan(ClientId: i), Limit: MAX_CLAN_LENGTH); // client clan
2568 Packer.AddInt(i: m_aClients[i].m_Country); // client country (ISO 3166-1 numeric)
2569 Packer.AddInt(i: m_aClients[i].m_Score.value_or(u: -1)); // client score
2570 Packer.AddInt(i: GameServer()->IsClientPlayer(ClientId: i) ? 0 : 1); // flag spectator=1, bot=2 (player=0)
2571
2572 const int MaxPacketSize = NET_MAX_CONNLESS_PAYLOAD - 128;
2573 if(MaxConsideredClients == MAX_CLIENTS)
2574 {
2575 if(Packer.Size() > MaxPacketSize - 32) // -32 because repacking will increase the length of the name
2576 {
2577 // Server info is too large for a packet. Only include as many clients as fit.
2578 // We need to ensure that the client counts match, otherwise the 0.7 client
2579 // will ignore the info, so we repack but only consider the first i clients.
2580 CacheServerInfoSixup(pCache, SendClients: true, MaxConsideredClients: i);
2581 return;
2582 }
2583 }
2584 else
2585 {
2586 dbg_assert(Packer.Size() <= MaxPacketSize, "Max packet size exceeded while repacking. Packer.Size()=%d MaxPacketSize=%d", Packer.Size(), MaxPacketSize);
2587 }
2588 }
2589 }
2590 }
2591
2592 pCache->AddChunk(pData: Packer.Data(), Size: Packer.Size());
2593}
2594
2595void CServer::SendServerInfo(const NETADDR *pAddr, int Token, int Type, bool SendClients)
2596{
2597 CPacker p;
2598 char aBuf[128];
2599 p.Reset();
2600
2601 CCache *pCache = &m_aServerInfoCache[GetCacheIndex(Type, SendClient: SendClients)];
2602
2603#define ADD_RAW(p, x) (p).AddRaw(x, sizeof(x))
2604#define ADD_INT(p, x) \
2605 do \
2606 { \
2607 str_format(aBuf, sizeof(aBuf), "%d", x); \
2608 (p).AddString(aBuf, 0); \
2609 } while(0)
2610
2611 CNetChunk Packet;
2612 Packet.m_ClientId = -1;
2613 Packet.m_Address = *pAddr;
2614 Packet.m_Flags = NETSENDFLAG_CONNLESS;
2615
2616 for(const auto &Chunk : pCache->m_vCache)
2617 {
2618 p.Reset();
2619 if(Type == SERVERINFO_EXTENDED)
2620 {
2621 if(&Chunk == &pCache->m_vCache.front())
2622 p.AddRaw(pData: SERVERBROWSE_INFO_EXTENDED, Size: sizeof(SERVERBROWSE_INFO_EXTENDED));
2623 else
2624 p.AddRaw(pData: SERVERBROWSE_INFO_EXTENDED_MORE, Size: sizeof(SERVERBROWSE_INFO_EXTENDED_MORE));
2625 ADD_INT(p, Token);
2626 }
2627 else if(Type == SERVERINFO_64_LEGACY)
2628 {
2629 ADD_RAW(p, SERVERBROWSE_INFO_64_LEGACY);
2630 ADD_INT(p, Token);
2631 }
2632 else if(Type == SERVERINFO_VANILLA || Type == SERVERINFO_INGAME)
2633 {
2634 ADD_RAW(p, SERVERBROWSE_INFO);
2635 ADD_INT(p, Token);
2636 }
2637 else
2638 {
2639 dbg_assert_failed("Invalid serverinfo Type: %d", Type);
2640 }
2641
2642 p.AddRaw(pData: Chunk.m_vData.data(), Size: Chunk.m_vData.size());
2643 Packet.m_pData = p.Data();
2644 Packet.m_DataSize = p.Size();
2645 m_NetServer.Send(pChunk: &Packet);
2646 }
2647}
2648
2649void CServer::GetServerInfoSixup(CPacker *pPacker, bool SendClients)
2650{
2651 CCache::CCacheChunk &FirstChunk = m_aSixupServerInfoCache[SendClients].m_vCache.front();
2652 pPacker->AddRaw(pData: FirstChunk.m_vData.data(), Size: FirstChunk.m_vData.size());
2653}
2654
2655void CServer::FillAntibot(CAntibotRoundData *pData)
2656{
2657 for(int ClientId = 0; ClientId < MAX_CLIENTS; ClientId++)
2658 {
2659 CAntibotPlayerData *pPlayer = &pData->m_aPlayers[ClientId];
2660 if(m_aClients[ClientId].m_State == CServer::CClient::STATE_EMPTY)
2661 {
2662 pPlayer->m_aAddress[0] = '\0';
2663 }
2664 else
2665 {
2666 // No need for expensive str_copy since we don't truncate and the string is
2667 // ASCII anyway
2668 static_assert(std::size((CAntibotPlayerData{}).m_aAddress) >= NETADDR_MAXSTRSIZE);
2669 static_assert(std::is_same_v<decltype(CServer{}.ClientAddrStringImpl(ClientId, IncludePort: true)), const std::array<char, NETADDR_MAXSTRSIZE> &>);
2670 mem_copy(dest: pPlayer->m_aAddress, source: ClientAddrStringImpl(ClientId, IncludePort: true).data(), size: NETADDR_MAXSTRSIZE);
2671 pPlayer->m_Sixup = m_aClients[ClientId].m_Sixup;
2672 pPlayer->m_DnsblNone = m_aClients[ClientId].m_DnsblState == EDnsblState::NONE;
2673 pPlayer->m_DnsblPending = m_aClients[ClientId].m_DnsblState == EDnsblState::PENDING;
2674 pPlayer->m_DnsblBlacklisted = m_aClients[ClientId].m_DnsblState == EDnsblState::BLACKLISTED;
2675 pPlayer->m_Authed = IsRconAuthed(ClientId);
2676 }
2677 }
2678}
2679
2680void CServer::ExpireServerInfo()
2681{
2682 m_ServerInfoNeedsUpdate = true;
2683}
2684
2685void CServer::ExpireServerInfoAndQueueResend()
2686{
2687 m_ServerInfoNeedsUpdate = true;
2688 m_ServerInfoNeedsResend = true;
2689}
2690
2691void CServer::UpdateRegisterServerInfo()
2692{
2693 // count the players
2694 int PlayerCount = 0, ClientCount = 0;
2695 for(int i = 0; i < MAX_CLIENTS; i++)
2696 {
2697 if(m_aClients[i].IncludedInServerInfo())
2698 {
2699 if(GameServer()->IsClientPlayer(ClientId: i))
2700 PlayerCount++;
2701
2702 ClientCount++;
2703 }
2704 }
2705
2706 int MaxPlayers = std::max(a: m_NetServer.MaxClients() - std::max(a: g_Config.m_SvSpectatorSlots, b: g_Config.m_SvReservedSlots), b: PlayerCount);
2707 int MaxClients = std::max(a: m_NetServer.MaxClients() - g_Config.m_SvReservedSlots, b: ClientCount);
2708 char aMapSha256[SHA256_MAXSTRSIZE];
2709
2710 sha256_str(digest: m_aCurrentMapSha256[MAP_TYPE_SIX], str: aMapSha256, max_len: sizeof(aMapSha256));
2711
2712 CJsonStringWriter JsonWriter;
2713
2714 JsonWriter.BeginObject();
2715 JsonWriter.WriteAttribute(pName: "max_clients");
2716 JsonWriter.WriteIntValue(Value: MaxClients);
2717
2718 JsonWriter.WriteAttribute(pName: "max_players");
2719 JsonWriter.WriteIntValue(Value: MaxPlayers);
2720
2721 JsonWriter.WriteAttribute(pName: "passworded");
2722 JsonWriter.WriteBoolValue(Value: g_Config.m_Password[0]);
2723
2724 JsonWriter.WriteAttribute(pName: "game_type");
2725 JsonWriter.WriteStrValue(pValue: GameServer()->GameType());
2726
2727 if(g_Config.m_SvRegisterCommunityToken[0])
2728 {
2729 if(g_Config.m_SvFlag != -1)
2730 {
2731 JsonWriter.WriteAttribute(pName: "country");
2732 JsonWriter.WriteIntValue(Value: g_Config.m_SvFlag); // ISO 3166-1 numeric
2733 }
2734 }
2735
2736 JsonWriter.WriteAttribute(pName: "name");
2737 JsonWriter.WriteStrValue(pValue: g_Config.m_SvName);
2738
2739 JsonWriter.WriteAttribute(pName: "map");
2740 JsonWriter.BeginObject();
2741 JsonWriter.WriteAttribute(pName: "name");
2742 JsonWriter.WriteStrValue(pValue: GameServer()->Map()->BaseName());
2743 JsonWriter.WriteAttribute(pName: "sha256");
2744 JsonWriter.WriteStrValue(pValue: aMapSha256);
2745 JsonWriter.WriteAttribute(pName: "size");
2746 JsonWriter.WriteIntValue(Value: m_aCurrentMapSize[MAP_TYPE_SIX]);
2747 if(m_aMapDownloadUrl[0])
2748 {
2749 JsonWriter.WriteAttribute(pName: "url");
2750 JsonWriter.WriteStrValue(pValue: m_aMapDownloadUrl);
2751 }
2752 JsonWriter.EndObject();
2753
2754 JsonWriter.WriteAttribute(pName: "version");
2755 JsonWriter.WriteStrValue(pValue: GameServer()->Version());
2756
2757 JsonWriter.WriteAttribute(pName: "client_score_kind");
2758 JsonWriter.WriteStrValue(pValue: "time"); // "points" or "time"
2759
2760 JsonWriter.WriteAttribute(pName: "requires_login");
2761 JsonWriter.WriteBoolValue(Value: false);
2762
2763 {
2764 bool FoundFlags = false;
2765 auto Flag = [&](const char *pFlag) {
2766 if(!FoundFlags)
2767 {
2768 JsonWriter.WriteAttribute(pName: "flags");
2769 JsonWriter.BeginArray();
2770 FoundFlags = true;
2771 }
2772 JsonWriter.WriteStrValue(pValue: pFlag);
2773 };
2774
2775 if(g_Config.m_SvRegisterCommunityToken[0] && g_Config.m_SvOfficialTutorial[0])
2776 {
2777 SHA256_DIGEST Sha256 = sha256(message: g_Config.m_SvOfficialTutorial, message_len: str_length(str: g_Config.m_SvOfficialTutorial));
2778 char aSha256[SHA256_MAXSTRSIZE];
2779 sha256_str(digest: Sha256, str: aSha256, max_len: sizeof(aSha256));
2780 if(str_comp(a: aSha256, b: "8a11dc71274313e78a09ff58b8e696fb5009ce8606d12077ceb78ebf99a57464") == 0)
2781 {
2782 Flag("tutorial");
2783 }
2784 }
2785
2786 if(FoundFlags)
2787 {
2788 JsonWriter.EndArray();
2789 }
2790 }
2791
2792 JsonWriter.WriteAttribute(pName: "clients");
2793 JsonWriter.BeginArray();
2794
2795 for(int i = 0; i < MAX_CLIENTS; i++)
2796 {
2797 if(m_aClients[i].IncludedInServerInfo())
2798 {
2799 JsonWriter.BeginObject();
2800
2801 JsonWriter.WriteAttribute(pName: "name");
2802 JsonWriter.WriteStrValue(pValue: ClientName(ClientId: i));
2803
2804 JsonWriter.WriteAttribute(pName: "clan");
2805 JsonWriter.WriteStrValue(pValue: ClientClan(ClientId: i));
2806
2807 JsonWriter.WriteAttribute(pName: "country");
2808 JsonWriter.WriteIntValue(Value: m_aClients[i].m_Country); // ISO 3166-1 numeric
2809
2810 JsonWriter.WriteAttribute(pName: "score");
2811 JsonWriter.WriteIntValue(Value: m_aClients[i].m_Score.value_or(u: FinishTime::NOT_FINISHED_TIMESCORE));
2812
2813 JsonWriter.WriteAttribute(pName: "is_player");
2814 JsonWriter.WriteBoolValue(Value: GameServer()->IsClientPlayer(ClientId: i));
2815
2816 GameServer()->OnUpdatePlayerServerInfo(pJsonWriter: &JsonWriter, ClientId: i);
2817
2818 JsonWriter.EndObject();
2819 }
2820 }
2821
2822 JsonWriter.EndArray();
2823 JsonWriter.EndObject();
2824
2825 m_pRegister->OnNewInfo(pInfo: JsonWriter.GetOutputString().c_str());
2826}
2827
2828void CServer::UpdateServerInfo(bool Resend)
2829{
2830 if(m_RunServer == UNINITIALIZED)
2831 return;
2832
2833 UpdateRegisterServerInfo();
2834
2835 for(int i = 0; i < 3; i++)
2836 for(int j = 0; j < 2; j++)
2837 CacheServerInfo(pCache: &m_aServerInfoCache[i * 2 + j], Type: i, SendClients: j);
2838
2839 for(int i = 0; i < 2; i++)
2840 CacheServerInfoSixup(pCache: &m_aSixupServerInfoCache[i], SendClients: i, MaxConsideredClients: MAX_CLIENTS);
2841
2842 if(Resend)
2843 {
2844 for(int i = 0; i < MaxClients(); ++i)
2845 {
2846 if(m_aClients[i].m_State != CClient::STATE_EMPTY)
2847 {
2848 if(!IsSixup(ClientId: i))
2849 {
2850 SendServerInfo(pAddr: ClientAddr(ClientId: i), Token: -1, Type: SERVERINFO_INGAME, SendClients: false);
2851 }
2852 else
2853 {
2854 CMsgPacker ServerInfoMessage(protocol7::NETMSG_SERVERINFO, true, true);
2855 GetServerInfoSixup(pPacker: &ServerInfoMessage, SendClients: false);
2856 SendMsg(pMsg: &ServerInfoMessage, Flags: MSGFLAG_VITAL | MSGFLAG_FLUSH, ClientId: i);
2857 }
2858 }
2859 }
2860 m_ServerInfoNeedsResend = false;
2861 }
2862
2863 m_ServerInfoNeedsUpdate = false;
2864}
2865
2866void CServer::PumpNetwork(bool PacketWaiting)
2867{
2868 CNetChunk Packet;
2869 SECURITY_TOKEN ResponseToken;
2870
2871 m_NetServer.Update();
2872
2873 // Coalesce the flushes triggered while handling this burst of incoming
2874 // packets (preinput broadcasts, timing/ping replies, ...) into one packet
2875 // per recipient, flushed once all packets have been handled below.
2876 m_NetServer.BeginFlushBatch();
2877
2878 if(PacketWaiting)
2879 {
2880 // process packets
2881 ResponseToken = NET_SECURITY_TOKEN_UNKNOWN;
2882 while(m_NetServer.Recv(pChunk: &Packet, pResponseToken: &ResponseToken))
2883 {
2884 if(Packet.m_ClientId == -1)
2885 {
2886 if(ResponseToken == NET_SECURITY_TOKEN_UNKNOWN && m_pRegister->OnPacket(pPacket: &Packet))
2887 continue;
2888
2889 {
2890 int ExtraToken = 0;
2891 int Type = -1;
2892 if(Packet.m_DataSize >= (int)sizeof(SERVERBROWSE_GETINFO) + 1 &&
2893 mem_comp(a: Packet.m_pData, b: SERVERBROWSE_GETINFO, size: sizeof(SERVERBROWSE_GETINFO)) == 0)
2894 {
2895 if(Packet.m_Flags & NETSENDFLAG_EXTENDED)
2896 {
2897 Type = SERVERINFO_EXTENDED;
2898 ExtraToken = (Packet.m_aExtraData[0] << 8) | Packet.m_aExtraData[1];
2899 }
2900 else
2901 {
2902 Type = SERVERINFO_VANILLA;
2903 }
2904 }
2905 else if(Packet.m_DataSize >= (int)sizeof(SERVERBROWSE_GETINFO_64_LEGACY) + 1 &&
2906 mem_comp(a: Packet.m_pData, b: SERVERBROWSE_GETINFO_64_LEGACY, size: sizeof(SERVERBROWSE_GETINFO_64_LEGACY)) == 0)
2907 {
2908 Type = SERVERINFO_64_LEGACY;
2909 }
2910 if(Type == SERVERINFO_VANILLA && ResponseToken != NET_SECURITY_TOKEN_UNKNOWN && Config()->m_SvSixup)
2911 {
2912 CUnpacker Unpacker;
2913 Unpacker.Reset(pData: (unsigned char *)Packet.m_pData + sizeof(SERVERBROWSE_GETINFO), Size: Packet.m_DataSize - sizeof(SERVERBROWSE_GETINFO));
2914 int SrvBrwsToken = Unpacker.GetInt();
2915 if(Unpacker.Error())
2916 {
2917 continue;
2918 }
2919
2920 CPacker Packer;
2921 Packer.Reset();
2922 Packer.AddRaw(pData: SERVERBROWSE_INFO, Size: sizeof(SERVERBROWSE_INFO));
2923 Packer.AddInt(i: SrvBrwsToken);
2924 GetServerInfoSixup(pPacker: &Packer, SendClients: RateLimitServerInfoConnless());
2925 CNetBase::SendPacketConnlessWithToken7(Socket: m_NetServer.Socket(), pAddr: &Packet.m_Address, pData: Packer.Data(), DataSize: Packer.Size(), Token: ResponseToken, ResponseToken: m_NetServer.GetToken(Addr: Packet.m_Address));
2926 }
2927 else if(Type != -1)
2928 {
2929 int Token = ((unsigned char *)Packet.m_pData)[sizeof(SERVERBROWSE_GETINFO)];
2930 Token |= ExtraToken << 8;
2931 SendServerInfoConnless(pAddr: &Packet.m_Address, Token, Type);
2932 }
2933 }
2934 }
2935 else
2936 {
2937 if(m_aClients[Packet.m_ClientId].m_State == CClient::STATE_REDIRECTED)
2938 continue;
2939
2940 int GameFlags = 0;
2941 if(Packet.m_Flags & NET_CHUNKFLAG_VITAL)
2942 {
2943 GameFlags |= MSGFLAG_VITAL;
2944 }
2945 if(Antibot()->OnEngineClientMessage(ClientId: Packet.m_ClientId, pData: Packet.m_pData, Size: Packet.m_DataSize, Flags: GameFlags))
2946 {
2947 continue;
2948 }
2949
2950 ProcessClientPacket(pPacket: &Packet);
2951 }
2952 }
2953 }
2954 {
2955 unsigned char aBuffer[NET_MAX_CHUNK_SIZE];
2956 int Flags;
2957 mem_zero(block: &Packet, size: sizeof(Packet));
2958 Packet.m_pData = aBuffer;
2959 while(Antibot()->OnEngineSimulateClientMessage(pClientId: &Packet.m_ClientId, pBuffer: aBuffer, BufferSize: sizeof(aBuffer), pOutSize: &Packet.m_DataSize, pFlags: &Flags))
2960 {
2961 Packet.m_Flags = 0;
2962 if(Flags & MSGFLAG_VITAL)
2963 {
2964 Packet.m_Flags |= NET_CHUNKFLAG_VITAL;
2965 }
2966 ProcessClientPacket(pPacket: &Packet);
2967 }
2968 }
2969
2970 m_NetServer.EndFlushBatch();
2971
2972 m_ServerBan.Update();
2973 m_Econ.Update();
2974}
2975
2976void CServer::ChangeMap(const char *pMap)
2977{
2978 str_copy(dst&: Config()->m_SvMap, src: pMap);
2979 m_MapReload = str_comp(a: Config()->m_SvMap, b: GameServer()->Map()->FullName()) != 0;
2980}
2981
2982void CServer::ReloadMap()
2983{
2984 m_SameMapReload = true;
2985}
2986
2987int CServer::LoadMap(const char *pMapName)
2988{
2989 m_MapReload = false;
2990 m_SameMapReload = false;
2991
2992 char aBuf[IO_MAX_PATH_LENGTH];
2993 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "maps/%s.map", pMapName);
2994 if(!str_valid_filename(str: fs_filename(path: aBuf)))
2995 {
2996 log_error("server", "The name '%s' cannot be used for maps because not all platforms support it", aBuf);
2997 return 0;
2998 }
2999 if(!GameServer()->OnMapChange(pNewMapName: aBuf, MapNameSize: sizeof(aBuf)))
3000 {
3001 return 0;
3002 }
3003 if(!GameServer()->Map()->Load(pFullName: pMapName, pStorage: Storage(), pPath: aBuf, StorageType: IStorage::TYPE_ALL))
3004 {
3005 return 0;
3006 }
3007
3008 // reinit snapshot ids
3009 m_IdPool.TimeoutIds();
3010
3011 // get the crc of the map
3012 m_aCurrentMapSha256[MAP_TYPE_SIX] = GameServer()->Map()->Sha256();
3013 m_aCurrentMapCrc[MAP_TYPE_SIX] = GameServer()->Map()->Crc();
3014 char aBufMsg[256];
3015 char aSha256[SHA256_MAXSTRSIZE];
3016 sha256_str(digest: m_aCurrentMapSha256[MAP_TYPE_SIX], str: aSha256, max_len: sizeof(aSha256));
3017 str_format(buffer: aBufMsg, buffer_size: sizeof(aBufMsg), format: "%s sha256 is %s", aBuf, aSha256);
3018 Console()->Print(Level: IConsole::OUTPUT_LEVEL_ADDINFO, pFrom: "server", pStr: aBufMsg);
3019
3020 // load complete map into memory for download
3021 {
3022 free(ptr: m_apCurrentMapData[MAP_TYPE_SIX]);
3023 void *pData;
3024 Storage()->ReadFile(pFilename: aBuf, Type: IStorage::TYPE_ALL, ppResult: &pData, pResultLen: &m_aCurrentMapSize[MAP_TYPE_SIX]);
3025 m_apCurrentMapData[MAP_TYPE_SIX] = (unsigned char *)pData;
3026 }
3027
3028 if(Config()->m_SvMapsBaseUrl[0])
3029 {
3030 char aEscaped[256];
3031 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s_%s.map", pMapName, aSha256);
3032 EscapeUrl(aBuf&: aEscaped, pStr: aBuf);
3033 str_format(buffer: m_aMapDownloadUrl, buffer_size: sizeof(m_aMapDownloadUrl), format: "%s%s", Config()->m_SvMapsBaseUrl, aEscaped);
3034 }
3035 else
3036 {
3037 m_aMapDownloadUrl[0] = '\0';
3038 }
3039
3040 // load sixup version of the map
3041 if(Config()->m_SvSixup)
3042 {
3043 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "maps7/%s.map", pMapName);
3044 void *pData;
3045 if(!Storage()->ReadFile(pFilename: aBuf, Type: IStorage::TYPE_ALL, ppResult: &pData, pResultLen: &m_aCurrentMapSize[MAP_TYPE_SIXUP]))
3046 {
3047 Config()->m_SvSixup = 0;
3048 if(m_pRegister)
3049 {
3050 m_pRegister->OnConfigChange();
3051 }
3052 log_error("sixup", "couldn't load map %s", aBuf);
3053 log_info("sixup", "disabling 0.7 compatibility");
3054 }
3055 else
3056 {
3057 free(ptr: m_apCurrentMapData[MAP_TYPE_SIXUP]);
3058 m_apCurrentMapData[MAP_TYPE_SIXUP] = (unsigned char *)pData;
3059
3060 m_aCurrentMapSha256[MAP_TYPE_SIXUP] = sha256(message: m_apCurrentMapData[MAP_TYPE_SIXUP], message_len: m_aCurrentMapSize[MAP_TYPE_SIXUP]);
3061 m_aCurrentMapCrc[MAP_TYPE_SIXUP] = crc32(crc: 0, buf: m_apCurrentMapData[MAP_TYPE_SIXUP], len: m_aCurrentMapSize[MAP_TYPE_SIXUP]);
3062 sha256_str(digest: m_aCurrentMapSha256[MAP_TYPE_SIXUP], str: aSha256, max_len: sizeof(aSha256));
3063 str_format(buffer: aBufMsg, buffer_size: sizeof(aBufMsg), format: "%s sha256 is %s", aBuf, aSha256);
3064 Console()->Print(Level: IConsole::OUTPUT_LEVEL_ADDINFO, pFrom: "sixup", pStr: aBufMsg);
3065 }
3066 }
3067 if(!Config()->m_SvSixup)
3068 {
3069 free(ptr: m_apCurrentMapData[MAP_TYPE_SIXUP]);
3070 m_apCurrentMapData[MAP_TYPE_SIXUP] = nullptr;
3071 }
3072
3073 for(int i = 0; i < MAX_CLIENTS; i++)
3074 m_aPrevStates[i] = m_aClients[i].m_State;
3075
3076 return 1;
3077}
3078
3079void CServer::UpdateDebugDummies(bool ForceDisconnect)
3080{
3081 if(m_PreviousDebugDummies == g_Config.m_DbgDummies && !ForceDisconnect)
3082 return;
3083
3084 g_Config.m_DbgDummies = std::clamp(val: g_Config.m_DbgDummies, lo: 0, hi: MaxClients());
3085 for(int DummyIndex = 0; DummyIndex < std::max(a: m_PreviousDebugDummies, b: g_Config.m_DbgDummies); ++DummyIndex)
3086 {
3087 const bool AddDummy = !ForceDisconnect && DummyIndex < g_Config.m_DbgDummies;
3088 const int ClientId = MaxClients() - DummyIndex - 1;
3089 CClient &Client = m_aClients[ClientId];
3090 if(AddDummy && m_aClients[ClientId].m_State == CClient::STATE_EMPTY)
3091 {
3092 NewClientCallback(ClientId, pUser: this, Sixup: false);
3093 Client.m_DebugDummy = true;
3094
3095 // See https://en.wikipedia.org/wiki/Unique_local_address
3096 Client.m_DebugDummyAddr.type = NETTYPE_IPV6;
3097 Client.m_DebugDummyAddr.ip[0] = 0xfd;
3098 // Global ID (40 bits): random
3099 secure_random_fill(bytes: &Client.m_DebugDummyAddr.ip[1], length: 5);
3100 // Subnet ID (16 bits): constant
3101 Client.m_DebugDummyAddr.ip[6] = 0xc0;
3102 Client.m_DebugDummyAddr.ip[7] = 0xde;
3103 // Interface ID (64 bits): set to client ID
3104 Client.m_DebugDummyAddr.ip[8] = 0x00;
3105 Client.m_DebugDummyAddr.ip[9] = 0x00;
3106 Client.m_DebugDummyAddr.ip[10] = 0x00;
3107 Client.m_DebugDummyAddr.ip[11] = 0x00;
3108 uint_to_bytes_be(bytes: &Client.m_DebugDummyAddr.ip[12], value: ClientId);
3109 // Port: random like normal clients
3110 Client.m_DebugDummyAddr.port = secure_rand_below(below: 65535 - 1024) + 1024;
3111 net_addr_str(addr: &Client.m_DebugDummyAddr, string: Client.m_aDebugDummyAddrString.data(), max_length: Client.m_aDebugDummyAddrString.size(), add_port: true);
3112 net_addr_str(addr: &Client.m_DebugDummyAddr, string: Client.m_aDebugDummyAddrStringNoPort.data(), max_length: Client.m_aDebugDummyAddrStringNoPort.size(), add_port: false);
3113
3114 GameServer()->OnClientConnected(ClientId, pPersistentData: nullptr);
3115 Client.m_State = CClient::STATE_INGAME;
3116 Client.m_DDNetVersion = DDNET_VERSION_NUMBER;
3117 Client.m_GotDDNetVersionPacket = true;
3118 Client.m_DDNetVersionSettled = true;
3119 str_format(buffer: Client.m_aName, buffer_size: sizeof(Client.m_aName), format: "Debug dummy %d", DummyIndex + 1);
3120 GameServer()->OnClientEnter(ClientId);
3121 }
3122 else if(!AddDummy && Client.m_DebugDummy)
3123 {
3124 DelClientCallback(ClientId, pReason: "Dropping debug dummy", pUser: this);
3125 }
3126
3127 if(AddDummy && Client.m_DebugDummy)
3128 {
3129 CNetObj_PlayerInput Input = {.m_Direction: 0};
3130 Input.m_Direction = (ClientId & 1) ? -1 : 1;
3131 Client.m_aInputs[0].m_GameTick = Tick() + 1;
3132 mem_copy(dest: Client.m_aInputs[0].m_aData, source: &Input, size: std::min(a: sizeof(Input), b: sizeof(Client.m_aInputs[0].m_aData)));
3133 Client.m_LatestInput = Client.m_aInputs[0];
3134 Client.m_CurrentInput = 0;
3135 }
3136 }
3137
3138 m_PreviousDebugDummies = ForceDisconnect ? 0 : g_Config.m_DbgDummies;
3139}
3140
3141int CServer::Run()
3142{
3143 if(m_RunServer == UNINITIALIZED)
3144 m_RunServer = RUNNING;
3145
3146 m_AuthManager.Init();
3147
3148 if(Config()->m_Debug)
3149 {
3150 g_UuidManager.DebugDump();
3151 }
3152
3153 {
3154 int Size = GameServer()->PersistentClientDataSize();
3155 for(auto &Client : m_aClients)
3156 {
3157 Client.m_HasPersistentData = false;
3158 Client.m_pPersistentData = malloc(size: Size);
3159 }
3160 }
3161 m_pPersistentData = malloc(size: GameServer()->PersistentDataSize());
3162
3163 // load map
3164 if(!LoadMap(pMapName: Config()->m_SvMap))
3165 {
3166 log_error("server", "failed to load map. mapname='%s'", Config()->m_SvMap);
3167 return -1;
3168 }
3169
3170 if(Config()->m_SvSqliteFile[0] != '\0')
3171 {
3172 if(!fs_is_relative_path(path: Config()->m_SvSqliteFile))
3173 {
3174 log_error("server", "sv_sqlite_file must be a relative path. path='%s'", Config()->m_SvSqliteFile);
3175 return -1;
3176 }
3177 char aFullPath[IO_MAX_PATH_LENGTH];
3178 Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, pDir: Config()->m_SvSqliteFile, pBuffer: aFullPath, BufferSize: sizeof(aFullPath));
3179
3180 if(Config()->m_SvUseSql)
3181 {
3182 DbPool()->RegisterSqliteDatabase(DatabaseMode: CDbConnectionPool::WRITE_BACKUP, aFilename: aFullPath);
3183 }
3184 else
3185 {
3186 DbPool()->RegisterSqliteDatabase(DatabaseMode: CDbConnectionPool::READ, aFilename: aFullPath);
3187 DbPool()->RegisterSqliteDatabase(DatabaseMode: CDbConnectionPool::WRITE, aFilename: aFullPath);
3188 }
3189 }
3190
3191 // start server
3192 NETADDR BindAddr;
3193 if(g_Config.m_Bindaddr[0] == '\0')
3194 {
3195 mem_zero(block: &BindAddr, size: sizeof(BindAddr));
3196 }
3197 else if(net_host_lookup(hostname: g_Config.m_Bindaddr, addr: &BindAddr, types: NETTYPE_ALL) != 0)
3198 {
3199 log_error("server", "The configured bindaddr '%s' cannot be resolved", g_Config.m_Bindaddr);
3200 return -1;
3201 }
3202 BindAddr.type = Config()->m_SvIpv4Only ? (NETTYPE_IPV4 | NETTYPE_WEBSOCKET_IPV4) : NETTYPE_ALL;
3203
3204 int Port = Config()->m_SvPort;
3205 for(BindAddr.port = Port != 0 ? Port : 8303; !m_NetServer.Open(BindAddr, pNetBan: &m_ServerBan, MaxClients: Config()->m_SvMaxClients, MaxClientsPerIp: Config()->m_SvMaxClientsPerIp); BindAddr.port++)
3206 {
3207 if(Port != 0 || BindAddr.port >= 8310)
3208 {
3209 log_error("server", "couldn't open socket. port %d might already be in use", BindAddr.port);
3210 return -1;
3211 }
3212 }
3213
3214 if(Port == 0)
3215 log_info("server", "using port %d", BindAddr.port);
3216
3217#if defined(CONF_UPNP)
3218 m_UPnP.Open(Address: BindAddr);
3219#endif
3220
3221 if(!m_pHttp->Init(ShutdownDelay: std::chrono::seconds{2}))
3222 {
3223 log_error("server", "Failed to initialize the HTTP client.");
3224 return -1;
3225 }
3226
3227 m_pEngine = Kernel()->RequestInterface<IEngine>();
3228 m_pRegister = CreateRegister(pConfig: &g_Config, pConsole: m_pConsole, pEngine: m_pEngine, pHttp: m_pHttp, ServerPort: g_Config.m_SvRegisterPort > 0 ? g_Config.m_SvRegisterPort : this->Port(), SixupSecurityToken: m_NetServer.GetGlobalToken());
3229
3230 m_NetServer.SetCallbacks(pfnNewClient: NewClientCallback, pfnNewClientNoAuth: NewClientNoAuthCallback, pfnClientRejoin: ClientRejoinCallback, pfnDelClient: DelClientCallback, pUser: this);
3231
3232 m_Econ.Init(pConfig: Config(), pConsole: Console(), pNetBan: &m_ServerBan);
3233
3234 m_Fifo.Init(pConsole: Console(), pFifoFile: Config()->m_SvInputFifo, Flag: CFGFLAG_SERVER);
3235
3236 char aBuf[256];
3237 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "server name is '%s'", Config()->m_SvName);
3238 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server", pStr: aBuf);
3239
3240 Antibot()->Init();
3241 GameServer()->OnInit(pPersistentData: nullptr);
3242 if(ErrorShutdown())
3243 {
3244 m_RunServer = STOPPING;
3245 }
3246 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server", pStr: "version " GAME_RELEASE_VERSION " on " CONF_PLATFORM_STRING " " CONF_ARCH_STRING);
3247 if(GIT_SHORTREV_HASH)
3248 {
3249 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "git revision hash: %s", GIT_SHORTREV_HASH);
3250 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server", pStr: aBuf);
3251 }
3252
3253 ReadAnnouncementsFile();
3254 InitMaplist();
3255
3256 // process pending commands
3257 m_pConsole->StoreCommands(Store: false);
3258 m_pRegister->OnConfigChange();
3259
3260 if(m_AuthManager.IsGenerated())
3261 {
3262 log_info("server", "+-------------------------+");
3263 log_info("server", "| rcon password: '%s' |", Config()->m_SvRconPassword);
3264 log_info("server", "+-------------------------+");
3265 }
3266
3267 // start game
3268 {
3269 bool NonActive = false;
3270 bool PacketWaiting = false;
3271
3272 m_GameStartTime = time_get();
3273
3274 UpdateServerInfo(Resend: false);
3275 while(m_RunServer < STOPPING)
3276 {
3277 if(NonActive)
3278 PumpNetwork(PacketWaiting);
3279
3280 set_new_tick();
3281
3282 int64_t LastTime = time_get();
3283 int NewTicks = 0;
3284
3285 // load new map
3286 if(m_MapReload || m_SameMapReload || m_CurrentGameTick >= MAX_TICK) // force reload to make sure the ticks stay within a valid range
3287 {
3288 const bool SameMapReload = m_SameMapReload;
3289 // load map
3290 if(LoadMap(pMapName: Config()->m_SvMap))
3291 {
3292 // new map loaded
3293
3294 // ask the game for the data it wants to persist past a map change
3295 for(int i = 0; i < MAX_CLIENTS; i++)
3296 {
3297 if(m_aClients[i].m_State == CClient::STATE_INGAME)
3298 {
3299 m_aClients[i].m_HasPersistentData = GameServer()->OnClientDataPersist(ClientId: i, pData: m_aClients[i].m_pPersistentData);
3300 }
3301 }
3302
3303 UpdateDebugDummies(ForceDisconnect: true);
3304 GameServer()->OnShutdown(pPersistentData: m_pPersistentData);
3305
3306 for(int ClientId = 0; ClientId < MAX_CLIENTS; ClientId++)
3307 {
3308 if(m_aClients[ClientId].m_State <= CClient::STATE_AUTH)
3309 continue;
3310
3311 if(SameMapReload)
3312 SendMapReload(ClientId);
3313
3314 SendMap(ClientId);
3315 bool HasPersistentData = m_aClients[ClientId].m_HasPersistentData;
3316 m_aClients[ClientId].Reset();
3317 m_aClients[ClientId].m_HasPersistentData = HasPersistentData;
3318 m_aClients[ClientId].m_State = CClient::STATE_CONNECTING;
3319 }
3320
3321 m_GameStartTime = time_get();
3322 m_CurrentGameTick = MIN_TICK;
3323 m_ServerInfoFirstRequest = 0;
3324 Kernel()->ReregisterInterface(pInterface: GameServer());
3325 Console()->StoreCommands(Store: true);
3326 GameServer()->OnInit(pPersistentData: m_pPersistentData);
3327 Console()->StoreCommands(Store: false);
3328
3329 for(int ClientId = 0; ClientId < MAX_CLIENTS; ClientId++)
3330 {
3331 CClient &Client = m_aClients[ClientId];
3332 if(Client.m_State < CClient::STATE_PREAUTH)
3333 continue;
3334
3335 // When doing a map change, a new Teehistorian file is created. For players that are already
3336 // on the server, no PlayerJoin event is produced in Teehistorian from the network engine.
3337 // Record PlayerJoin events here to record the Sixup version and player join event.
3338 GameServer()->TeehistorianRecordPlayerJoin(ClientId, Sixup: Client.m_Sixup);
3339
3340 // Record the players auth state aswell if needed.
3341 // This was recorded in AuthInit in the past.
3342 if(IsRconAuthed(ClientId))
3343 {
3344 GameServer()->TeehistorianRecordAuthLogin(ClientId, Level: GetAuthedState(ClientId), pAuthName: GetAuthName(ClientId));
3345 }
3346 }
3347
3348 if(ErrorShutdown())
3349 {
3350 break;
3351 }
3352 ExpireServerInfo();
3353 }
3354 else
3355 {
3356 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "failed to load map. mapname='%s'", Config()->m_SvMap);
3357 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server", pStr: aBuf);
3358 str_copy(dst&: Config()->m_SvMap, src: GameServer()->Map()->FullName());
3359 }
3360 }
3361
3362 while(LastTime > TickStartTime(Tick: m_CurrentGameTick + 1))
3363 {
3364 GameServer()->OnPreTickTeehistorian();
3365 UpdateDebugDummies(ForceDisconnect: false);
3366
3367 for(int c = 0; c < MAX_CLIENTS; c++)
3368 {
3369 if(m_aClients[c].m_State != CClient::STATE_INGAME)
3370 continue;
3371 bool ClientHadInput = false;
3372 for(auto &Input : m_aClients[c].m_aInputs)
3373 {
3374 if(Input.m_GameTick == Tick() + 1)
3375 {
3376 GameServer()->OnClientPredictedEarlyInput(ClientId: c, pInput: Input.m_aData);
3377 ClientHadInput = true;
3378 break;
3379 }
3380 }
3381 if(!ClientHadInput)
3382 GameServer()->OnClientPredictedEarlyInput(ClientId: c, pInput: nullptr);
3383 }
3384
3385 m_CurrentGameTick++;
3386 NewTicks++;
3387
3388 // apply new input
3389 for(int c = 0; c < MAX_CLIENTS; c++)
3390 {
3391 if(m_aClients[c].m_State != CClient::STATE_INGAME)
3392 continue;
3393 bool ClientHadInput = false;
3394 for(auto &Input : m_aClients[c].m_aInputs)
3395 {
3396 if(Input.m_GameTick == Tick())
3397 {
3398 GameServer()->OnClientPredictedInput(ClientId: c, pInput: Input.m_aData);
3399 ClientHadInput = true;
3400 break;
3401 }
3402 }
3403 if(!ClientHadInput)
3404 GameServer()->OnClientPredictedInput(ClientId: c, pInput: nullptr);
3405 }
3406
3407 GameServer()->OnTick();
3408 if(ErrorShutdown())
3409 {
3410 break;
3411 }
3412 }
3413
3414 // snap game
3415 if(NewTicks)
3416 {
3417 DoSnapshot();
3418
3419 const int CommandSendingClientId = Tick() % MAX_CLIENTS;
3420 UpdateClientRconCommands(ClientId: CommandSendingClientId);
3421 UpdateClientMaplistEntries(ClientId: CommandSendingClientId);
3422
3423 m_Fifo.Update();
3424
3425#if defined(CONF_PLATFORM_ANDROID)
3426 std::vector<std::string> vAndroidCommandQueue = FetchAndroidServerCommandQueue();
3427 for(const std::string &Command : vAndroidCommandQueue)
3428 {
3429 Console()->ExecuteLineFlag(Command.c_str(), CFGFLAG_SERVER, IConsole::CLIENT_ID_UNSPECIFIED);
3430 }
3431#endif
3432
3433 // master server stuff
3434 m_pRegister->Update();
3435
3436 if(m_ServerInfoNeedsUpdate)
3437 {
3438 UpdateServerInfo(Resend: m_ServerInfoNeedsResend);
3439 }
3440
3441 Antibot()->OnEngineTick();
3442
3443 // handle dnsbl
3444 if(Config()->m_SvDnsbl)
3445 {
3446 for(int ClientId = 0; ClientId < MAX_CLIENTS; ClientId++)
3447 {
3448 if(m_aClients[ClientId].m_State == CClient::STATE_EMPTY)
3449 continue;
3450
3451 if(m_aClients[ClientId].m_DnsblState == EDnsblState::NONE)
3452 {
3453 // initiate dnsbl lookup
3454 InitDnsbl(ClientId);
3455 }
3456 else if(m_aClients[ClientId].m_DnsblState == EDnsblState::PENDING &&
3457 m_aClients[ClientId].m_pDnsblLookup->State() == IJob::STATE_DONE)
3458 {
3459 if(m_aClients[ClientId].m_pDnsblLookup->Result() != 0)
3460 {
3461 // entry not found -> whitelisted
3462 m_aClients[ClientId].m_DnsblState = EDnsblState::WHITELISTED;
3463
3464 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "ClientId=%d addr=<{%s}> secure=%s whitelisted", ClientId, ClientAddrString(ClientId, IncludePort: true), m_NetServer.HasSecurityToken(ClientId) ? "yes" : "no");
3465 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "dnsbl", pStr: aBuf);
3466 }
3467 else
3468 {
3469 // entry found -> blacklisted
3470 m_aClients[ClientId].m_DnsblState = EDnsblState::BLACKLISTED;
3471
3472 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "ClientId=%d addr=<{%s}> secure=%s blacklisted", ClientId, ClientAddrString(ClientId, IncludePort: true), m_NetServer.HasSecurityToken(ClientId) ? "yes" : "no");
3473 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "dnsbl", pStr: aBuf);
3474
3475 if(Config()->m_SvDnsblBan)
3476 {
3477 m_NetServer.NetBan()->BanAddr(pAddr: ClientAddr(ClientId), Seconds: 60, pReason: Config()->m_SvDnsblBanReason, VerbatimReason: true);
3478 }
3479 }
3480 }
3481 }
3482 }
3483 for(int i = 0; i < MAX_CLIENTS; ++i)
3484 {
3485 if(m_aClients[i].m_State == CClient::STATE_REDIRECTED)
3486 {
3487 if(time_get() > m_aClients[i].m_RedirectDropTime)
3488 {
3489 m_NetServer.Drop(ClientId: i, pReason: "redirected");
3490 }
3491 }
3492 }
3493 }
3494
3495 if(!NonActive)
3496 PumpNetwork(PacketWaiting);
3497
3498 NonActive = true;
3499 for(const auto &Client : m_aClients)
3500 {
3501 if(Client.m_State != CClient::STATE_EMPTY)
3502 {
3503 NonActive = false;
3504 break;
3505 }
3506 }
3507
3508 if(NonActive)
3509 {
3510 if(Config()->m_SvReloadWhenEmpty == 1)
3511 {
3512 m_MapReload = true;
3513 Config()->m_SvReloadWhenEmpty = 0;
3514 }
3515 else if(Config()->m_SvReloadWhenEmpty == 2 && !m_ReloadedWhenEmpty)
3516 {
3517 m_MapReload = true;
3518 m_ReloadedWhenEmpty = true;
3519 }
3520 }
3521 else
3522 {
3523 m_ReloadedWhenEmpty = false;
3524 }
3525
3526 // wait for incoming data
3527 if(NonActive && Config()->m_SvShutdownWhenEmpty)
3528 {
3529 m_RunServer = STOPPING;
3530 }
3531 else if(NonActive &&
3532 !m_aDemoRecorder[RECORDER_MANUAL].IsRecording() &&
3533 !m_aDemoRecorder[RECORDER_AUTO].IsRecording())
3534 {
3535 PacketWaiting = net_socket_read_wait(sock: m_NetServer.Socket(), nanoseconds: 1s);
3536 }
3537 else
3538 {
3539 set_new_tick();
3540 LastTime = time_get();
3541 const auto MicrosecondsToWait = std::chrono::duration_cast<std::chrono::microseconds>(d: std::chrono::nanoseconds(TickStartTime(Tick: m_CurrentGameTick + 1) - LastTime)) + 1us;
3542 PacketWaiting = MicrosecondsToWait > 0us ? net_socket_read_wait(sock: m_NetServer.Socket(), nanoseconds: MicrosecondsToWait) : true;
3543 }
3544 if(IsInterrupted())
3545 {
3546 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server", pStr: "interrupted");
3547 break;
3548 }
3549 }
3550 }
3551 const char *pDisconnectReason = "Server shutdown";
3552 if(m_aShutdownReason[0])
3553 pDisconnectReason = m_aShutdownReason;
3554
3555 if(ErrorShutdown())
3556 {
3557 log_info("server", "shutdown from game server (%s)", m_aErrorShutdownReason);
3558 pDisconnectReason = m_aErrorShutdownReason;
3559 }
3560 // disconnect all clients on shutdown
3561 for(int i = 0; i < MAX_CLIENTS; ++i)
3562 {
3563 if(m_aClients[i].m_State != CClient::STATE_EMPTY)
3564 m_NetServer.Drop(ClientId: i, pReason: pDisconnectReason);
3565 }
3566
3567 m_pRegister->OnShutdown();
3568 m_Econ.Shutdown();
3569 m_Fifo.Shutdown();
3570 m_pHttp->Shutdown();
3571 Engine()->ShutdownJobs();
3572
3573 GameServer()->OnShutdown(pPersistentData: nullptr);
3574 GameServer()->Map()->Unload();
3575 DbPool()->OnShutdown();
3576
3577#if defined(CONF_UPNP)
3578 m_UPnP.Shutdown();
3579#endif
3580 m_NetServer.Close();
3581
3582 return ErrorShutdown();
3583}
3584
3585void CServer::ConKick(IConsole::IResult *pResult, void *pUser)
3586{
3587 if(pResult->NumArguments() > 1)
3588 {
3589 char aBuf[128];
3590 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Kicked (%s)", pResult->GetString(Index: 1));
3591 ((CServer *)pUser)->Kick(ClientId: pResult->GetVictim(), pReason: aBuf);
3592 }
3593 else
3594 {
3595 ((CServer *)pUser)->Kick(ClientId: pResult->GetVictim(), pReason: "Kicked by console");
3596 }
3597}
3598
3599void CServer::ConStatus(IConsole::IResult *pResult, void *pUser)
3600{
3601 char aBuf[1024];
3602 CServer *pThis = static_cast<CServer *>(pUser);
3603 const char *pName = pResult->NumArguments() == 1 ? pResult->GetString(Index: 0) : "";
3604
3605 for(int i = 0; i < MAX_CLIENTS; i++)
3606 {
3607 if(pThis->m_aClients[i].m_State == CClient::STATE_EMPTY)
3608 continue;
3609
3610 if(!str_utf8_find_nocase(haystack: pThis->m_aClients[i].m_aName, needle: pName))
3611 continue;
3612
3613 if(pThis->m_aClients[i].m_State == CClient::STATE_INGAME)
3614 {
3615 char aDnsblStr[64];
3616 aDnsblStr[0] = '\0';
3617 if(pThis->Config()->m_SvDnsbl)
3618 {
3619 str_format(buffer: aDnsblStr, buffer_size: sizeof(aDnsblStr), format: " dnsbl=%s", DnsblStateStr(State: pThis->m_aClients[i].m_DnsblState));
3620 }
3621
3622 char aAuthStr[128];
3623 aAuthStr[0] = '\0';
3624 if(pThis->m_aClients[i].m_AuthKey >= 0)
3625 {
3626 const char *pAuthStr = "";
3627 const int AuthState = pThis->GetAuthedState(ClientId: i);
3628
3629 if(AuthState == AUTHED_ADMIN)
3630 {
3631 pAuthStr = "(Admin)";
3632 }
3633 else if(AuthState == AUTHED_MOD)
3634 {
3635 pAuthStr = "(Mod)";
3636 }
3637 else if(AuthState == AUTHED_HELPER)
3638 {
3639 pAuthStr = "(Helper)";
3640 }
3641
3642 str_format(buffer: aAuthStr, buffer_size: sizeof(aAuthStr), format: " key='%s' %s", pThis->m_AuthManager.KeyIdent(Slot: pThis->m_aClients[i].m_AuthKey), pAuthStr);
3643 }
3644
3645 const char *pClientPrefix = "";
3646 if(pThis->m_aClients[i].m_Sixup)
3647 {
3648 pClientPrefix = "0.7:";
3649 }
3650 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "id=%d addr=<{%s}> name='%s' client=%s%d secure=%s flags=%d%s%s",
3651 i, pThis->ClientAddrString(ClientId: i, IncludePort: true), pThis->m_aClients[i].m_aName, pClientPrefix, pThis->m_aClients[i].m_DDNetVersion,
3652 pThis->m_NetServer.HasSecurityToken(ClientId: i) ? "yes" : "no", pThis->m_aClients[i].m_Flags, aDnsblStr, aAuthStr);
3653 }
3654 else
3655 {
3656 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "id=%d addr=<{%s}> connecting", i, pThis->ClientAddrString(ClientId: i, IncludePort: true));
3657 }
3658 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server", pStr: aBuf);
3659 }
3660}
3661
3662static int GetAuthLevel(const char *pLevel)
3663{
3664 int Level = -1;
3665 if(!str_comp_nocase(a: pLevel, b: "admin"))
3666 Level = AUTHED_ADMIN;
3667 else if(str_startswith(str: pLevel, prefix: "mod"))
3668 Level = AUTHED_MOD;
3669 else if(!str_comp_nocase(a: pLevel, b: "helper"))
3670 Level = AUTHED_HELPER;
3671
3672 return Level;
3673}
3674
3675bool CServer::CanClientUseCommandCallback(int ClientId, const IConsole::ICommandInfo *pCommand, void *pUser)
3676{
3677 return ((CServer *)pUser)->CanClientUseCommand(ClientId, pCommand);
3678}
3679
3680bool CServer::CanClientUseCommand(int ClientId, const IConsole::ICommandInfo *pCommand) const
3681{
3682 if(pCommand->Flags() & CFGFLAG_CHAT)
3683 return true;
3684 if(pCommand->Flags() & CMDFLAG_PRACTICE)
3685 return true;
3686 if(!IsRconAuthed(ClientId))
3687 return false;
3688 return pCommand->GetAccessLevel() >= ConsoleAccessLevel(ClientId);
3689}
3690
3691void CServer::AuthRemoveKey(int KeySlot)
3692{
3693 m_AuthManager.RemoveKey(Slot: KeySlot);
3694 LogoutKey(Key: KeySlot, pReason: "key removal");
3695
3696 // Update indices.
3697 for(auto &Client : m_aClients)
3698 {
3699 if(Client.m_AuthKey == KeySlot)
3700 {
3701 Client.m_AuthKey = -1;
3702 }
3703 else if(Client.m_AuthKey > KeySlot)
3704 {
3705 --Client.m_AuthKey;
3706 }
3707 }
3708}
3709
3710void CServer::ConAuthAdd(IConsole::IResult *pResult, void *pUser)
3711{
3712 CServer *pThis = (CServer *)pUser;
3713 CAuthManager *pManager = &pThis->m_AuthManager;
3714
3715 const char *pIdent = pResult->GetString(Index: 0);
3716 const char *pLevel = pResult->GetString(Index: 1);
3717 const char *pPw = pResult->GetString(Index: 2);
3718
3719 if(!pManager->IsValidIdent(pIdent))
3720 {
3721 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "ident is invalid");
3722 return;
3723 }
3724
3725 int Level = GetAuthLevel(pLevel);
3726 if(Level == -1)
3727 {
3728 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "level can be one of {\"admin\", \"mod(erator)\", \"helper\"}");
3729 return;
3730 }
3731 // back compat to change "mod", "modder" and so on as parameters to "moderator"
3732 pLevel = CAuthManager::AuthLevelToRoleName(AuthLevel: Level);
3733
3734 bool NeedUpdate = !pManager->NumNonDefaultKeys();
3735 if(pManager->AddKey(pIdent, pPw, pRoleName: pLevel) < 0)
3736 {
3737 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "ident already exists");
3738 }
3739 else
3740 {
3741 if(NeedUpdate)
3742 pThis->SendRconType(ClientId: -1, UsernameReq: true);
3743 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "key added");
3744 }
3745}
3746
3747void CServer::ConAuthAddHashed(IConsole::IResult *pResult, void *pUser)
3748{
3749 CServer *pThis = (CServer *)pUser;
3750 CAuthManager *pManager = &pThis->m_AuthManager;
3751
3752 const char *pIdent = pResult->GetString(Index: 0);
3753 const char *pLevel = pResult->GetString(Index: 1);
3754 const char *pPw = pResult->GetString(Index: 2);
3755 const char *pSalt = pResult->GetString(Index: 3);
3756
3757 if(!pManager->IsValidIdent(pIdent))
3758 {
3759 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "ident is invalid");
3760 return;
3761 }
3762
3763 int Level = GetAuthLevel(pLevel);
3764 if(Level == -1)
3765 {
3766 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "level can be one of {\"admin\", \"mod(erator)\", \"helper\"}");
3767 return;
3768 }
3769 // back compat to change "mod", "modder" and so on as parameters to "moderator"
3770 pLevel = CAuthManager::AuthLevelToRoleName(AuthLevel: Level);
3771
3772 MD5_DIGEST Hash;
3773 unsigned char aSalt[SALT_BYTES];
3774
3775 if(md5_from_str(out: &Hash, str: pPw))
3776 {
3777 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "Malformed password hash");
3778 return;
3779 }
3780 if(str_hex_decode(dst: aSalt, dst_size: sizeof(aSalt), src: pSalt))
3781 {
3782 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "Malformed salt hash");
3783 return;
3784 }
3785
3786 bool NeedUpdate = !pManager->NumNonDefaultKeys();
3787
3788 if(pManager->AddKeyHash(pIdent, Hash, pSalt: aSalt, pRoleName: pLevel) < 0)
3789 {
3790 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "ident already exists");
3791 }
3792 else
3793 {
3794 if(NeedUpdate)
3795 pThis->SendRconType(ClientId: -1, UsernameReq: true);
3796 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "key added");
3797 }
3798}
3799
3800void CServer::ConAuthUpdate(IConsole::IResult *pResult, void *pUser)
3801{
3802 CServer *pThis = (CServer *)pUser;
3803 CAuthManager *pManager = &pThis->m_AuthManager;
3804
3805 const char *pIdent = pResult->GetString(Index: 0);
3806 const char *pLevel = pResult->GetString(Index: 1);
3807 const char *pPw = pResult->GetString(Index: 2);
3808
3809 int KeySlot = pManager->FindKey(pIdent);
3810 if(KeySlot == -1)
3811 {
3812 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "ident couldn't be found");
3813 return;
3814 }
3815
3816 int Level = GetAuthLevel(pLevel);
3817 if(Level == -1)
3818 {
3819 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "level can be one of {\"admin\", \"mod(erator)\", \"helper\"}");
3820 return;
3821 }
3822 // back compat to change "mod", "modder" and so on as parameters to "moderator"
3823 pLevel = CAuthManager::AuthLevelToRoleName(AuthLevel: Level);
3824
3825 pManager->UpdateKey(Slot: KeySlot, pPw, pRoleName: pLevel);
3826 pThis->LogoutKey(Key: KeySlot, pReason: "key update");
3827
3828 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "key updated");
3829}
3830
3831void CServer::ConAuthUpdateHashed(IConsole::IResult *pResult, void *pUser)
3832{
3833 CServer *pThis = (CServer *)pUser;
3834 CAuthManager *pManager = &pThis->m_AuthManager;
3835
3836 const char *pIdent = pResult->GetString(Index: 0);
3837 const char *pLevel = pResult->GetString(Index: 1);
3838 const char *pPw = pResult->GetString(Index: 2);
3839 const char *pSalt = pResult->GetString(Index: 3);
3840
3841 int KeySlot = pManager->FindKey(pIdent);
3842 if(KeySlot == -1)
3843 {
3844 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "ident couldn't be found");
3845 return;
3846 }
3847
3848 int Level = GetAuthLevel(pLevel);
3849 if(Level == -1)
3850 {
3851 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "level can be one of {\"admin\", \"mod(erator)\", \"helper\"}");
3852 return;
3853 }
3854 // back compat to change "mod", "modder" and so on as parameters to "moderator"
3855 pLevel = CAuthManager::AuthLevelToRoleName(AuthLevel: Level);
3856
3857 MD5_DIGEST Hash;
3858 unsigned char aSalt[SALT_BYTES];
3859
3860 if(md5_from_str(out: &Hash, str: pPw))
3861 {
3862 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "Malformed password hash");
3863 return;
3864 }
3865 if(str_hex_decode(dst: aSalt, dst_size: sizeof(aSalt), src: pSalt))
3866 {
3867 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "Malformed salt hash");
3868 return;
3869 }
3870
3871 pManager->UpdateKeyHash(Slot: KeySlot, Hash, pSalt: aSalt, pRoleName: pLevel);
3872 pThis->LogoutKey(Key: KeySlot, pReason: "key update");
3873
3874 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "key updated");
3875}
3876
3877void CServer::ConAuthRemove(IConsole::IResult *pResult, void *pUser)
3878{
3879 CServer *pThis = (CServer *)pUser;
3880 CAuthManager *pManager = &pThis->m_AuthManager;
3881
3882 const char *pIdent = pResult->GetString(Index: 0);
3883
3884 int KeySlot = pManager->FindKey(pIdent);
3885 if(KeySlot == -1)
3886 {
3887 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "ident couldn't be found");
3888 return;
3889 }
3890
3891 pThis->AuthRemoveKey(KeySlot);
3892
3893 if(!pManager->NumNonDefaultKeys())
3894 pThis->SendRconType(ClientId: -1, UsernameReq: false);
3895
3896 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "auth", pStr: "key removed, all users logged out");
3897}
3898
3899static void ListKeysCallback(const char *pIdent, const char *pRoleName, void *pUser)
3900{
3901 log_info("auth", "%s %s", pIdent, pRoleName);
3902}
3903
3904void CServer::ConAuthList(IConsole::IResult *pResult, void *pUser)
3905{
3906 CServer *pThis = (CServer *)pUser;
3907 CAuthManager *pManager = &pThis->m_AuthManager;
3908
3909 pManager->ListKeys(pfnListCallback: ListKeysCallback, pUser: pThis);
3910}
3911
3912void CServer::ConShutdown(IConsole::IResult *pResult, void *pUser)
3913{
3914 CServer *pThis = static_cast<CServer *>(pUser);
3915 pThis->m_RunServer = STOPPING;
3916 const char *pReason = pResult->GetString(Index: 0);
3917 if(pReason[0])
3918 {
3919 str_copy(dst&: pThis->m_aShutdownReason, src: pReason);
3920 }
3921}
3922
3923void CServer::DemoRecorder_HandleAutoStart()
3924{
3925 if(Config()->m_SvAutoDemoRecord)
3926 {
3927 m_aDemoRecorder[RECORDER_AUTO].Stop(Mode: IDemoRecorder::EStopMode::KEEP_FILE);
3928
3929 char aTimestamp[20];
3930 str_timestamp(buffer: aTimestamp, buffer_size: sizeof(aTimestamp));
3931 char aFilename[IO_MAX_PATH_LENGTH];
3932 str_format(buffer: aFilename, buffer_size: sizeof(aFilename), format: "demos/auto/server/%s_%s.demo", GameServer()->Map()->BaseName(), aTimestamp);
3933 m_aDemoRecorder[RECORDER_AUTO].Start(
3934 pStorage: Storage(),
3935 pConsole: m_pConsole,
3936 pFilename: aFilename,
3937 pNetversion: GameServer()->NetVersion(),
3938 pMap: GameServer()->Map()->BaseName(),
3939 Sha256: m_aCurrentMapSha256[MAP_TYPE_SIX],
3940 MapCrc: m_aCurrentMapCrc[MAP_TYPE_SIX],
3941 pType: "server",
3942 MapSize: m_aCurrentMapSize[MAP_TYPE_SIX],
3943 pMapData: m_apCurrentMapData[MAP_TYPE_SIX],
3944 MapFile: nullptr,
3945 pfnFilter: nullptr,
3946 pUser: nullptr);
3947
3948 if(Config()->m_SvAutoDemoMax)
3949 {
3950 // clean up auto recorded demos
3951 CFileCollection AutoDemos;
3952 AutoDemos.Init(pStorage: Storage(), pPath: "demos/auto/server", pFileDesc: "", pFileExt: ".demo", MaxEntries: Config()->m_SvAutoDemoMax);
3953 }
3954 }
3955}
3956
3957void CServer::SaveDemo(int ClientId, float Time)
3958{
3959 if(IsRecording(ClientId))
3960 {
3961 char aNewFilename[IO_MAX_PATH_LENGTH];
3962 str_format(buffer: aNewFilename, buffer_size: sizeof(aNewFilename), format: "demos/%s_%s_%05.2f.demo", GameServer()->Map()->BaseName(), m_aClients[ClientId].m_aName, Time);
3963 m_aDemoRecorder[ClientId].Stop(Mode: IDemoRecorder::EStopMode::KEEP_FILE, pTargetFilename: aNewFilename);
3964 }
3965}
3966
3967void CServer::StartRecord(int ClientId)
3968{
3969 if(Config()->m_SvPlayerDemoRecord)
3970 {
3971 char aFilename[IO_MAX_PATH_LENGTH];
3972 str_format(buffer: aFilename, buffer_size: sizeof(aFilename), format: "demos/%s_%d_%d_tmp.demo", GameServer()->Map()->BaseName(), m_NetServer.Address().port, ClientId);
3973 m_aDemoRecorder[ClientId].Start(
3974 pStorage: Storage(),
3975 pConsole: Console(),
3976 pFilename: aFilename,
3977 pNetversion: GameServer()->NetVersion(),
3978 pMap: GameServer()->Map()->BaseName(),
3979 Sha256: m_aCurrentMapSha256[MAP_TYPE_SIX],
3980 MapCrc: m_aCurrentMapCrc[MAP_TYPE_SIX],
3981 pType: "server",
3982 MapSize: m_aCurrentMapSize[MAP_TYPE_SIX],
3983 pMapData: m_apCurrentMapData[MAP_TYPE_SIX],
3984 MapFile: nullptr,
3985 pfnFilter: nullptr,
3986 pUser: nullptr);
3987 }
3988}
3989
3990void CServer::StopRecord(int ClientId)
3991{
3992 if(IsRecording(ClientId))
3993 {
3994 m_aDemoRecorder[ClientId].Stop(Mode: IDemoRecorder::EStopMode::REMOVE_FILE);
3995 }
3996}
3997
3998bool CServer::IsRecording(int ClientId)
3999{
4000 return m_aDemoRecorder[ClientId].IsRecording();
4001}
4002
4003void CServer::StopDemos()
4004{
4005 for(int i = 0; i < NUM_RECORDERS; i++)
4006 {
4007 if(!m_aDemoRecorder[i].IsRecording())
4008 continue;
4009
4010 m_aDemoRecorder[i].Stop(Mode: i < MAX_CLIENTS ? IDemoRecorder::EStopMode::REMOVE_FILE : IDemoRecorder::EStopMode::KEEP_FILE);
4011 }
4012}
4013
4014void CServer::ConRecord(IConsole::IResult *pResult, void *pUser)
4015{
4016 CServer *pServer = (CServer *)pUser;
4017
4018 if(pServer->IsRecording(ClientId: RECORDER_MANUAL))
4019 {
4020 pServer->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "demo_recorder", pStr: "Demo recorder already recording");
4021 return;
4022 }
4023
4024 char aFilename[IO_MAX_PATH_LENGTH];
4025 if(pResult->NumArguments())
4026 {
4027 str_format(buffer: aFilename, buffer_size: sizeof(aFilename), format: "demos/%s.demo", pResult->GetString(Index: 0));
4028 }
4029 else
4030 {
4031 char aTimestamp[20];
4032 str_timestamp(buffer: aTimestamp, buffer_size: sizeof(aTimestamp));
4033 str_format(buffer: aFilename, buffer_size: sizeof(aFilename), format: "demos/demo_%s.demo", aTimestamp);
4034 }
4035 pServer->m_aDemoRecorder[RECORDER_MANUAL].Start(
4036 pStorage: pServer->Storage(),
4037 pConsole: pServer->Console(),
4038 pFilename: aFilename,
4039 pNetversion: pServer->GameServer()->NetVersion(),
4040 pMap: pServer->GameServer()->Map()->BaseName(),
4041 Sha256: pServer->m_aCurrentMapSha256[MAP_TYPE_SIX],
4042 MapCrc: pServer->m_aCurrentMapCrc[MAP_TYPE_SIX],
4043 pType: "server",
4044 MapSize: pServer->m_aCurrentMapSize[MAP_TYPE_SIX],
4045 pMapData: pServer->m_apCurrentMapData[MAP_TYPE_SIX],
4046 MapFile: nullptr,
4047 pfnFilter: nullptr,
4048 pUser: nullptr);
4049}
4050
4051void CServer::ConStopRecord(IConsole::IResult *pResult, void *pUser)
4052{
4053 ((CServer *)pUser)->m_aDemoRecorder[RECORDER_MANUAL].Stop(Mode: IDemoRecorder::EStopMode::KEEP_FILE);
4054}
4055
4056void CServer::ConMapReload(IConsole::IResult *pResult, void *pUser)
4057{
4058 ((CServer *)pUser)->ReloadMap();
4059}
4060
4061void CServer::ConLogout(IConsole::IResult *pResult, void *pUser)
4062{
4063 CServer *pServer = (CServer *)pUser;
4064
4065 if(pServer->m_RconClientId >= 0 && pServer->m_RconClientId < MAX_CLIENTS &&
4066 pServer->m_aClients[pServer->m_RconClientId].m_State != CServer::CClient::STATE_EMPTY)
4067 {
4068 pServer->LogoutClient(ClientId: pServer->m_RconClientId, pReason: "");
4069 }
4070}
4071
4072void CServer::ConShowIps(IConsole::IResult *pResult, void *pUser)
4073{
4074 CServer *pServer = (CServer *)pUser;
4075
4076 if(pServer->m_RconClientId >= 0 && pServer->m_RconClientId < MAX_CLIENTS &&
4077 pServer->m_aClients[pServer->m_RconClientId].m_State != CServer::CClient::STATE_EMPTY)
4078 {
4079 if(pResult->NumArguments())
4080 {
4081 pServer->m_aClients[pServer->m_RconClientId].m_ShowIps = pResult->GetInteger(Index: 0);
4082 }
4083 else
4084 {
4085 char aStr[9];
4086 str_format(buffer: aStr, buffer_size: sizeof(aStr), format: "Value: %d", pServer->m_aClients[pServer->m_RconClientId].m_ShowIps);
4087 pServer->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server", pStr: aStr);
4088 }
4089 }
4090}
4091
4092void CServer::ConHideAuthStatus(IConsole::IResult *pResult, void *pUser)
4093{
4094 CServer *pServer = (CServer *)pUser;
4095
4096 if(pServer->m_RconClientId >= 0 && pServer->m_RconClientId < MAX_CLIENTS &&
4097 pServer->m_aClients[pServer->m_RconClientId].m_State != CServer::CClient::STATE_EMPTY)
4098 {
4099 if(pResult->NumArguments())
4100 {
4101 pServer->m_aClients[pServer->m_RconClientId].m_AuthHidden = pResult->GetInteger(Index: 0);
4102 }
4103 else
4104 {
4105 char aStr[9];
4106 str_format(buffer: aStr, buffer_size: sizeof(aStr), format: "Value: %d", pServer->m_aClients[pServer->m_RconClientId].m_AuthHidden);
4107 pServer->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server", pStr: aStr);
4108 }
4109 }
4110}
4111
4112void CServer::ConForceHighBandwidthOnSpectate(IConsole::IResult *pResult, void *pUser)
4113{
4114 CServer *pServer = (CServer *)pUser;
4115
4116 if(pServer->m_RconClientId >= 0 && pServer->m_RconClientId < MAX_CLIENTS &&
4117 pServer->m_aClients[pServer->m_RconClientId].m_State != CServer::CClient::STATE_EMPTY)
4118 {
4119 if(pResult->NumArguments())
4120 {
4121 pServer->m_aClients[pServer->m_RconClientId].m_ForceHighBandwidthOnSpectate = pResult->GetInteger(Index: 0);
4122 }
4123 else
4124 {
4125 char aStr[9];
4126 str_format(buffer: aStr, buffer_size: sizeof(aStr), format: "Value: %d", pServer->m_aClients[pServer->m_RconClientId].m_ForceHighBandwidthOnSpectate);
4127 pServer->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server", pStr: aStr);
4128 }
4129 }
4130}
4131
4132void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData)
4133{
4134 CServer *pSelf = (CServer *)pUserData;
4135
4136 if(!MysqlAvailable())
4137 {
4138 log_error("server", "can't add MySQL server: compiled without MySQL support");
4139 return;
4140 }
4141
4142 if(!pSelf->Config()->m_SvUseSql)
4143 return;
4144
4145 if(pResult->NumArguments() < 7 || pResult->NumArguments() > 9)
4146 {
4147 log_error("server", "7 to 9 arguments are required");
4148 return;
4149 }
4150
4151 CMysqlConfig Config;
4152 bool Write;
4153 if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "r") == 0)
4154 {
4155 Write = false;
4156 }
4157 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "w") == 0)
4158 {
4159 Write = true;
4160 }
4161 else
4162 {
4163 log_error("server", "choose either 'r' for SqlReadServer or 'w' for SqlWriteServer");
4164 return;
4165 }
4166
4167 str_copy(dst&: Config.m_aDatabase, src: pResult->GetString(Index: 1));
4168 str_copy(dst&: Config.m_aPrefix, src: pResult->GetString(Index: 2));
4169 str_copy(dst&: Config.m_aUser, src: pResult->GetString(Index: 3));
4170 str_copy(dst&: Config.m_aPass, src: pResult->GetString(Index: 4));
4171 str_copy(dst&: Config.m_aIp, src: pResult->GetString(Index: 5));
4172 Config.m_aBindaddr[0] = '\0';
4173 Config.m_Port = pResult->GetInteger(Index: 6);
4174 Config.m_Setup = pResult->NumArguments() >= 8 ? pResult->GetInteger(Index: 7) : true;
4175 Config.m_UseSsl = pResult->NumArguments() >= 9 ? pResult->GetInteger(Index: 8) != 0 : false;
4176 str_copy(dst&: Config.m_aSslCa, src: g_Config.m_SvSqlSslCa);
4177 str_copy(dst&: Config.m_aSslCert, src: g_Config.m_SvSqlSslCert);
4178 str_copy(dst&: Config.m_aSslKey, src: g_Config.m_SvSqlSslKey);
4179
4180 log_info("server",
4181 "Adding new Sql%sServer: DB: '%s' Prefix: '%s' User: '%s' IP: <{%s}> Port: %d",
4182 Write ? "Write" : "Read",
4183 Config.m_aDatabase, Config.m_aPrefix, Config.m_aUser, Config.m_aIp, Config.m_Port);
4184 pSelf->DbPool()->RegisterMysqlDatabase(DatabaseMode: Write ? CDbConnectionPool::WRITE : CDbConnectionPool::READ, pMysqlConfig: &Config);
4185}
4186
4187void CServer::ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData)
4188{
4189 CServer *pSelf = (CServer *)pUserData;
4190
4191 if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "w") == 0)
4192 {
4193 pSelf->DbPool()->Print(DatabaseMode: CDbConnectionPool::WRITE);
4194 pSelf->DbPool()->Print(DatabaseMode: CDbConnectionPool::WRITE_BACKUP);
4195 }
4196 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "r") == 0)
4197 {
4198 pSelf->DbPool()->Print(DatabaseMode: CDbConnectionPool::READ);
4199 }
4200 else
4201 {
4202 log_error("server", "choose either 'r' for SqlReadServer or 'w' for SqlWriteServer");
4203 return;
4204 }
4205}
4206
4207void CServer::ConReloadAnnouncement(IConsole::IResult *pResult, void *pUserData)
4208{
4209 CServer *pThis = static_cast<CServer *>(pUserData);
4210 pThis->ReadAnnouncementsFile();
4211}
4212
4213void CServer::ConReloadMaplist(IConsole::IResult *pResult, void *pUserData)
4214{
4215 CServer *pThis = static_cast<CServer *>(pUserData);
4216 pThis->InitMaplist();
4217}
4218
4219void CServer::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
4220{
4221 pfnCallback(pResult, pCallbackUserData);
4222 if(pResult->NumArguments())
4223 {
4224 CServer *pThis = static_cast<CServer *>(pUserData);
4225 str_clean_whitespaces(str: pThis->Config()->m_SvName);
4226 pThis->ExpireServerInfoAndQueueResend();
4227 }
4228}
4229
4230void CServer::ConchainMaxclientsperipUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
4231{
4232 pfnCallback(pResult, pCallbackUserData);
4233 if(pResult->NumArguments())
4234 ((CServer *)pUserData)->m_NetServer.SetMaxClientsPerIp(pResult->GetInteger(Index: 0));
4235}
4236
4237void CServer::ConchainCommandAccessUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
4238{
4239 if(pResult->NumArguments() == 2)
4240 {
4241 CServer *pThis = static_cast<CServer *>(pUserData);
4242 const IConsole::ICommandInfo *pInfo = pThis->Console()->GetCommandInfo(pName: pResult->GetString(Index: 0), FlagMask: CFGFLAG_SERVER, Temp: false);
4243 IConsole::EAccessLevel OldAccessLevel = IConsole::EAccessLevel::ADMIN;
4244 if(pInfo)
4245 OldAccessLevel = pInfo->GetAccessLevel();
4246 pfnCallback(pResult, pCallbackUserData);
4247 if(pInfo && OldAccessLevel != pInfo->GetAccessLevel())
4248 {
4249 for(int i = 0; i < MAX_CLIENTS; ++i)
4250 {
4251 if(pThis->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY)
4252 continue;
4253 if(!pThis->IsRconAuthed(ClientId: i))
4254 continue;
4255
4256 const IConsole::EAccessLevel ClientAccessLevel = pThis->ConsoleAccessLevel(ClientId: i);
4257 bool HadAccess = OldAccessLevel >= ClientAccessLevel;
4258 bool HasAccess = pInfo->GetAccessLevel() >= ClientAccessLevel;
4259
4260 // Nothing changed
4261 if(HadAccess == HasAccess)
4262 continue;
4263 // Command not sent yet. The sending will happen in alphabetical order with correctly updated permissions.
4264 if(pThis->m_aClients[i].m_pRconCmdToSend && str_comp(a: pResult->GetString(Index: 0), b: pThis->m_aClients[i].m_pRconCmdToSend->Name()) >= 0)
4265 continue;
4266
4267 if(HasAccess)
4268 pThis->SendRconCmdAdd(pCommandInfo: pInfo, ClientId: i);
4269 else
4270 pThis->SendRconCmdRem(pCommandInfo: pInfo, ClientId: i);
4271 }
4272 }
4273 }
4274 else
4275 {
4276 pfnCallback(pResult, pCallbackUserData);
4277 }
4278}
4279
4280void CServer::LogoutClient(int ClientId, const char *pReason)
4281{
4282 if(!IsSixup(ClientId))
4283 {
4284 CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS, true);
4285 Msg.AddInt(i: 0); //authed
4286 Msg.AddInt(i: 0); //cmdlist
4287 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId);
4288 }
4289 else
4290 {
4291 CMsgPacker Msg(protocol7::NETMSG_RCON_AUTH_OFF, true, true);
4292 SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId);
4293 }
4294
4295 m_aClients[ClientId].m_AuthTries = 0;
4296 m_aClients[ClientId].m_pRconCmdToSend = nullptr;
4297 m_aClients[ClientId].m_MaplistEntryToSend = CClient::MAPLIST_UNINITIALIZED;
4298
4299 if(*pReason)
4300 {
4301 char aBuf[64];
4302 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Logged out by %s.", pReason);
4303 SendRconLine(ClientId, pLine: aBuf);
4304 log_info("server", "ClientId=%d with key='%s' logged out by %s", ClientId, m_AuthManager.KeyIdent(m_aClients[ClientId].m_AuthKey), pReason);
4305 }
4306 else
4307 {
4308 SendRconLine(ClientId, pLine: "Logout successful.");
4309 log_info("server", "ClientId=%d with key='%s' logged out", ClientId, m_AuthManager.KeyIdent(m_aClients[ClientId].m_AuthKey));
4310 }
4311
4312 m_aClients[ClientId].m_AuthKey = -1;
4313
4314 GameServer()->OnSetAuthed(ClientId, Level: AUTHED_NO);
4315}
4316
4317void CServer::LogoutKey(int Key, const char *pReason)
4318{
4319 for(int i = 0; i < MAX_CLIENTS; i++)
4320 if(m_aClients[i].m_AuthKey == Key)
4321 LogoutClient(ClientId: i, pReason);
4322}
4323
4324void CServer::ConchainRconPasswordChangeGeneric(const char *pRoleName, const char *pCurrent, IConsole::IResult *pResult)
4325{
4326 if(pResult->NumArguments() == 1)
4327 {
4328 int KeySlot = m_AuthManager.DefaultKey(pRoleName);
4329 const char *pNew = pResult->GetString(Index: 0);
4330 if(str_comp(a: pCurrent, b: pNew) == 0)
4331 {
4332 return;
4333 }
4334 if(KeySlot == -1 && pNew[0])
4335 {
4336 m_AuthManager.AddDefaultKey(pRoleName, pPw: pNew);
4337 }
4338 else if(KeySlot >= 0)
4339 {
4340 if(!pNew[0])
4341 {
4342 AuthRemoveKey(KeySlot);
4343 // Already logs users out.
4344 }
4345 else
4346 {
4347 m_AuthManager.UpdateKey(Slot: KeySlot, pPw: pNew, pRoleName);
4348 LogoutKey(Key: KeySlot, pReason: "key update");
4349 }
4350 }
4351 }
4352}
4353
4354void CServer::ConchainRconPasswordChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
4355{
4356 CServer *pThis = static_cast<CServer *>(pUserData);
4357 pThis->ConchainRconPasswordChangeGeneric(pRoleName: RoleName::ADMIN, pCurrent: pThis->Config()->m_SvRconPassword, pResult);
4358 pfnCallback(pResult, pCallbackUserData);
4359}
4360
4361void CServer::ConchainRconModPasswordChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
4362{
4363 CServer *pThis = static_cast<CServer *>(pUserData);
4364 pThis->ConchainRconPasswordChangeGeneric(pRoleName: RoleName::MODERATOR, pCurrent: pThis->Config()->m_SvRconModPassword, pResult);
4365 pfnCallback(pResult, pCallbackUserData);
4366}
4367
4368void CServer::ConchainRconHelperPasswordChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
4369{
4370 CServer *pThis = static_cast<CServer *>(pUserData);
4371 pThis->ConchainRconPasswordChangeGeneric(pRoleName: RoleName::HELPER, pCurrent: pThis->Config()->m_SvRconHelperPassword, pResult);
4372 pfnCallback(pResult, pCallbackUserData);
4373}
4374
4375void CServer::ConchainMapUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
4376{
4377 CServer *pThis = static_cast<CServer *>(pUserData);
4378 pfnCallback(pResult, pCallbackUserData);
4379 if(pResult->NumArguments() >= 1 && pThis->GameServer()->Map()->IsLoaded())
4380 {
4381 pThis->m_MapReload = str_comp(a: pThis->Config()->m_SvMap, b: pThis->GameServer()->Map()->FullName()) != 0;
4382 }
4383}
4384
4385void CServer::ConchainSixupUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
4386{
4387 pfnCallback(pResult, pCallbackUserData);
4388 CServer *pThis = static_cast<CServer *>(pUserData);
4389 if(pResult->NumArguments() >= 1 && pThis->GameServer()->Map()->IsLoaded())
4390 {
4391 pThis->m_MapReload |= (pThis->m_apCurrentMapData[MAP_TYPE_SIXUP] != nullptr) != (pResult->GetInteger(Index: 0) != 0);
4392 }
4393}
4394
4395void CServer::ConchainRegisterCommunityTokenRedact(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
4396{
4397 // community tokens look like this:
4398 // ddtc_6DnZq5Ix0J2kvDHbkPNtb6bsZxOVQg4ly2jw. The first 11 bytes are
4399 // shared between the token and the verification token, so they're
4400 // semi-public. Redact everything beyond that point.
4401 static constexpr int REDACT_FROM = 11;
4402 if(pResult->NumArguments() == 0 && str_length(str: g_Config.m_SvRegisterCommunityToken) > REDACT_FROM)
4403 {
4404 char aTruncated[16];
4405 str_truncate(dst: aTruncated, dst_size: sizeof(aTruncated), src: g_Config.m_SvRegisterCommunityToken, truncation_len: REDACT_FROM);
4406 log_info("config", "Value: %s[REDACTED] (total length %d)", aTruncated, str_length(g_Config.m_SvRegisterCommunityToken));
4407 return;
4408 }
4409 pfnCallback(pResult, pCallbackUserData);
4410}
4411
4412void CServer::ConchainLoglevel(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
4413{
4414 CServer *pSelf = (CServer *)pUserData;
4415 pfnCallback(pResult, pCallbackUserData);
4416 if(pResult->NumArguments())
4417 {
4418 pSelf->m_pFileLogger->SetFilter(CLogFilter{.m_MaxLevel: IConsole::ToLogLevelFilter(ConsoleLevel: g_Config.m_Loglevel)});
4419 }
4420}
4421
4422void CServer::ConchainStdoutOutputLevel(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
4423{
4424 CServer *pSelf = (CServer *)pUserData;
4425 pfnCallback(pResult, pCallbackUserData);
4426 if(pResult->NumArguments() && pSelf->m_pStdoutLogger)
4427 {
4428 pSelf->m_pStdoutLogger->SetFilter(CLogFilter{.m_MaxLevel: IConsole::ToLogLevelFilter(ConsoleLevel: g_Config.m_StdoutOutputLevel)});
4429 }
4430}
4431
4432void CServer::ConchainAnnouncementFilename(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
4433{
4434 CServer *pSelf = (CServer *)pUserData;
4435 bool Changed = pResult->NumArguments() && str_comp(a: pResult->GetString(Index: 0), b: g_Config.m_SvAnnouncementFilename);
4436 pfnCallback(pResult, pCallbackUserData);
4437 if(Changed)
4438 {
4439 pSelf->ReadAnnouncementsFile();
4440 }
4441}
4442
4443void CServer::ConchainInputFifo(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
4444{
4445 CServer *pSelf = (CServer *)pUserData;
4446 pfnCallback(pResult, pCallbackUserData);
4447 if(pSelf->m_Fifo.IsInit())
4448 {
4449 pSelf->m_Fifo.Shutdown();
4450 pSelf->m_Fifo.Init(pConsole: pSelf->Console(), pFifoFile: pSelf->Config()->m_SvInputFifo, Flag: CFGFLAG_SERVER);
4451 }
4452}
4453
4454#if defined(CONF_FAMILY_UNIX)
4455void CServer::ConchainConnLoggingServerChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
4456{
4457 pfnCallback(pResult, pCallbackUserData);
4458 if(pResult->NumArguments() == 1)
4459 {
4460 CServer *pServer = (CServer *)pUserData;
4461
4462 // open socket to send new connections
4463 if(!pServer->m_ConnLoggingSocketCreated)
4464 {
4465 pServer->m_ConnLoggingSocket = net_unix_create_unnamed();
4466 if(pServer->m_ConnLoggingSocket == -1)
4467 {
4468 pServer->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server", pStr: "Failed to created socket for communication with the connection logging server.");
4469 }
4470 else
4471 {
4472 pServer->m_ConnLoggingSocketCreated = true;
4473 }
4474 }
4475
4476 // set the destination address for the connection logging
4477 net_unix_set_addr(addr: &pServer->m_ConnLoggingDestAddr, path: pResult->GetString(Index: 0));
4478 }
4479}
4480#endif
4481
4482void CServer::RegisterCommands()
4483{
4484 m_pConsole = Kernel()->RequestInterface<IConsole>();
4485 m_pGameServer = Kernel()->RequestInterface<IGameServer>();
4486 m_pHttp = Kernel()->RequestInterface<IEngineHttp>();
4487 m_pStorage = Kernel()->RequestInterface<IStorage>();
4488 m_pAntibot = Kernel()->RequestInterface<IEngineAntibot>();
4489
4490 // register console commands
4491 Console()->Register(pName: "kick", pParams: "v[id] ?r[reason]", Flags: CFGFLAG_SERVER, pfnFunc: ConKick, pUser: this, pHelp: "Kick player with specified id for any reason");
4492 Console()->Register(pName: "status", pParams: "?r[name]", Flags: CFGFLAG_SERVER, pfnFunc: ConStatus, pUser: this, pHelp: "List players containing name or all players");
4493 Console()->Register(pName: "shutdown", pParams: "?r[reason]", Flags: CFGFLAG_SERVER, pfnFunc: ConShutdown, pUser: this, pHelp: "Shut down");
4494 Console()->Register(pName: "logout", pParams: "", Flags: CFGFLAG_SERVER, pfnFunc: ConLogout, pUser: this, pHelp: "Logout of rcon");
4495 Console()->Register(pName: "show_ips", pParams: "?i[show]", Flags: CFGFLAG_SERVER, pfnFunc: ConShowIps, pUser: this, pHelp: "Show IP addresses in rcon commands (1 = on, 0 = off)");
4496 Console()->Register(pName: "hide_auth_status", pParams: "?i[hide]", Flags: CFGFLAG_SERVER, pfnFunc: ConHideAuthStatus, pUser: this, pHelp: "Opt out of spectator count and hide auth status to non-authed players (1 = hidden, 0 = shown)");
4497 Console()->Register(pName: "force_high_bandwidth_on_spectate", pParams: "?i[enable]", Flags: CFGFLAG_SERVER, pfnFunc: ConForceHighBandwidthOnSpectate, pUser: this, pHelp: "Force high bandwidth mode when spectating (1 = on, 0 = off)");
4498
4499 Console()->Register(pName: "record", pParams: "?s[file]", Flags: CFGFLAG_SERVER | CFGFLAG_STORE, pfnFunc: ConRecord, pUser: this, pHelp: "Record to a file");
4500 Console()->Register(pName: "stoprecord", pParams: "", Flags: CFGFLAG_SERVER, pfnFunc: ConStopRecord, pUser: this, pHelp: "Stop recording");
4501
4502 Console()->Register(pName: "reload", pParams: "", Flags: CFGFLAG_SERVER, pfnFunc: ConMapReload, pUser: this, pHelp: "Reload the map");
4503
4504 Console()->Register(pName: "add_sqlserver", pParams: "s['r'|'w'] s[Database] s[Prefix] s[User] s[Password] s[IP] i[Port] ?i[SetUpDatabase ?] ?i[SSL ?]", Flags: CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, pfnFunc: ConAddSqlServer, pUser: this, pHelp: "add a sqlserver");
4505 Console()->Register(pName: "dump_sqlservers", pParams: "s['r'|'w']", Flags: CFGFLAG_SERVER, pfnFunc: ConDumpSqlServers, pUser: this, pHelp: "dumps all sqlservers readservers = r, writeservers = w");
4506
4507 Console()->Register(pName: "auth_add", pParams: "s[ident] s[level] r[pw]", Flags: CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, pfnFunc: ConAuthAdd, pUser: this, pHelp: "Add a rcon key");
4508 Console()->Register(pName: "auth_add_p", pParams: "s[ident] s[level] s[hash] s[salt]", Flags: CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, pfnFunc: ConAuthAddHashed, pUser: this, pHelp: "Add a prehashed rcon key");
4509 Console()->Register(pName: "auth_change", pParams: "s[ident] s[level] r[pw]", Flags: CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, pfnFunc: ConAuthUpdate, pUser: this, pHelp: "Update a rcon key");
4510 Console()->Register(pName: "auth_change_p", pParams: "s[ident] s[level] s[hash] s[salt]", Flags: CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, pfnFunc: ConAuthUpdateHashed, pUser: this, pHelp: "Update a rcon key with prehashed data");
4511 Console()->Register(pName: "auth_remove", pParams: "s[ident]", Flags: CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, pfnFunc: ConAuthRemove, pUser: this, pHelp: "Remove a rcon key");
4512 Console()->Register(pName: "auth_list", pParams: "", Flags: CFGFLAG_SERVER, pfnFunc: ConAuthList, pUser: this, pHelp: "List all rcon keys");
4513
4514 Console()->Register(pName: "reload_announcement", pParams: "", Flags: CFGFLAG_SERVER, pfnFunc: ConReloadAnnouncement, pUser: this, pHelp: "Reload the announcements");
4515 Console()->Register(pName: "reload_maplist", pParams: "", Flags: CFGFLAG_SERVER, pfnFunc: ConReloadMaplist, pUser: this, pHelp: "Reload the maplist");
4516
4517 RustVersionRegister(console&: *Console());
4518
4519 Console()->Chain(pName: "sv_name", pfnChainFunc: ConchainSpecialInfoupdate, pUser: this);
4520 Console()->Chain(pName: "password", pfnChainFunc: ConchainSpecialInfoupdate, pUser: this);
4521 Console()->Chain(pName: "sv_reserved_slots", pfnChainFunc: ConchainSpecialInfoupdate, pUser: this);
4522 Console()->Chain(pName: "sv_spectator_slots", pfnChainFunc: ConchainSpecialInfoupdate, pUser: this);
4523
4524 Console()->Chain(pName: "sv_max_clients_per_ip", pfnChainFunc: ConchainMaxclientsperipUpdate, pUser: this);
4525 Console()->Chain(pName: "access_level", pfnChainFunc: ConchainCommandAccessUpdate, pUser: this);
4526
4527 Console()->Chain(pName: "sv_rcon_password", pfnChainFunc: ConchainRconPasswordChange, pUser: this);
4528 Console()->Chain(pName: "sv_rcon_mod_password", pfnChainFunc: ConchainRconModPasswordChange, pUser: this);
4529 Console()->Chain(pName: "sv_rcon_helper_password", pfnChainFunc: ConchainRconHelperPasswordChange, pUser: this);
4530 Console()->Chain(pName: "sv_map", pfnChainFunc: ConchainMapUpdate, pUser: this);
4531 Console()->Chain(pName: "sv_sixup", pfnChainFunc: ConchainSixupUpdate, pUser: this);
4532 Console()->Chain(pName: "sv_register_community_token", pfnChainFunc: ConchainRegisterCommunityTokenRedact, pUser: nullptr);
4533
4534 Console()->Chain(pName: "loglevel", pfnChainFunc: ConchainLoglevel, pUser: this);
4535 Console()->Chain(pName: "stdout_output_level", pfnChainFunc: ConchainStdoutOutputLevel, pUser: this);
4536
4537 Console()->Chain(pName: "sv_announcement_filename", pfnChainFunc: ConchainAnnouncementFilename, pUser: this);
4538
4539 Console()->Chain(pName: "sv_input_fifo", pfnChainFunc: ConchainInputFifo, pUser: this);
4540
4541#if defined(CONF_FAMILY_UNIX)
4542 Console()->Chain(pName: "sv_conn_logging_server", pfnChainFunc: ConchainConnLoggingServerChange, pUser: this);
4543#endif
4544
4545 // register console commands in sub parts
4546 m_ServerBan.InitServerBan(pConsole: Console(), pStorage: Storage(), pServer: this);
4547 m_NameBans.InitConsole(pConsole: Console());
4548 m_pGameServer->OnConsoleInit();
4549 Console()->SetCanUseCommandCallback(pfnCallback: CanClientUseCommandCallback, pUser: this);
4550}
4551
4552std::optional<int> CServer::SnapNewId()
4553{
4554 return m_IdPool.NewId();
4555}
4556
4557void CServer::SnapFreeId(int Id)
4558{
4559 m_IdPool.FreeId(Id);
4560}
4561
4562bool CServer::SnapNewItem(int Type, int Id, const void *pData, int Size)
4563{
4564 return m_SnapshotBuilder.NewItem(Type, Id, pData, Size);
4565}
4566
4567void CServer::SnapSetStaticsize(int ItemType, int Size)
4568{
4569 m_SnapshotDelta.SetStaticsize(ItemType, Size);
4570}
4571
4572void CServer::SnapSetStaticsize7(int ItemType, int Size)
4573{
4574 m_SnapshotDeltaSixup.SetStaticsize(ItemType, Size);
4575}
4576
4577CServer *CreateServer() { return new CServer(); }
4578
4579// DDRace
4580
4581void CServer::ReadAnnouncementsFile()
4582{
4583 m_vAnnouncements.clear();
4584
4585 if(g_Config.m_SvAnnouncementFilename[0] == '\0')
4586 return;
4587
4588 CLineReader LineReader;
4589 if(!LineReader.OpenFile(File: m_pStorage->OpenFile(pFilename: g_Config.m_SvAnnouncementFilename, Flags: IOFLAG_READ, Type: IStorage::TYPE_ALL)))
4590 {
4591 log_error("server", "Failed load announcements from '%s'", g_Config.m_SvAnnouncementFilename);
4592 return;
4593 }
4594 while(const char *pLine = LineReader.Get())
4595 {
4596 if(str_length(str: pLine) && pLine[0] != '#')
4597 {
4598 m_vAnnouncements.emplace_back(args&: pLine);
4599 }
4600 }
4601 log_info("server", "Loaded %" PRIzu " announcements", m_vAnnouncements.size());
4602}
4603
4604const char *CServer::GetAnnouncementLine()
4605{
4606 if(m_vAnnouncements.empty())
4607 {
4608 return nullptr;
4609 }
4610 else if(m_vAnnouncements.size() == 1)
4611 {
4612 m_AnnouncementLastLine = 0;
4613 }
4614 else if(!g_Config.m_SvAnnouncementRandom)
4615 {
4616 if(++m_AnnouncementLastLine >= m_vAnnouncements.size())
4617 m_AnnouncementLastLine %= m_vAnnouncements.size();
4618 }
4619 else
4620 {
4621 unsigned Rand;
4622 do
4623 {
4624 Rand = rand() % m_vAnnouncements.size();
4625 } while(Rand == m_AnnouncementLastLine);
4626
4627 m_AnnouncementLastLine = Rand;
4628 }
4629
4630 return m_vAnnouncements[m_AnnouncementLastLine].c_str();
4631}
4632
4633struct CSubdirCallbackUserdata
4634{
4635 CServer *m_pServer;
4636 char m_aCurrentFolder[IO_MAX_PATH_LENGTH];
4637};
4638
4639int CServer::MaplistEntryCallback(const char *pFilename, int IsDir, int DirType, void *pUser)
4640{
4641 CSubdirCallbackUserdata *pUserdata = static_cast<CSubdirCallbackUserdata *>(pUser);
4642 CServer *pThis = pUserdata->m_pServer;
4643
4644 if(str_comp(a: pFilename, b: ".") == 0 || str_comp(a: pFilename, b: "..") == 0)
4645 return 0;
4646
4647 char aFilename[IO_MAX_PATH_LENGTH];
4648 if(pUserdata->m_aCurrentFolder[0] != '\0')
4649 str_format(buffer: aFilename, buffer_size: sizeof(aFilename), format: "%s/%s", pUserdata->m_aCurrentFolder, pFilename);
4650 else
4651 str_copy(dst&: aFilename, src: pFilename);
4652
4653 if(IsDir)
4654 {
4655 CSubdirCallbackUserdata Userdata;
4656 Userdata.m_pServer = pThis;
4657 str_copy(dst&: Userdata.m_aCurrentFolder, src: aFilename);
4658 char aFindPath[IO_MAX_PATH_LENGTH];
4659 str_format(buffer: aFindPath, buffer_size: sizeof(aFindPath), format: "maps/%s/", aFilename);
4660 pThis->Storage()->ListDirectory(Type: IStorage::TYPE_ALL, pPath: aFindPath, pfnCallback: MaplistEntryCallback, pUser: &Userdata);
4661 return 0;
4662 }
4663
4664 const char *pSuffix = str_endswith(str: aFilename, suffix: ".map");
4665 if(!pSuffix) // not ending with .map
4666 return 0;
4667 const size_t FilenameLength = pSuffix - aFilename;
4668 aFilename[FilenameLength] = '\0'; // remove suffix
4669 if(FilenameLength >= sizeof(CMaplistEntry().m_aName)) // name too long
4670 return 0;
4671
4672 pThis->m_vMaplistEntries.emplace_back(args&: aFilename);
4673 return 0;
4674}
4675
4676void CServer::InitMaplist()
4677{
4678 m_vMaplistEntries.clear();
4679
4680 CSubdirCallbackUserdata Userdata;
4681 Userdata.m_pServer = this;
4682 Userdata.m_aCurrentFolder[0] = '\0';
4683 Storage()->ListDirectory(Type: IStorage::TYPE_ALL, pPath: "maps/", pfnCallback: MaplistEntryCallback, pUser: &Userdata);
4684
4685 std::sort(first: m_vMaplistEntries.begin(), last: m_vMaplistEntries.end());
4686 log_info("server", "Found %d maps for maplist", (int)m_vMaplistEntries.size());
4687
4688 for(CClient &Client : m_aClients)
4689 {
4690 if(Client.m_State != CClient::STATE_INGAME)
4691 continue;
4692
4693 // Resend maplist to clients that already got it or are currently getting it
4694 if(Client.m_MaplistEntryToSend == CClient::MAPLIST_DONE || Client.m_MaplistEntryToSend >= 0)
4695 {
4696 Client.m_MaplistEntryToSend = CClient::MAPLIST_UNINITIALIZED;
4697 }
4698 }
4699}
4700
4701int *CServer::GetIdMap(int ClientId)
4702{
4703 return m_aClients[ClientId].m_aIdMap;
4704}
4705
4706int *CServer::GetReverseIdMap(int ClientId)
4707{
4708 return m_aClients[ClientId].m_aReverseIdMap;
4709}
4710
4711bool CServer::SetTimedOut(int ClientId, int OrigId)
4712{
4713 if(!m_NetServer.HasErrored(ClientId))
4714 {
4715 return false;
4716 }
4717
4718 // The login was on the current conn, logout should also be on the current conn
4719 if(IsRconAuthed(ClientId: OrigId))
4720 {
4721 LogoutClient(ClientId: OrigId, pReason: "Timeout Protection");
4722 }
4723
4724 m_NetServer.ResumeOldConnection(ClientId, OrigId);
4725
4726 m_aClients[ClientId].m_Sixup = m_aClients[OrigId].m_Sixup;
4727 m_aClients[ClientId].m_AuthKey = -1;
4728 m_aClients[ClientId].m_Flags = m_aClients[OrigId].m_Flags;
4729 m_aClients[ClientId].m_DDNetVersion = m_aClients[OrigId].m_DDNetVersion;
4730 m_aClients[ClientId].m_GotDDNetVersionPacket = m_aClients[OrigId].m_GotDDNetVersionPacket;
4731 m_aClients[ClientId].m_DDNetVersionSettled = m_aClients[OrigId].m_DDNetVersionSettled;
4732
4733 DelClientCallback(ClientId: OrigId, pReason: "Timeout Protection used", pUser: this);
4734
4735 // OnSetTimedOut must be called after DelClientCallback to preserve the client id.
4736 // The order is important for the player initialization algorithm in CPlayerMapping::CPlayerMap::InitPlayer
4737 // because it loops over all players to find others with the same ip address.
4738 // IP matching is important for hammerfly/dummy copy to work by guaran-tee-ing dummy and player map have the same ids
4739 // Never forget: 0.7 really implemented netmsgs for join/leave, means client ids have to be stable across using timeout protection.
4740 // When InitPlayer runs it has to assign the same client id as before since local id cant be changed in 0.7
4741 GameServer()->OnSetTimedOut(ClientId);
4742 return true;
4743}
4744
4745void CServer::SetErrorShutdown(const char *pReason)
4746{
4747 str_copy(dst&: m_aErrorShutdownReason, src: pReason);
4748}
4749
4750void CServer::SetLoggers(std::shared_ptr<ILogger> &&pFileLogger, std::shared_ptr<ILogger> &&pStdoutLogger)
4751{
4752 m_pFileLogger = pFileLogger;
4753 m_pStdoutLogger = pStdoutLogger;
4754}
4755