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