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