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