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