Bug Summary

File:server/sv_ccmds.cpp
Location:line 76, column 7
Description:Access to field 'state' results in a dereference of a null pointer (loaded from variable 'cl')

Annotated Source Code

1/**
2 * @file
3 * @brief Console-only server commands.
4 *
5 * These commands can only be entered from stdin or by a remote operator datagram.
6 */
7
8/*
9All original material Copyright (C) 2002-2011 UFO: Alien Invasion.
10
11Original file from Quake 2 v3.21: quake2-2.31/server/sv_ccmds.c
12Copyright (C) 1997-2001 Id Software, Inc.
13
14This program is free software; you can redistribute it and/or
15modify it under the terms of the GNU General Public License
16as published by the Free Software Foundation; either version 2
17of the License, or (at your option) any later version.
18
19This program is distributed in the hope that it will be useful,
20but WITHOUT ANY WARRANTY; without even the implied warranty of
21MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22
23See the GNU General Public License for more details.
24
25You should have received a copy of the GNU General Public License
26along with this program; if not, write to the Free Software
27Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28
29*/
30
31#include "server.h"
32#include "../common/http.h"
33
34void SV_Heartbeat_f (void)
35{
36 /* heartbeats will always be sent to the ufo master */
37 svs.lastHeartbeat = -9999999; /* send immediately */
38}
39
40/**
41 * @brief Add the server to the master server list so that others can see the server in the server list
42 * @sa SV_InitGame
43 */
44void SV_SetMaster_f (void)
45{
46 if (sv_maxclients->integer == 1)
47 return;
48
49 /* make sure the server is listed public */
50 Cvar_Set("public", "1");
51
52 Com_Printf("Master server at [%s] - sending a ping\n", masterserver_url->string);
53 HTTP_GetURL(va("%s/ufo/masterserver.php?ping&port=%s", masterserver_url->string, port->string), NULL__null);
54
55 if (!sv_dedicated->integer)
56 return;
57
58 /* only dedicated servers are sending heartbeats */
59 SV_Heartbeat_f();
60}
61
62/**
63 * @brief searches a player by id or name
64 * @param[in] s Either the numeric id of the player, or the player name
65 * @return the client structure
66 */
67static client_t* SV_GetPlayerClientStructure (const char *s)
68{
69 /* numeric values are just slot numbers */
70 if (s[0] >= '0' && s[0] <= '9') {
4
Taking true branch
71 int idnum = atoi(Cmd_Argv(1));
72 client_t *cl = NULL__null;
73 /* check for a name match */
74 while ((cl = SV_GetNextClient(cl)) != NULL__null && idnum > 0)
5
Loop condition is false. Execution continues on line 76
75 idnum--;
76 if (cl->state == cs_free) {
6
Access to field 'state' results in a dereference of a null pointer (loaded from variable 'cl')
77 Com_Printf("Client %i is not active\n", idnum);
78 return NULL__null;
79 }
80 return cl;
81 } else {
82 client_t *cl = NULL__null;
83 /* check for a name match */
84 while ((cl = SV_GetNextClient(cl)) != NULL__null) {
85 if (cl->state == cs_free)
86 continue;
87 if (Q_streq(cl->name, s)(strcmp(cl->name, s) == 0)) {
88 return cl;
89 }
90 }
91 }
92
93 Com_Printf("Userid %s is not on the server\n", s);
94 return NULL__null;
95}
96
97/**
98 * @brief Checks whether a map exists
99 */
100bool SV_CheckMap (const char *map, const char *assembly)
101{
102 char expanded[MAX_QPATH64];
103
104 /* base attacks starts with . and random maps with + */
105 if (map[0] == '+') {
106 Com_sprintf(expanded, sizeof(expanded), "maps/%s.ump", map + 1);
107
108 /* check for ump file */
109 if (FS_CheckFile("%s", expanded) < 0) {
110 Com_Printf("Can't find %s\n", expanded);
111 return false;
112 }
113 } else if (!assembly) {
114 Com_sprintf(expanded, sizeof(expanded), "maps/%s.bsp", map);
115
116 /* check for bsp file */
117 if (FS_CheckFile("%s", expanded) < 0) {
118 Com_Printf("Can't find %s\n", expanded);
119 return false;
120 }
121 }
122 return true;
123}
124
125/**
126 * @brief Goes directly to a given map
127 * @sa SV_InitGame
128 * @sa SV_SpawnServer
129 */
130static void SV_Map_f (void)
131{
132 const char *assembly = NULL__null;
133 char bufMap[MAX_TOKEN_CHARS256 * MAX_TILESTRINGS25];
134 char bufAssembly[MAX_TOKEN_CHARS256 * MAX_TILESTRINGS25];
135 bool day;
136
137 if (Cmd_Argc() < 3) {
138 Com_Printf("Usage: %s <day|night> <mapname> [<assembly>]\n", Cmd_Argv(0));
139 Com_Printf("Use 'maplist' to get a list of all installed maps\n");
140 return;
141 }
142
143 if (Q_streq(Cmd_Argv(0), "devmap")(strcmp(Cmd_Argv(0), "devmap") == 0)) {
144 Com_Printf("deactivate ai - make sure to reset sv_ai after maptesting\n");
145 Cvar_SetValue("sv_ai", 0);
146 Cvar_SetValue("sv_cheats", 1);
147 Cvar_SetValue("sv_send_edicts", 1);
148 Cvar_SetValue("g_notu", 1);
149 Cvar_SetValue("g_nospawn", 1);
150 } else {
151 Cvar_SetValue("sv_ai", 1);
152 Cvar_SetValue("sv_send_edicts", 0);
153 Cvar_SetValue("g_notu", 0);
154 Cvar_SetValue("g_nospawn", 0);
155 }
156
157 if (Q_streq(Cmd_Argv(1), "day")(strcmp(Cmd_Argv(1), "day") == 0)) {
158 day = true;
159 } else if (Q_streq(Cmd_Argv(1), "night")(strcmp(Cmd_Argv(1), "night") == 0)) {
160 day = false;
161 } else {
162 Com_Printf("Invalid lightmap parameter - use day or night\n");
163 return;
164 }
165 /* we copy them to buffers because the command pointers might be invalid soon */
166
167 Q_strncpyz(bufMap, Cmd_Argv(2), sizeof(bufMap))Q_strncpyzDebug( bufMap, Cmd_Argv(2), sizeof(bufMap), "src/server/sv_ccmds.cpp"
, 167 )
;
168 /* assembled maps uses position strings */
169 if (Cmd_Argc() == 4) {
170 assembly = bufAssembly;
171 Q_strncpyz(bufAssembly, Cmd_Argv(3), sizeof(bufAssembly))Q_strncpyzDebug( bufAssembly, Cmd_Argv(3), sizeof(bufAssembly
), "src/server/sv_ccmds.cpp", 171 )
;
172 }
173
174 /* check to make sure the level exists */
175 if (!SV_CheckMap(bufMap, assembly))
176 return;
177
178 /* start up the next map */
179 SV_Map(day, bufMap, assembly);
180}
181
182/**
183 * @brief Kick a user off of the server
184 */
185static void SV_Kick_f (void)
186{
187 client_t *cl;
188
189 if (!svs.initialized) {
1
Taking false branch
190 Com_Printf("No server running.\n");
191 return;
192 }
193
194 if (Cmd_Argc() != 2) {
2
Taking false branch
195 Com_Printf("Usage: %s <userid>\n", Cmd_Argv(0));
196 return;
197 }
198
199 cl = SV_GetPlayerClientStructure(Cmd_Argv(1));
3
Calling 'SV_GetPlayerClientStructure'
200 if (cl == NULL__null)
201 return;
202
203 SV_BroadcastPrintf(PRINT_CONSOLE2, "%s was kicked\n", cl->name);
204 /* print directly, because the dropped client won't get the
205 * SV_BroadcastPrintf message */
206 SV_DropClient(cl, "You were kicked from the game\n");
207}
208
209/**
210 * @brief Forces a game start even if not all players are ready yet
211 * @sa SV_CheckGameStart
212 */
213static void SV_StartGame_f (void)
214{
215 client_t* cl = NULL__null;
216 int cnt = 0;
217 while ((cl = SV_GetNextClient(cl)) != NULL__null) {
218 if (cl->state != cs_free) {
219 cl->player->isReady = true;
220 cnt++;
221 }
222 }
223 Cvar_ForceSet("sv_maxclients", va("%i", cnt));
224}
225
226/**
227 * @brief Prints some server info to the game console - like connected players
228 * and current running map
229 */
230static void SV_Status_f (void)
231{
232 int i;
233 client_t *cl;
234 const char *s;
235 char buf[256];
236
237 if (!svs.clients) {
238 Com_Printf("No server running.\n");
239 return;
240 }
241 Com_Printf("map : %s (%s)\n", sv->name, (SV_GetConfigStringInteger(CS_LIGHTMAP12) ? "day" : "night"));
242 Com_Printf("active team : %i\n", svs.ge->ClientGetActiveTeam());
243
244 Com_Printf("num status name timeout ready address \n");
245 Com_Printf("--- ------- --------------- -------------- ----- ---------------------\n");
246
247 cl = NULL__null;
248 i = 0;
249 while ((cl = SV_GetNextClient(cl)) != NULL__null) {
250 char state_buf[16];
251 char const* state;
252
253 i++;
254
255 if (cl->state == cs_free)
256 continue;
257
258 switch (cl->state) {
259 case cs_connected:
260 state = "CONNECT"; break;
261 case cs_spawning:
262 state = "SPAWNIN"; break;
263 case cs_began:
264 state = "BEGAN "; break;
265 case cs_spawned:
266 state = "SPAWNED"; break;
267
268 default:
269 sprintf(state_buf, "%7i", cl->state);
270 state = state_buf;
271 break;
272 }
273
274 s = NET_StreamPeerToName(cl->stream, buf, sizeof(buf), false);
275 Com_Printf("%3i %s %-15s %14i %-5s %-21s\n", i, state, cl->name, cl->lastmessage,
276 cl->player->isReady ? "true" : "false", s);
277 }
278 Com_Printf("\n");
279}
280
281#ifdef DEDICATED_ONLY1
282/**
283 * @sa SV_BroadcastPrintf
284 */
285static void SV_ConSay_f (void)
286{
287 const char *p;
288 char text[1024];
289
290 if (Cmd_Argc() < 2)
291 return;
292
293 if (!Com_ServerState()) {
294 Com_Printf("no server is running\n");
295 return;
296 }
297
298 Q_strncpyz(text, "serverconsole: ", sizeof(text))Q_strncpyzDebug( text, "serverconsole: ", sizeof(text), "src/server/sv_ccmds.cpp"
, 298 )
;
299 p = Cmd_Args();
300
301 if (*p == '"')
302 p++;
303
304 Q_strcat(text, p, sizeof(text));
305 if (text[strlen(text)] == '"')
306 text[strlen(text)] = 0;
307 SV_BroadcastPrintf(PRINT_CHAT0, "%s\n", text);
308}
309#endif
310
311/**
312 * @brief Examine or change the serverinfo string
313 */
314static void SV_Serverinfo_f (void)
315{
316 Com_Printf("Server info settings:\n");
317 Info_Print(Cvar_Serverinfo());
318}
319
320
321/**
322 * @brief Examine all a users info strings
323 * @sa CL_UserInfo_f
324 */
325static void SV_UserInfo_f (void)
326{
327 client_t *cl;
328
329 if (!svs.initialized) {
330 Com_Printf("No server running.\n");
331 return;
332 }
333
334 if (Cmd_Argc() != 2) {
335 Com_Printf("Usage: %s <userid>\n", Cmd_Argv(0));
336 return;
337 }
338
339 cl = SV_GetPlayerClientStructure(Cmd_Argv(1));
340 if (cl == NULL__null)
341 return;
342
343 Com_Printf("userinfo\n");
344 Com_Printf("--------\n");
345 Info_Print(cl->userinfo);
346}
347
348/**
349 * @brief Kick everyone off, possibly in preparation for a new game
350 */
351static void SV_KillServer_f (void)
352{
353 if (!svs.initialized)
354 return;
355 SV_Shutdown("Server was killed.", false);
356}
357
358/**
359 * @brief Let the game dll handle a command
360 */
361static void SV_ServerCommand_f (void)
362{
363 if (!svs.ge) {
364 Com_Printf("No game loaded.\n");
365 return;
366 }
367
368 if (Cmd_Argc() < 2) {
369 Com_Printf("Usage: %s <command> <parameter>\n", Cmd_Argv(0));
370 return;
371 }
372
373 Com_DPrintf(DEBUG_SERVER0x40, "Execute game command '%s'\n", Cmd_Args());
374
375 TH_MutexLock(svs.serverMutex)_TH_MutexLock((svs.serverMutex), "src/server/sv_ccmds.cpp", 375
)
;
376
377 svs.ge->ServerCommand();
378
379 TH_MutexUnlock(svs.serverMutex)_TH_MutexUnlock((svs.serverMutex), "src/server/sv_ccmds.cpp",
379)
;
380}
381
382/*=========================================================== */
383
384/**
385 * @brief Autocomplete function for the map command
386 * @param[out] match The found entry of the list we are searching, in case of more than one entry their common suffix is returned.
387 * @sa Cmd_AddParamCompleteFunction
388 */
389static int SV_CompleteMapCommand (const char *partial, const char **match)
390{
391 const char *dayNightStr = NULL__null;
392 static char dayNightMatch[7];
393
394 if (partial[0])
395 dayNightStr = strstr(partial, " ");
396 if (!dayNightStr) {
397 if (partial[0] == 'd') {
398 Q_strncpyz(dayNightMatch,"day ",sizeof(dayNightMatch))Q_strncpyzDebug( dayNightMatch, "day ", sizeof(dayNightMatch)
, "src/server/sv_ccmds.cpp", 398 )
;
399 *match = dayNightMatch;
400 return 1;
401 } else if (partial[0] == 'n') {
402 Q_strncpyz(dayNightMatch,"night ",sizeof(dayNightMatch))Q_strncpyzDebug( dayNightMatch, "night ", sizeof(dayNightMatch
), "src/server/sv_ccmds.cpp", 402 )
;
403 *match = dayNightMatch;
404 return 1;
405 }
406 /* neither day or night, delete previous content and display options */
407 Com_Printf("day\nnight\n");
408 dayNightMatch[0] = '\0';
409 *match = dayNightMatch;
410 return 2;
411 } else {
412 if (Q_streq(partial, "day ")(strcmp(partial, "day ") == 0) || Q_streq(partial, "night ")(strcmp(partial, "night ") == 0)) {
413 /* dayNightStr is correct, use it */
414 partial = dayNightStr + 1;
415 } else {
416 /* neither day or night, delete previous content and display options */
417 Com_Printf("day\nnight\n");
418 dayNightMatch[0] = '\0';
419 *match = dayNightMatch;
420 return 2;
421 }
422 }
423
424 FS_GetMaps(false);
425
426 int n = 0;
427 for (char* const* i = fs_maps, * const* const end = i + fs_numInstalledMaps; i != end; ++i) {
428 if (Cmd_GenericCompleteFunction(*i, partial, match)) {
429 Com_Printf("%s\n", *i);
430 ++n;
431 }
432 }
433 return n;
434}
435
436/**
437 * @brief List all valid maps
438 * @sa FS_GetMaps
439 */
440static void SV_ListMaps_f (void)
441{
442 int i;
443
444 FS_GetMaps(true);
445
446 for (i = 0; i <= fs_numInstalledMaps; i++)
447 Com_Printf("%s\n", fs_maps[i]);
448 Com_Printf("-----\n %i installed maps\n+name means random map assembly\n", fs_numInstalledMaps + 1);
449}
450
451/**
452 * @brief List for SV_CompleteServerCommand
453 * @sa ServerCommand
454 */
455struct serverCommand_t {
456 char const* name;
457 char const* description;
458};
459
460static serverCommand_t const serverCommandList[] = {
461 { "startgame", "Force the gamestart - useful for multiplayer games" },
462 { "addip", "The ip address is specified in dot format, and any unspecified digits will match any value, so you can specify an entire class C network with 'addip 192.246.40'" },
463 { "removeip", "Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single host" },
464 { "listip", "Prints the current list of filters" },
465 { "writeip", "Dumps ips to listip.cfg so it can be executed at a later date" },
466 { "ai_add", "Used to add ai opponents to a game - but no civilians" },
467 { "win", "Call the end game function with the given team" },
468#ifdef DEBUG1
469 { "debug_showall", "Debug function: Reveal all items to all sides" },
470 { "debug_actorinvlist", "Debug function to show the whole inventory of all connected clients on the server" },
471#endif
472 { 0, 0 }
473};
474
475/**
476 * @brief Autocomplete function for server commands
477 * @sa ServerCommand
478 */
479static int SV_CompleteServerCommand (const char *partial, const char **match)
480{
481 int n = 0;
482 for (serverCommand_t const* i = serverCommandList; i->name; ++i) {
483 if (Cmd_GenericCompleteFunction(i->name, partial, match)) {
484 Com_Printf("[cmd] %s\n", i->name);
485 if (*i->description)
486 Com_Printf(S_COLOR_GREEN"^2" " %s\n", i->description);
487 ++n;
488 }
489 }
490 return n;
491}
492
493/**
494 * @sa CL_ShowConfigstrings_f
495 */
496static void SV_PrintConfigStrings_f (void)
497{
498 int i;
499
500 for (i = 0; i < MAX_CONFIGSTRINGS(((((16 +25)+25)+256)+256)+(256*2)); i++) {
501 const char *configString;
502 /* CS_TILES and CS_POSITIONS can stretch over multiple configstrings,
503 * so don't send the middle parts again. */
504 if (i > CS_TILES16 && i < CS_POSITIONS(16 +25))
505 continue;
506 if (i > CS_POSITIONS(16 +25) && i < CS_MODELS((16 +25)+25))
507 continue;
508 configString = SV_GetConfigString(i);
509 if (configString[0] == '\0')
510 continue;
511 Com_Printf("configstring[%3i]: %s\n", i, configString);
512 }
513}
514
515#ifdef DEBUG1
516/** @todo this does not belong here */
517#include "../common/routing.h"
518
519/**
520 * @brief Dumps contents of the entire server map to console for inspection.
521 * @sa CL_InitLocal
522 */
523static void Grid_DumpWholeServerMap_f (void)
524{
525 int i;
526
527 for (i = 0; i < ACTOR_MAX_SIZE(2); i++)
528 RT_DumpWholeMap(&sv->mapTiles, &sv->mapData.map[i]);
529}
530
531/**
532 * @brief Dumps contents of the entire server routing table to CSV file.
533 * @sa CL_InitLocal
534 */
535static void Grid_DumpServerRoutes_f (void)
536{
537 ipos3_t wpMins, wpMaxs;
538 VecToPos(sv->mapData.mapMin, wpMins)( (wpMins)[0] = ((int)(sv->mapData.mapMin)[0] + 4096) / 32
, (wpMins)[1] = ((int)(sv->mapData.mapMin)[1] + 4096) / 32
, (wpMins)[2] = std::min((8 - 1), ((int)(sv->mapData.mapMin
)[2] / 64)) )
;
539 VecToPos(sv->mapData.mapMax, wpMaxs)( (wpMaxs)[0] = ((int)(sv->mapData.mapMax)[0] + 4096) / 32
, (wpMaxs)[1] = ((int)(sv->mapData.mapMax)[1] + 4096) / 32
, (wpMaxs)[2] = std::min((8 - 1), ((int)(sv->mapData.mapMax
)[2] / 64)) )
;
540 RT_WriteCSVFiles(sv->mapData.map, "ufoaiserver", wpMins, wpMaxs);
541}
542#endif
543
544/**
545 * @sa SV_Init
546 */
547void SV_InitOperatorCommands (void)
548{
549 Cmd_AddCommand("heartbeat", SV_Heartbeat_f, "Sends a heartbeat to the masterserver");
550 Cmd_AddCommand("kick", SV_Kick_f, "Kick a user from the server");
551 Cmd_AddCommand("startgame", SV_StartGame_f, "Forces a game start even if not all players are ready yet");
552 Cmd_AddCommand("status", SV_Status_f, "Prints status of server and connected clients");
553 Cmd_AddCommand("serverinfo", SV_Serverinfo_f, "Prints the serverinfo that is visible in the server browsers");
554 Cmd_AddCommand("info", SV_UserInfo_f, "Prints the userinfo for a given userid");
555
556 Cmd_AddCommand("map", SV_Map_f, "Quit client and load the new map");
557 Cmd_AddParamCompleteFunction("map", SV_CompleteMapCommand);
558 Cmd_AddCommand("devmap", SV_Map_f, "Quit client and load the new map - deactivate the ai");
559 Cmd_AddParamCompleteFunction("devmap", SV_CompleteMapCommand);
560 Cmd_AddCommand("maplist", SV_ListMaps_f, "List of all available maps");
561
562 Cmd_AddCommand("setmaster", SV_SetMaster_f, "Send ping command to masterserver (see cvar masterserver_url)");
563
564#ifdef DEDICATED_ONLY1
565 Cmd_AddCommand("say", SV_ConSay_f, "Broadcasts server messages to all connected players");
566#endif
567
568#ifdef DEBUG1
569 Cmd_AddCommand("debug_sgrid", Grid_DumpWholeServerMap_f, "Shows the whole server side pathfinding grid of the current loaded map");
570 Cmd_AddCommand("debug_sroute", Grid_DumpServerRoutes_f, "Shows the whole server side pathfinding grid of the current loaded map");
571#endif
572
573 Cmd_AddCommand("killserver", SV_KillServer_f, "Shuts the server down - and disconnect all clients");
574 Cmd_AddCommand("sv_configstrings", SV_PrintConfigStrings_f, "Prints the server config strings to game console");
575
576 Cmd_AddCommand("sv", SV_ServerCommand_f, "Server command");
577 Cmd_AddParamCompleteFunction("sv", SV_CompleteServerCommand);
578}