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