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