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