1 /**
2 * @file
3 * @brief Main server code?
4 */
5
6 /*
7 All original material Copyright (C) 2002-2013 UFO: Alien Invasion.
8
9 Original file from Quake 2 v3.21: quake2-2.31/server/sv_main.c
10 Copyright (C) 1997-2001 Id Software, Inc.
11
12 This program is free software; you can redistribute it and/or
13 modify it under the terms of the GNU General Public License
14 as published by the Free Software Foundation; either version 2
15 of the License, or (at your option) any later version.
16
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
20
21 See the GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26
27 */
28
29 #include "server.h"
30 #include "sv_log.h"
31 #include "../ports/system.h"
32 #include "../shared/scopedmutex.h"
33 #include <SDL.h>
34
35 /** password for remote server commands */
36 static cvar_t* rcon_password;
37 static cvar_t* sv_http_downloadserver;
38 static cvar_t* sv_enablemorale;
39 static cvar_t* sv_maxsoldiersperteam;
40 static cvar_t* sv_maxsoldiersperplayer;
41 static cvar_t* sv_hostname;
42 /** minimum seconds between connect messages */
43 static cvar_t* sv_reconnect_limit;
44 static cvar_t* sv_timeout; /* seconds without any message */
45
46 cvar_t* sv_maxclients = nullptr;
47 cvar_t* sv_dumpmapassembly;
48 cvar_t* sv_threads;
49 cvar_t* sv_rma;
50 cvar_t* sv_rmadisplaythemap;
51 /** should heartbeats be sent */
52 cvar_t* sv_public;
53 cvar_t* sv_mapname;
54
55 memPool_t* sv_genericPool;
56
57 typedef struct leakyBucket_s {
58 char node[64];
59
60 int lastTime;
61 signed char burst;
62
63 long hash;
64
65 struct leakyBucket_s* prev;
66 struct leakyBucket_s* next;
67 } leakyBucket_t;
68
69 /* This is deliberately quite large to make it more of an effort to DoS */
70 #define MAX_BUCKETS 16384
71 #define MAX_HASHES 1024
72
73 static leakyBucket_t buckets[MAX_BUCKETS];
74 static leakyBucket_t* bucketHashes[MAX_HASHES];
75 static leakyBucket_t outboundLeakyBucket;
76
SV_GetConfigString(int index)77 char* SV_GetConfigString (int index)
78 {
79 if (!Com_CheckConfigStringIndex(index))
80 Com_Error(ERR_FATAL, "Invalid config string index given: %i", index);
81
82 return sv->configstrings[index];
83 }
84
SV_GetConfigStringInteger(int index)85 int SV_GetConfigStringInteger (int index)
86 {
87 return atoi(SV_GetConfigString(index));
88 }
89
SV_SetConfigString(int index,...)90 char* SV_SetConfigString (int index, ...)
91 {
92 va_list ap;
93 const char* value;
94
95 if (!Com_CheckConfigStringIndex(index))
96 Com_Error(ERR_FATAL, "Invalid config string index given: %i", index);
97
98 va_start(ap, index);
99
100 switch (index) {
101 case CS_LIGHTMAP:
102 case CS_MAPCHECKSUM:
103 case CS_UFOCHECKSUM:
104 case CS_OBJECTAMOUNT:
105 value = va("%i", va_arg(ap, int));
106 break;
107 default:
108 value = va_arg(ap, char*);
109 break;
110 }
111
112 /* change the string in sv
113 * there may be overflows in i==CS_TILES - but thats ok
114 * see definition of configstrings and MAX_TILESTRINGS */
115 if (index == CS_TILES || index == CS_POSITIONS)
116 Q_strncpyz(sv->configstrings[index], value, MAX_TOKEN_CHARS * MAX_TILESTRINGS);
117 else
118 Q_strncpyz(sv->configstrings[index], value, sizeof(sv->configstrings[index]));
119
120 va_end(ap);
121
122 return sv->configstrings[index];
123 }
124
125 /**
126 * @brief Iterates through clients
127 * @param[in] lastClient Pointer of the client to iterate from. call with nullptr to get the first one.
128 */
SV_GetNextClient(client_t * lastClient)129 client_t* SV_GetNextClient (client_t* lastClient)
130 {
131 client_t* endOfClients = &svs.clients[sv_maxclients->integer];
132 client_t* client;
133
134 if (!sv_maxclients->integer)
135 return nullptr;
136
137 if (!lastClient)
138 return svs.clients;
139 assert(lastClient >= svs.clients);
140 assert(lastClient < endOfClients);
141
142 client = lastClient;
143
144 client++;
145 if (client >= endOfClients)
146 return nullptr;
147 else
148 return client;
149 }
150
SV_GetClient(int index)151 client_t* SV_GetClient (int index)
152 {
153 return &svs.clients[index];
154 }
155
156 /**
157 * @brief Called when the player is totally leaving the server, either willingly
158 * or unwillingly. This is NOT called if the entire server is quitting
159 * or crashing.
160 */
SV_DropClient(client_t * drop,const char * message)161 void SV_DropClient (client_t* drop, const char* message)
162 {
163 /* add the disconnect */
164 dbuffer msg(2 + strlen(message));
165 NET_WriteByte(&msg, svc_disconnect);
166 NET_WriteString(&msg, message);
167 NET_WriteMsg(drop->stream, msg);
168 SV_BroadcastPrintf(PRINT_CHAT, "%s was dropped from the server - reason: %s\n", drop->name, message);
169
170 if (drop->state == cs_spawned || drop->state == cs_spawning) {
171 /* call the prog function for removing a client */
172 /* this will remove the body, among other things */
173 const ScopedMutex scopedMutex(svs.serverMutex);
174 svs.ge->ClientDisconnect(*drop->player);
175 }
176
177 NET_StreamFinished(drop->stream);
178 drop->stream = nullptr;
179
180 drop->player->setInUse(false);
181 SV_SetClientState(drop, cs_free);
182 drop->name[0] = 0;
183
184 if (svs.abandon) {
185 int count = 0;
186 client_t* cl = nullptr;
187 while ((cl = SV_GetNextClient(cl)) != nullptr)
188 if (cl->state >= cs_connected)
189 count++;
190 if (count == 0)
191 svs.killserver = true;
192 }
193 }
194
195 /*
196 ==============================================================================
197 CONNECTIONLESS COMMANDS
198 ==============================================================================
199 */
200
201 /**
202 * @brief Find or allocate a bucket for an address
203 */
SVC_BucketForAddress(struct net_stream & address,int burst,int period)204 static leakyBucket_t* SVC_BucketForAddress (struct net_stream &address, int burst, int period)
205 {
206 char node[64];
207 NET_StreamPeerToName(&address, node, sizeof(node), false);
208
209 const long hash = Com_HashKey(node, MAX_HASHES);
210 const int now = Sys_Milliseconds();
211
212 for (leakyBucket_t* bucket = bucketHashes[hash]; bucket; bucket = bucket->next) {
213 if (!Q_streq(bucket->node, node))
214 continue;
215
216 return bucket;
217 }
218
219 for (int i = 0; i < MAX_BUCKETS; i++) {
220 leakyBucket_t* bucket = &buckets[i];
221 const int interval = now - bucket->lastTime;
222
223 /* Reclaim expired buckets */
224 if (bucket->lastTime > 0 && (interval > (burst * period) || interval < 0)) {
225 if (bucket->prev != nullptr) {
226 bucket->prev->next = bucket->next;
227 } else {
228 bucketHashes[bucket->hash] = bucket->next;
229 }
230
231 if (bucket->next != nullptr) {
232 bucket->next->prev = bucket->prev;
233 }
234
235 OBJZERO(*bucket);
236 }
237
238 if (Q_strnull(bucket->node)) {
239 Q_strncpyz(bucket->node, node, sizeof(bucket->node));
240 bucket->lastTime = now;
241 bucket->burst = 0;
242 bucket->hash = hash;
243
244 /* Add to the head of the relevant hash chain */
245 bucket->next = bucketHashes[hash];
246 if (bucketHashes[hash] != nullptr) {
247 bucketHashes[hash]->prev = bucket;
248 }
249
250 bucket->prev = nullptr;
251 bucketHashes[hash] = bucket;
252
253 return bucket;
254 }
255 }
256
257 /* Couldn't allocate a bucket for this address */
258 return nullptr;
259 }
260
SVC_RateLimit(leakyBucket_t * bucket,int burst=10,int period=100)261 static bool SVC_RateLimit (leakyBucket_t* bucket, int burst = 10, int period = 100)
262 {
263 if (bucket == nullptr)
264 return true;
265
266 const int now = Sys_Milliseconds();
267 const int interval = now - bucket->lastTime;
268 const int expired = interval / period;
269 const int expiredRemainder = interval % period;
270
271 if (expired > bucket->burst) {
272 bucket->burst = 0;
273 bucket->lastTime = now;
274 } else {
275 bucket->burst -= expired;
276 bucket->lastTime = now - expiredRemainder;
277 }
278
279 if (bucket->burst < burst) {
280 bucket->burst++;
281 return false;
282 }
283
284 return true;
285 }
286
287 /**
288 * @brief Rate limit for a particular address
289 */
SVC_RateLimitAddress(struct net_stream & from,int burst=10,int period=1000)290 static bool SVC_RateLimitAddress (struct net_stream &from, int burst = 10, int period = 1000)
291 {
292 leakyBucket_t* bucket = SVC_BucketForAddress(from, burst, period);
293 return SVC_RateLimit(bucket, burst, period);
294 }
295
296 /**
297 * @brief Responds with teaminfo such as free team num
298 * @sa CL_ParseTeamInfoMessage
299 */
SVC_TeamInfo(struct net_stream * s)300 static void SVC_TeamInfo (struct net_stream *s)
301 {
302 if (SVC_RateLimitAddress(*s)) {
303 Com_DPrintf(DEBUG_SERVER, "SVC_TeamInfo: rate limit from %s exceeded, dropping request\n", NET_StreamToString(s));
304 return;
305 }
306
307 /* Allow getinfo to be DoSed relatively easily, but prevent excess outbound bandwidth usage when being flooded inbound */
308 if (SVC_RateLimit(&outboundLeakyBucket)) {
309 Com_DPrintf(DEBUG_SERVER, "SVC_TeamInfo: rate limit exceeded, dropping request\n");
310 return;
311 }
312
313 char infoGlobal[MAX_INFO_STRING] = "";
314 Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_teamplay", Cvar_GetString("sv_teamplay"));
315 Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_maxteams", Cvar_GetString("sv_maxteams"));
316 Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_maxplayersperteam", Cvar_GetString("sv_maxplayersperteam"));
317
318 dbuffer msg;
319 NET_WriteByte(&msg, svc_oob);
320 NET_WriteRawString(&msg, "teaminfo\n");
321 NET_WriteString(&msg, infoGlobal);
322
323 client_t* cl = nullptr;
324 while ((cl = SV_GetNextClient(cl)) != nullptr) {
325 if (cl->state < cs_connected)
326 continue;
327 char infoPlayer[MAX_INFO_STRING] = "";
328 /* show players that already have a team with their teamnum */
329 int teamId = svs.ge->ClientGetTeamNum(*cl->player);
330 if (!teamId)
331 teamId = TEAM_NO_ACTIVE;
332 Info_SetValueForKeyAsInteger(infoPlayer, sizeof(infoPlayer), "cl_team", teamId);
333 Info_SetValueForKeyAsInteger(infoPlayer, sizeof(infoPlayer), "cl_ready", svs.ge->ClientIsReady(cl->player));
334 Info_SetValueForKey(infoPlayer, sizeof(infoPlayer), "cl_name", cl->name);
335 NET_WriteString(&msg, infoPlayer);
336 }
337
338 NET_WriteByte(&msg, 0);
339
340 NET_WriteMsg(s, msg);
341 }
342
343 /**
344 * @brief Responds with all the info that the server browser can see
345 * @sa SV_StatusString
346 */
SVC_Status(struct net_stream * s)347 static void SVC_Status (struct net_stream *s)
348 {
349 if (SVC_RateLimitAddress(*s)) {
350 Com_DPrintf(DEBUG_SERVER, "SVC_Status: rate limit from %s exceeded, dropping request\n", NET_StreamToString(s));
351 return;
352 }
353
354 /* Allow getstatus to be DoSed relatively easily, but prevent excess outbound bandwidth usage when being flooded inbound */
355 if (SVC_RateLimit(&outboundLeakyBucket, 10, 100)) {
356 Com_DPrintf(DEBUG_SERVER, "SVC_Status: rate limit exceeded, dropping request\n");
357 return;
358 }
359
360 dbuffer msg;
361 NET_WriteByte(&msg, svc_oob);
362 NET_WriteRawString(&msg, SV_CMD_PRINT "\n");
363 char info[MAX_INFO_STRING];
364 NET_WriteRawString(&msg, Cvar_Serverinfo(info, sizeof(info)));
365 NET_WriteRawString(&msg, "\n");
366
367 client_t* cl = nullptr;
368 while ((cl = SV_GetNextClient(cl)) != nullptr) {
369 if (cl->state <= cs_free)
370 continue;
371
372 char player[1024];
373 Com_sprintf(player, sizeof(player), "%i \"%s\"\n", svs.ge->ClientGetTeamNum(*cl->player), cl->name);
374 NET_WriteRawString(&msg, player);
375 }
376
377 NET_WriteMsg(s, msg);
378 }
379
380 /**
381 * @brief Responds with short info for broadcast scans
382 * @note The second parameter should be the current protocol version number.
383 * @note Only a short server description - the user can determine whether he is
384 * interested in a full status
385 * @sa CL_ParseStatusMessage
386 * @sa CL_ProcessPingReply
387 */
SVC_Info(struct net_stream * s)388 static void SVC_Info (struct net_stream *s)
389 {
390 if (SVC_RateLimitAddress(*s)) {
391 Com_DPrintf(DEBUG_SERVER, "SVC_Info: rate limit from %s exceeded, dropping request\n", NET_StreamToString(s));
392 return;
393 }
394
395 /* Allow getinfo to be DoSed relatively easily, but prevent excess outbound bandwidth usage when being flooded inbound */
396 if (SVC_RateLimit(&outboundLeakyBucket)) {
397 Com_DPrintf(DEBUG_SERVER, "SVC_Info: rate limit exceeded, dropping request\n");
398 return;
399 }
400
401 if (sv_maxclients->integer == 1) {
402 Com_DPrintf(DEBUG_SERVER, "Ignore info string in singleplayer mode\n");
403 return; /* ignore in single player */
404 }
405
406 const int version = atoi(Cmd_Argv(1));
407 if (version != PROTOCOL_VERSION) {
408 char string[MAX_VAR];
409 Com_sprintf(string, sizeof(string), "%s: wrong version (client: %i, host: %i)\n", sv_hostname->string, version, PROTOCOL_VERSION);
410 NET_OOB_Printf(s, SV_CMD_PRINT "\n%s", string);
411 return;
412 }
413
414 int count = 0;
415
416 client_t* cl = nullptr;
417 while ((cl = SV_GetNextClient(cl)) != nullptr)
418 if (cl->state >= cs_spawning)
419 count++;
420
421 char infostring[MAX_INFO_STRING];
422 infostring[0] = '\0';
423 Info_SetValueForKey(infostring, sizeof(infostring), "sv_protocol", DOUBLEQUOTE(PROTOCOL_VERSION));
424 Info_SetValueForKey(infostring, sizeof(infostring), "sv_hostname", sv_hostname->string);
425 Info_SetValueForKey(infostring, sizeof(infostring), "sv_dedicated", sv_dedicated->string);
426 Info_SetValueForKey(infostring, sizeof(infostring), "sv_gametype", sv_gametype->string);
427 Info_SetValueForKey(infostring, sizeof(infostring), "sv_mapname", sv->name);
428 Info_SetValueForKeyAsInteger(infostring, sizeof(infostring), "clients", count);
429 Info_SetValueForKey(infostring, sizeof(infostring), "sv_maxclients", sv_maxclients->string);
430 Info_SetValueForKey(infostring, sizeof(infostring), "sv_version", UFO_VERSION);
431 NET_OOB_Printf(s, SV_CMD_INFO "\n%s", infostring);
432 }
433
434
435 /**
436 * @brief A connection request that did not come from the master
437 * @sa CL_ConnectionlessPacket
438 */
SVC_DirectConnect(struct net_stream * stream)439 static void SVC_DirectConnect (struct net_stream *stream)
440 {
441 Com_DPrintf(DEBUG_SERVER, "SVC_DirectConnect()\n");
442
443 if (sv->started || sv->spawned) {
444 Com_Printf("rejected connect because match is already running\n");
445 NET_OOB_Printf(stream, SV_CMD_PRINT "\n" REJ_GAME_ALREADY_STARTED "\n");
446 return;
447 }
448
449 char buf[256];
450 const char* peername = NET_StreamPeerToName(stream, buf, sizeof(buf), false);
451
452 const int version = atoi(Cmd_Argv(1));
453 if (version != PROTOCOL_VERSION) {
454 Com_Printf("rejected connect from version %i - %s\n", version, peername);
455 NET_OOB_Printf(stream, SV_CMD_PRINT "\n" REJ_SERVER_VERSION_MISMATCH "\n");
456 return;
457 }
458
459 char userinfo[MAX_INFO_STRING];
460 Q_strncpyz(userinfo, Cmd_Argv(2), sizeof(userinfo));
461
462 if (Q_strnull(userinfo)) { /* catch empty userinfo */
463 Com_Printf("Empty userinfo from %s\n", peername);
464 NET_OOB_Printf(stream, SV_CMD_PRINT "\n" REJ_CONNECTION_REFUSED "\n");
465 return;
466 }
467
468 if (strchr(userinfo, '\xFF')) { /* catch end of message in string exploit */
469 Com_Printf("Illegal userinfo contained xFF from %s\n", peername);
470 NET_OOB_Printf(stream, SV_CMD_PRINT "\n" REJ_CONNECTION_REFUSED "\n");
471 return;
472 }
473
474 if (strlen(Info_ValueForKey(userinfo, "ip"))) { /* catch spoofed ips */
475 Com_Printf("Illegal userinfo contained ip from %s\n", peername);
476 NET_OOB_Printf(stream, SV_CMD_PRINT "\n" REJ_CONNECTION_REFUSED "\n");
477 return;
478 }
479
480 /* force the IP key/value pair so the game can filter based on ip */
481 Info_SetValueForKey(userinfo, sizeof(userinfo), "ip", peername);
482
483 /* find a client slot */
484 client_t* cl = nullptr;
485 while ((cl = SV_GetNextClient(cl)) != nullptr)
486 if (cl->state == cs_free)
487 break;
488 if (cl == nullptr) {
489 NET_OOB_Printf(stream, SV_CMD_PRINT "\n" REJ_SERVER_FULL "\n");
490 Com_Printf("Rejected a connection - server is full.\n");
491 return;
492 }
493
494 /* build a new connection - accept the new client
495 * this is the only place a client_t is ever initialized */
496 OBJZERO(*cl);
497 const int playernum = cl - SV_GetClient(0);
498 SrvPlayer *player = PLAYER_NUM(playernum);
499 cl->player = player;
500 cl->player->setNum(playernum);
501
502 bool connected;
503 {
504 const ScopedMutex scopedMutex(svs.serverMutex);
505 connected = svs.ge->ClientConnect(player, userinfo, sizeof(userinfo));
506 }
507
508 /* get the game a chance to reject this connection or modify the userinfo */
509 if (!connected) {
510 const char* rejmsg = Info_ValueForKey(userinfo, "rejmsg");
511 if (rejmsg[0] != '\0') {
512 NET_OOB_Printf(stream, SV_CMD_PRINT "\n%s\n" REJ_CONNECTION_REFUSED "\n", rejmsg);
513 Com_Printf("Game rejected a connection from %s. Reason: %s\n", peername, rejmsg);
514 } else {
515 NET_OOB_Printf(stream, SV_CMD_PRINT "\n" REJ_CONNECTION_REFUSED "\n");
516 Com_Printf("Game rejected a connection from %s.\n", peername);
517 }
518 return;
519 }
520
521 /* new player */
522 cl->player->setInUse(true);
523 cl->lastmessage = svs.realtime;
524
525 /* parse some info from the info strings */
526 Q_strncpyz(cl->userinfo, userinfo, sizeof(cl->userinfo));
527 SV_UserinfoChanged(cl);
528
529 /* send the connect packet to the client */
530 if (sv_http_downloadserver->string[0])
531 NET_OOB_Printf(stream, CL_CMD_CLIENT_CONNECT " dlserver=%s", sv_http_downloadserver->string);
532 else
533 NET_OOB_Printf(stream, CL_CMD_CLIENT_CONNECT);
534
535 SV_SetClientState(cl, cs_connected);
536
537 Q_strncpyz(cl->peername, peername, sizeof(cl->peername));
538 cl->stream = stream;
539 NET_StreamSetData(stream, cl);
540 }
541
542 /**
543 * @brief Checks whether the remote connection is allowed (rcon_password must be
544 * set on the server) - and verify the user given password with the cvar value.
545 */
Rcon_Validate(const char * password)546 static inline bool Rcon_Validate (const char* password)
547 {
548 /* no rcon access */
549 if (Q_strnull(rcon_password->string))
550 return false;
551
552 /* password not valid */
553 if (!Q_streq(password, rcon_password->string))
554 return false;
555
556 return true;
557 }
558
559 /**
560 * @brief A client issued an rcon command. Shift down the remaining args. Redirect all printfs
561 */
SVC_RemoteCommand(struct net_stream * stream)562 static void SVC_RemoteCommand (struct net_stream *stream)
563 {
564 char buf[64];
565 const char* peername = NET_StreamPeerToName(stream, buf, sizeof(buf), false);
566
567 /* Prevent using rcon as an amplifier and make dictionary attacks impractical */
568 if (SVC_RateLimitAddress(*stream)) {
569 Com_DPrintf(DEBUG_SERVER, "SVC_RemoteCommand: rate limit from %s exceeded, dropping request\n", peername);
570 return;
571 }
572
573 const bool valid = Rcon_Validate(Cmd_Argv(1));
574 if (!valid) {
575 static leakyBucket_t bucket;
576 /* Make DoS via rcon impractical */
577 if (SVC_RateLimit(&bucket, 10, 1000)) {
578 Com_DPrintf(DEBUG_SERVER, "SVC_RemoteCommand: rate limit exceeded, dropping request\n");
579 return;
580 }
581
582 Com_Printf("Bad rcon from %s with password: '%s'\n", peername, Cmd_Argv(1));
583 } else {
584 Com_Printf("Rcon from %s\n", peername);
585 }
586
587 static char sv_outputbuf[1024];
588 Com_BeginRedirect(stream, sv_outputbuf, sizeof(sv_outputbuf));
589
590 if (!valid) {
591 /* inform the client */
592 Com_Printf(BAD_RCON_PASSWORD);
593 } else {
594 char remaining[1024] = "";
595 int i;
596
597 /* execute the rcon commands */
598 for (i = 2; i < Cmd_Argc(); i++) {
599 Q_strcat(remaining, sizeof(remaining), "%s ", Cmd_Argv(i));
600 }
601
602 /* execute the string */
603 Cmd_ExecuteString("%s", remaining);
604 }
605
606 Com_EndRedirect();
607 }
608
609 /**
610 * @brief Handles a connectionless message from a client
611 * @sa NET_OOB_Printf
612 * @param[out] stream The stream to write to
613 * @param msg The message buffer to read the connectionless data from
614 */
SV_ConnectionlessPacket(struct net_stream * stream,dbuffer * msg)615 static void SV_ConnectionlessPacket (struct net_stream *stream, dbuffer* msg)
616 {
617 char s[512];
618
619 NET_ReadStringLine(msg, s, sizeof(s));
620 Cmd_TokenizeString(s, false, false);
621
622 const char* c = Cmd_Argv(0);
623 Com_DPrintf(DEBUG_SERVER, "Packet : %s\n", c);
624
625 if (Q_streq(c, SV_CMD_TEAMINFO)) {
626 SVC_TeamInfo(stream);
627 } else if (Q_streq(c, SV_CMD_INFO)) {
628 SVC_Info(stream);
629 } else if (Q_streq(c, SV_CMD_STATUS)) {
630 SVC_Status(stream);
631 } else if (Q_streq(c, SV_CMD_CONNECT)) {
632 SVC_DirectConnect(stream);
633 } else if (Q_streq(c, SV_CMD_RCON)) {
634 SVC_RemoteCommand(stream);
635 } else {
636 char buf[256];
637 Com_Printf("Bad connectionless packet from %s:\n%s\n", NET_StreamPeerToName(stream, buf, sizeof(buf), true), s);
638 }
639 }
640
641 /**
642 * @sa CL_ReadPacket
643 * @sa NET_ReadMsg
644 * @sa SV_Start
645 */
SV_ReadPacket(struct net_stream * s)646 void SV_ReadPacket (struct net_stream *s)
647 {
648 client_t* cl = static_cast<client_t* >(NET_StreamGetData(s));
649 dbuffer* msg;
650
651 while ((msg = NET_ReadMsg(s))) {
652 const int cmd = NET_ReadByte(msg);
653
654 if (cmd == clc_oob)
655 SV_ConnectionlessPacket(s, msg);
656 else if (cl)
657 SV_ExecuteClientMessage(cl, cmd, msg);
658 else {
659 NET_StreamFree(s);
660 s = nullptr;
661 }
662
663 delete msg;
664 }
665 }
666
667 #define HEARTBEAT_SECONDS 30
668
669 static SDL_Thread *masterServerHeartBeatThread;
670
671 /**
672 * @brief Send a message to the master every few minutes to
673 * let it know we are alive, and log information
674 */
Master_HeartbeatThread(void * data)675 static int Master_HeartbeatThread (void* data)
676 {
677 char url[512];
678 Com_sprintf(url, sizeof(url), "%s/ufo/masterserver.php?heartbeat&port=%s", masterserver_url->string, port->string);
679
680 /* send to master */
681 Com_Printf("sending heartbeat\n");
682 HTTP_GetURL(url, nullptr);
683
684 masterServerHeartBeatThread = nullptr;
685 return 0;
686 }
687
688 /**
689 * @sa CL_PingServers_f
690 */
Master_Heartbeat(void)691 static void Master_Heartbeat (void)
692 {
693 if (!sv_dedicated || !sv_dedicated->integer)
694 return; /* only dedicated servers send heartbeats */
695
696 if (!sv_public || !sv_public->integer)
697 return; /* a private dedicated game */
698
699 /* check for time wraparound */
700 if (svs.lastHeartbeat > svs.realtime)
701 svs.lastHeartbeat = svs.realtime;
702
703 if (svs.realtime - svs.lastHeartbeat < HEARTBEAT_SECONDS * 1000)
704 return; /* not time to send yet */
705
706 svs.lastHeartbeat = svs.realtime;
707
708 if (masterServerHeartBeatThread != nullptr)
709 SDL_WaitThread(masterServerHeartBeatThread, nullptr);
710
711 #if SDL_VERSION_ATLEAST(2,0,0)
712 masterServerHeartBeatThread = SDL_CreateThread(Master_HeartbeatThread, "HeartbeatThread", nullptr);
713 #else
714 masterServerHeartBeatThread = SDL_CreateThread(Master_HeartbeatThread, nullptr);
715 #endif
716 }
717
718 /**
719 * @brief If all connected clients have set their ready flag the server will spawn the clients
720 * and that change the client state.
721 * @sa SV_Spawn_f
722 */
SV_CheckSpawnSoldiers(void)723 static void SV_CheckSpawnSoldiers (void)
724 {
725 /* already started? */
726 if (sv->spawned)
727 return;
728
729 client_t* cl = nullptr;
730 while ((cl = SV_GetNextClient(cl)) != nullptr) {
731 /* all players must be connected and all of them must have set
732 * the ready flag */
733 if (cl->state != cs_began || !cl->player->isReady())
734 return;
735 }
736
737 sv->spawned = true;
738
739 cl = nullptr;
740 while ((cl = SV_GetNextClient(cl)) != nullptr)
741 if (cl->state != cs_free)
742 SV_ClientCommand(cl, CL_SPAWNSOLDIERS "\n");
743 }
744
SV_CheckStartMatch(void)745 static void SV_CheckStartMatch (void)
746 {
747 client_t* cl;
748
749 if (!sv->spawned || sv->started)
750 return;
751
752 if (sv_maxclients->integer > 1) {
753 cl = nullptr;
754 while ((cl = SV_GetNextClient(cl)) != nullptr) {
755 /* all players must have their actors spawned */
756 if (cl->state != cs_spawned)
757 return;
758 }
759 } else if (SV_GetClient(0)->state != cs_spawned) {
760 /* in single player mode we must have received the 'spawnsoldiers' */
761 return;
762 }
763
764 sv->started = true;
765
766 cl = nullptr;
767 while ((cl = SV_GetNextClient(cl)) != nullptr)
768 if (cl->state != cs_free)
769 SV_ClientCommand(cl, CL_STARTMATCH "\n");
770 }
771
772 #define PING_SECONDS 5
773
SV_PingPlayers(void)774 static void SV_PingPlayers (void)
775 {
776 /* check for time wraparound */
777 if (svs.lastPing > svs.realtime)
778 svs.lastPing = svs.realtime;
779
780 if (svs.realtime - svs.lastPing < PING_SECONDS * 1000)
781 return; /* not time to send yet */
782
783 svs.lastPing = svs.realtime;
784 client_t* cl = nullptr;
785 while ((cl = SV_GetNextClient(cl)) != nullptr) {
786 if (cl->state == cs_free)
787 continue;
788 dbuffer msg(1);
789 NET_WriteByte(&msg, svc_ping);
790 NET_WriteMsg(cl->stream, msg);
791 }
792 }
793
SV_CheckTimeouts(void)794 static void SV_CheckTimeouts (void)
795 {
796 const int droppoint = svs.realtime - 1000 * sv_timeout->integer;
797
798 if (sv_maxclients->integer == 1)
799 return;
800
801 client_t* cl = nullptr;
802 while ((cl = SV_GetNextClient(cl)) != nullptr) {
803 if (cl->state == cs_free)
804 continue;
805
806 /* might be invalid across a mapchange */
807 if (cl->lastmessage > svs.realtime)
808 cl->lastmessage = svs.realtime;
809
810 if (cl->lastmessage > 0 && cl->lastmessage < droppoint)
811 SV_DropClient(cl, "timed out");
812 }
813 }
814
815 /**
816 * @sa Qcommon_Frame
817 */
SV_Frame(int now,void * data)818 void SV_Frame (int now, void* data)
819 {
820 Com_ReadFromPipe();
821
822 /* change the gametype even if no server is running (e.g. the first time) */
823 if (sv_dedicated->integer && sv_gametype->modified) {
824 Com_SetGameType();
825 sv_gametype->modified = false;
826 }
827
828 if (sv_dedicated->integer) {
829 const char* s;
830 do {
831 s = Sys_ConsoleInput();
832 if (s)
833 Cbuf_AddText("%s\n", s);
834 } while (s);
835 }
836
837 /* if server is not active, do nothing */
838 if (!svs.initialized) {
839 #ifdef DEDICATED_ONLY
840 Com_Printf("Starting next map from the mapcycle\n");
841 SV_NextMapcycle();
842 #endif
843 return;
844 }
845
846 svs.realtime = now;
847
848 /* if time is about to hit the 32nd bit, kick all clients
849 * and clear sv.time, rather
850 * than checking for negative time wraparound everywhere.
851 * 2giga-milliseconds = 23 days, so it won't be too often */
852 if (svs.realtime > 0x70000000) {
853 SV_Map(true, sv->name, sv->assembly);
854 return;
855 }
856
857 /* keep the random time dependent */
858 rand();
859
860 SV_CheckSpawnSoldiers();
861 SV_CheckStartMatch();
862 SV_CheckTimeouts();
863
864 if (!sv_threads->integer)
865 SV_RunGameFrame();
866 else
867 /* signal the game frame thread to wake up */
868 SDL_CondSignal(svs.gameFrameCond);
869 SV_LogHandleOutput();
870
871 /* next map in the cycle */
872 if (sv->endgame && sv_maxclients->integer > 1)
873 SV_NextMapcycle();
874
875 /* send a heartbeat to the master if needed */
876 Master_Heartbeat();
877 SV_PingPlayers();
878
879 /* server is empty - so shutdown */
880 if (svs.abandon && svs.killserver)
881 SV_Shutdown("Server disconnected.", false);
882 }
883
884 /**
885 * @brief Informs all masters that this server is going down
886 */
Master_Shutdown(void)887 static void Master_Shutdown (void)
888 {
889 if (!sv_dedicated || !sv_dedicated->integer)
890 return; /* only dedicated servers send heartbeats */
891
892 if (!sv_public || !sv_public->integer)
893 return; /* a private dedicated game */
894
895 /* send to master */
896 HTTP_GetURL(va("%s/ufo/masterserver.php?shutdown&port=%s", masterserver_url->string, port->string), nullptr);
897 }
898
899 /**
900 * @brief Pull specific info from a newly changed userinfo string into a more C friendly form.
901 */
SV_UserinfoChanged(client_t * cl)902 void SV_UserinfoChanged (client_t* cl)
903 {
904 unsigned int i;
905
906 /* call prog code to allow overrides */
907 {
908 const ScopedMutex scopedMutex(svs.serverMutex);
909 svs.ge->ClientUserinfoChanged(*cl->player, cl->userinfo);
910 }
911
912 /* name of the player */
913 Q_strncpyz(cl->name, Info_ValueForKey(cl->userinfo, "cl_name"), sizeof(cl->name));
914 /* mask off high bit */
915 for (i = 0; i < sizeof(cl->name); i++)
916 cl->name[i] &= 127;
917
918 /* msg command */
919 cl->messagelevel = Info_IntegerForKey(cl->userinfo, "cl_msg");
920
921 Com_DPrintf(DEBUG_SERVER, "SV_UserinfoChanged: Changed userinfo for player %s\n", cl->name);
922 }
923
SV_CheckMaxSoldiersPerPlayer(cvar_t * cvar)924 static bool SV_CheckMaxSoldiersPerPlayer (cvar_t* cvar)
925 {
926 const int max = MAX_ACTIVETEAM;
927 return Cvar_AssertValue(cvar, 1, max, true);
928 }
929
SV_GetMapData(void)930 mapData_t* SV_GetMapData (void)
931 {
932 return &sv->mapData;
933 }
934
SV_GetMapTiles(void)935 mapTiles_t* SV_GetMapTiles (void)
936 {
937 return &sv->mapTiles;
938 }
939
940 /**
941 * @brief Only called once at startup, not for each game
942 */
SV_Init(void)943 void SV_Init (void)
944 {
945 Com_Printf("\n------ server initialization -------\n");
946
947 sv_genericPool = Mem_CreatePool("Server: Generic");
948
949 OBJZERO(svs);
950
951 sv = Mem_PoolAllocType(serverInstanceGame_t, sv_genericPool);
952
953 SV_InitOperatorCommands();
954
955 rcon_password = Cvar_Get("rcon_password", "", CVAR_ARCHIVE);
956 Cvar_Get("sv_cheats", "0", CVAR_SERVERINFO | CVAR_LATCH);
957 Cvar_Get("sv_protocol", DOUBLEQUOTE(PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_NOSET);
958 /* this cvar will become a latched cvar when you start the server */
959 sv_maxclients = Cvar_Get("sv_maxclients", "1", CVAR_SERVERINFO, "Max. connected clients");
960 sv_hostname = Cvar_Get("sv_hostname", "noname", CVAR_SERVERINFO | CVAR_ARCHIVE, "The name of the server that is displayed in the serverlist");
961 sv_http_downloadserver = Cvar_Get("sv_http_downloadserver", "", CVAR_ARCHIVE, "URL to a location where clients can download game content over HTTP");
962 sv_enablemorale = Cvar_Get("sv_enablemorale", "1", CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_LATCH, "Enable morale changes in multiplayer");
963 sv_maxsoldiersperteam = Cvar_Get("sv_maxsoldiersperteam", "4", CVAR_ARCHIVE | CVAR_SERVERINFO, "Max. amount of soldiers per team (see sv_maxsoldiersperplayer and sv_teamplay)");
964 sv_maxsoldiersperplayer = Cvar_Get("sv_maxsoldiersperplayer", DOUBLEQUOTE(MAX_ACTIVETEAM), CVAR_ARCHIVE | CVAR_SERVERINFO, "Max. amount of soldiers each player can control (see maxsoldiers and sv_teamplay)");
965 Cvar_SetCheckFunction("sv_maxsoldiersperplayer", SV_CheckMaxSoldiersPerPlayer);
966
967 sv_dumpmapassembly = Cvar_Get("sv_dumpmapassembly", "0", CVAR_ARCHIVE, "Dump map assembly information to game console");
968
969 sv_threads = Cvar_Get("sv_threads", "1", CVAR_LATCH | CVAR_ARCHIVE, "Run the server threaded");
970 sv_rma = Cvar_Get("sv_rma", "2", 0, "1 = old algorithm, 2 = new algorithm");
971 sv_rmadisplaythemap = Cvar_Get("sv_rmadisplaythemap", "0", 0, "Activate rma problem output");
972 sv_public = Cvar_Get("sv_public", "1", 0, "Should heartbeats be send to the masterserver");
973 sv_reconnect_limit = Cvar_Get("sv_reconnect_limit", "3", CVAR_ARCHIVE, "Minimum seconds between connect messages");
974 sv_timeout = Cvar_Get("sv_timeout", "200", CVAR_ARCHIVE, "Seconds until a client times out");
975
976 SV_MapcycleInit();
977 SV_LogInit();
978 }
979
980 /**
981 * @brief Used by SV_Shutdown to send a final message to all
982 * connected clients before the server goes down.
983 * @sa SV_Shutdown
984 */
SV_FinalMessage(const char * message,bool reconnect)985 static void SV_FinalMessage (const char* message, bool reconnect)
986 {
987 client_t* cl;
988 dbuffer msg(2 + strlen(message));
989
990 if (reconnect)
991 NET_WriteByte(&msg, svc_reconnect);
992 else
993 NET_WriteByte(&msg, svc_disconnect);
994 NET_WriteString(&msg, message);
995
996 cl = nullptr;
997 while ((cl = SV_GetNextClient(cl)) != nullptr)
998 if (cl->state >= cs_connected) {
999 NET_WriteConstMsg(cl->stream, msg);
1000 NET_StreamFinished(cl->stream);
1001 cl->stream = nullptr;
1002 }
1003
1004 /* make sure, that this is send */
1005 NET_Wait(0);
1006 }
1007
1008 /**
1009 * @brief Cleanup when the whole game process is shutting down
1010 * @sa SV_Shutdown
1011 * @sa Com_Quit
1012 */
SV_Clear(void)1013 void SV_Clear (void)
1014 {
1015 SV_MapcycleClear();
1016 SV_LogShutdown();
1017 }
1018
1019 /**
1020 * @brief Called when each game quits, before Sys_Quit or Sys_Error
1021 * @param[in] finalmsg The message all clients get as server shutdown message
1022 * @param[in] reconnect True if this is only a restart (new map or map restart),
1023 * false if the server shutdown completely and you also want to disconnect all clients
1024 */
SV_Shutdown(const char * finalmsg,bool reconnect)1025 void SV_Shutdown (const char* finalmsg, bool reconnect)
1026 {
1027 unsigned int i;
1028
1029 if (!svs.initialized)
1030 return;
1031
1032 if (svs.clients)
1033 SV_FinalMessage(finalmsg, reconnect);
1034
1035 Com_Printf("Shutdown server: %s\n", finalmsg);
1036
1037 Master_Shutdown();
1038 SV_ShutdownGameProgs();
1039
1040 NET_DatagramSocketClose(svs.netDatagramSocket);
1041 SV_Stop();
1042
1043 for (i = 0; i < sv->numSVModels; i++) {
1044 sv_model_t* model = &sv->svModels[i];
1045 Mem_Free(model->name);
1046 }
1047
1048 /* free current level */
1049 OBJZERO(*sv);
1050
1051 /* free server static data */
1052 Mem_Free(svs.clients);
1053
1054 SDL_DestroyMutex(svs.serverMutex);
1055
1056 OBJZERO(svs);
1057
1058 /* maybe we shut down before we init - e.g. in case of an error */
1059 if (sv_maxclients)
1060 sv_maxclients->flags &= ~CVAR_LATCH;
1061
1062 if (sv_mapname)
1063 sv_mapname->flags &= ~CVAR_NOSET;
1064 }
1065
1066 /**
1067 * @brief Will eventually shutdown the server once all clients have disconnected
1068 * @sa SV_CountPlayers
1069 */
SV_ShutdownWhenEmpty(void)1070 void SV_ShutdownWhenEmpty (void)
1071 {
1072 svs.abandon = true;
1073 /* pretend server is already dead, otherwise clients may try and reconnect */
1074 Com_SetServerState(ss_dead);
1075 }
1076
1077 /**
1078 * @brief Returns the number of spawned players
1079 * @sa SV_ShutdownWhenEmpty
1080 */
SV_CountPlayers(void)1081 int SV_CountPlayers (void)
1082 {
1083 int count = 0;
1084 client_t* cl;
1085
1086 if (!svs.initialized)
1087 return 0;
1088
1089 cl = nullptr;
1090 while ((cl = SV_GetNextClient(cl)) != nullptr) {
1091 if (cl->state != cs_spawned)
1092 continue;
1093
1094 count++;
1095 }
1096
1097 return count;
1098 }
1099