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