1 /**
2  * @file
3  * @brief Server commands.
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/game/g_svcmds.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 
30 #include "g_local.h"
31 #include "g_ai.h"
32 #include "g_client.h"
33 #include "g_edicts.h"
34 #include "g_inventory.h"
35 #include "g_match.h"
36 #include "g_vis.h"
37 
38 /**
39  * @brief PACKET FILTERING
40  * @note You can add or remove addresses from the filter list with:
41  *
42  * addip IP
43  * removeip IP
44  * 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".
45  * Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single host.
46  *
47  * listip
48  * Prints the current list of filters.
49  *
50  * writeip
51  * Dumps "addip IP" commands to listip.cfg so it can be executed at a later date.  The filter lists are not saved and restored by default, because I beleive it would cause too much confusion.
52  *
53  * sv_filterban <0 or 1>
54  * If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game.  This is the default setting.
55  * If 0, then only addresses matching the list will be allowed.  This lets you easily set up a private game, or a game that only allows players from your local network.
56  */
57 
58 typedef struct ipfilter_s {
59 	unsigned mask;
60 	unsigned compare;
61 } ipfilter_t;
62 
63 #define	MAX_IPFILTERS	1024
64 
65 static ipfilter_t ipfilters[MAX_IPFILTERS];
66 static int numipfilters;
67 
StringToFilter(const char * s,ipfilter_t * f)68 static bool StringToFilter (const char* s, ipfilter_t* f)
69 {
70 	char num[128];
71 	int i, j;
72 	byte b[4];
73 	byte m[4];
74 
75 	for (i = 0; i < 4; i++) {
76 		b[i] = 0;
77 		m[i] = 0;
78 	}
79 
80 	for (i = 0; i < 4; i++) {
81 		if (*s < '0' || *s > '9') {
82 			/** @todo find out if this printf is needed. Passing nullptr as a player would have crashed anyway. */
83 			//G_ClientPrintf(nullptr, PRINT_CONSOLE, "Bad filter address: %s\n", s);
84 			return false;
85 		}
86 
87 		j = 0;
88 		while (isdigit(*s)) {
89 			num[j++] = *s++;
90 		}
91 		num[j] = 0;
92 		b[i] = atoi(num);
93 		if (b[i] != 0)
94 			m[i] = 0xFF;
95 
96 		if (!*s)
97 			break;
98 		s++;
99 	}
100 
101 	f->mask = *(unsigned *) m;
102 	f->compare = *(unsigned *) b;
103 
104 	return true;
105 }
106 
SV_FilterPacket(const char * from)107 bool SV_FilterPacket (const char* from)
108 {
109 	int i;
110 	unsigned in;
111 	byte m[4];
112 	const char* p;
113 
114 	i = 0;
115 	p = from;
116 	while (*p && i < 4) {
117 		m[i] = 0;
118 		while (*p >= '0' && *p <= '9') {
119 			m[i] = m[i] * 10 + (*p - '0');
120 			p++;
121 		}
122 		if (!*p || *p == ':')
123 			break;
124 		i++, p++;
125 	}
126 
127 	in = *(unsigned *) m;
128 
129 	for (i = 0; i < numipfilters; i++)
130 		if ((in & ipfilters[i].mask) == ipfilters[i].compare)
131 			return sv_filterban->integer;
132 
133 	return !sv_filterban->integer;
134 }
135 
136 
137 /**
138  * @sa SVCmd_RemoveIP_f
139  */
SVCmd_AddIP_f(void)140 static void SVCmd_AddIP_f (void)
141 {
142 	int i;
143 
144 	if (gi.Cmd_Argc() < 3) {
145 		gi.DPrintf("Usage: %s <ip-mask>\n", gi.Cmd_Argv(1));
146 		return;
147 	}
148 
149 	for (i = 0; i < numipfilters; i++)
150 		if (ipfilters[i].compare == ~0x0)
151 			break;				/* free spot */
152 	if (i == numipfilters) {
153 		if (numipfilters == MAX_IPFILTERS) {
154 			gi.DPrintf("IP filter list is full\n");
155 			return;
156 		}
157 		numipfilters++;
158 	}
159 
160 	if (!StringToFilter(gi.Cmd_Argv(2), &ipfilters[i]))
161 		ipfilters[i].compare = ~0x0;
162 }
163 
164 /**
165  * @sa SVCmd_AddIP_f
166  */
SVCmd_RemoveIP_f(void)167 static void SVCmd_RemoveIP_f (void)
168 {
169 	ipfilter_t f;
170 	int i, j;
171 
172 	if (gi.Cmd_Argc() < 3) {
173 		gi.DPrintf("Usage: %s <ip-mask>\n", gi.Cmd_Argv(1));
174 		return;
175 	}
176 
177 	if (!StringToFilter(gi.Cmd_Argv(2), &f))
178 		return;
179 
180 	for (i = 0; i < numipfilters; i++)
181 		if (ipfilters[i].mask == f.mask && ipfilters[i].compare == f.compare) {
182 			for (j = i + 1; j < numipfilters; j++)
183 				ipfilters[j - 1] = ipfilters[j];
184 			numipfilters--;
185 			gi.DPrintf("Removed.\n");
186 			return;
187 		}
188 	gi.DPrintf("Didn't find %s.\n", gi.Cmd_Argv(2));
189 }
190 
191 /**
192  * @brief Shows the current ip in the filter list
193  */
SVCmd_ListIP_f(void)194 static void SVCmd_ListIP_f (void)
195 {
196 	int i;
197 	byte b[4];
198 
199 	gi.DPrintf("Filter list:\n");
200 	for (i = 0; i < numipfilters; i++) {
201 		*(unsigned *) b = ipfilters[i].compare;
202 		gi.DPrintf("%3i.%3i.%3i.%3i\n", b[0], b[1], b[2], b[3]);
203 	}
204 }
205 
206 /**
207  * @brief Store all ips in the current filter list in
208  */
SVCmd_WriteIP_f(void)209 static void SVCmd_WriteIP_f (void)
210 {
211 	FILE *f;
212 	char name[MAX_OSPATH];
213 	byte b[4];
214 	int i;
215 
216 	Com_sprintf(name, sizeof(name), "%s/listip.cfg", gi.FS_Gamedir());
217 
218 	gi.DPrintf("Writing %s.\n", name);
219 
220 	f = fopen(name, "wb");
221 	if (!f) {
222 		gi.DPrintf("Couldn't open %s\n", name);
223 		return;
224 	}
225 
226 	fprintf(f, "set sv_filterban %d\n", sv_filterban->integer);
227 
228 	for (i = 0; i < numipfilters; i++) {
229 		*(unsigned *) b = ipfilters[i].compare;
230 		fprintf(f, "sv addip %i.%i.%i.%i\n", b[0], b[1], b[2], b[3]);
231 	}
232 
233 	fclose(f);
234 }
235 
236 /**
237  * @brief Used to add ai opponents to a game
238  * @note civilians can not be added with this function
239  * @sa AI_CreatePlayer
240  */
SVCmd_AI_Add_f(void)241 static void SVCmd_AI_Add_f (void)
242 {
243 	int team;
244 
245 	if (gi.Cmd_Argc() < 3) {
246 		gi.DPrintf("Usage: %s <teamnum>\n", gi.Cmd_Argv(1));
247 		return;
248 	}
249 	team = atoi(gi.Cmd_Argv(2));
250 	if (team > TEAM_CIVILIAN && team < MAX_TEAMS) {
251 		if (!AI_CreatePlayer(team))
252 			gi.DPrintf("Couldn't create AI player.\n");
253 	} else
254 		gi.DPrintf("Bad team number.\n");
255 }
256 
257 
258 /**
259  * @brief Call the end game function with the given team
260  * used to e.g. abort singleplayer games and let the aliens win
261  * @sa G_MatchEndTrigger
262  */
SVCmd_Win_f(void)263 static void SVCmd_Win_f (void)
264 {
265 	int team;
266 
267 	if (gi.Cmd_Argc() < 3) {
268 		gi.DPrintf("Usage: %s <teamnum>\n", gi.Cmd_Argv(1));
269 		return;
270 	}
271 	team = atoi(gi.Cmd_Argv(2));
272 	if (team > TEAM_CIVILIAN && team < MAX_TEAMS)
273 		G_MatchEndTrigger(team, 0);
274 	else
275 		gi.DPrintf("Bad team number.\n");
276 }
277 
278 #ifdef DEBUG
SVCmd_ShowAll_f(void)279 static void SVCmd_ShowAll_f (void)
280 {
281 	Edict* ent = nullptr;
282 
283 	/* Make everything visible to anyone who can't already see it */
284 	while ((ent = G_EdictsGetNextInUse(ent))) {
285 		G_AppearPerishEvent(~G_VisToPM(ent->visflags), 1, *ent, nullptr);
286 		G_VisFlagsAdd(*ent, ~ent->visflags);
287 	}
288 	gi.DPrintf("All items and creatures revealed to all sides\n");
289 }
290 
SVCmd_AddItem_f(void)291 static void SVCmd_AddItem_f (void)
292 {
293 	const int team = TEAM_DEFAULT;
294 	Edict* ent = G_EdictsGetNextLivingActorOfTeam(nullptr, team);
295 
296 	if (gi.Cmd_Argc() < 3) {
297 		gi.DPrintf("Usage: %s <item-id>\n", gi.Cmd_Argv(1));
298 		return;
299 	}
300 
301 	if (!ent) {
302 		gi.DPrintf("Could not add item, no living members of team %i left\n", team);
303 		return;
304 	}
305 
306 	G_AddItemToFloor(ent->pos, gi.Cmd_Argv(2));
307 }
308 
309 /**
310  * @brief Debug function to show the whole inventory of all connected clients on the server
311  */
SVCmd_ActorInvList_f(void)312 static void SVCmd_ActorInvList_f (void)
313 {
314 	Player *player;
315 	int i;
316 
317 	/* show inventory of all players around - include even the ai players */
318 	for (i = 0, player = game.players; i < game.sv_maxplayersperteam * 2; i++, player++) {
319 		if (!player->isInUse())
320 			continue;
321 		G_InvList_f(*player);
322 	}
323 }
324 
SVCmd_ListEdicts_f(void)325 static void SVCmd_ListEdicts_f (void)
326 {
327 	Edict* ent = nullptr;
328 	int i = 0;
329 	Com_Printf("number | entnum | mapnum | type | inuse | pnum | team | size |  HP | state | classname      | model/ptl             | pos\n");
330 	while ((ent = G_EdictsGetNext(ent))) {
331 		char buf[128];
332 		const char* model;
333 		if (ent->type == ET_PARTICLE)
334 			model = ent->particle;
335 		else if (ent->model)
336 			model = ent->model;
337 		else
338 			model = "no mdl";
339 		Com_sprintf(buf, sizeof(buf), "#%5i | #%5i | #%5i | %4i | %5i | %4i | %4i | %4i | %3i | %5i | %14s | %21s | %i:%i:%i",
340 				i, ent->number, ent->mapNum, ent->type, ent->inuse, ent->pnum, ent->team, ent->fieldSize,
341 				ent->HP, ent->state, ent->classname, model, ent->pos[0], ent->pos[1], ent->pos[2]);
342 		Com_Printf("%s\n", buf);
343 		i++;
344 	}
345 }
346 #endif
347 
348 /**
349  * @brief ServerCommand will be called when an "sv" command is issued.
350  * The game can issue gi.Cmd_Argc() / gi.Cmd_Argv() commands to get the rest
351  * of the parameters
352  * @sa serverCommandList
353  */
G_ServerCommand(void)354 void G_ServerCommand (void)
355 {
356 	const char* cmd;
357 
358 	cmd = gi.Cmd_Argv(1);
359 	if (Q_strcasecmp(cmd, "addip") == 0)
360 		SVCmd_AddIP_f();
361 	else if (Q_strcasecmp(cmd, "removeip") == 0)
362 		SVCmd_RemoveIP_f();
363 	else if (Q_strcasecmp(cmd, "listip") == 0)
364 		SVCmd_ListIP_f();
365 	else if (Q_strcasecmp(cmd, "writeip") == 0)
366 		SVCmd_WriteIP_f();
367 	else if (Q_strcasecmp(cmd, "ai_add") == 0)
368 		SVCmd_AI_Add_f();
369 	else if (Q_strcasecmp(cmd, "win") == 0)
370 		SVCmd_Win_f();
371 #ifdef DEBUG
372 	else if (Q_strcasecmp(cmd, "debug_showall") == 0)
373 		SVCmd_ShowAll_f();
374 	else if (Q_strcasecmp(cmd, "debug_additem") == 0)
375 		SVCmd_AddItem_f();
376 	else if (Q_strcasecmp(cmd, "debug_actorinvlist") == 0)
377 		SVCmd_ActorInvList_f();
378 	else if (Q_strcasecmp(cmd, "debug_listedicts") == 0)
379 		SVCmd_ListEdicts_f();
380 #endif
381 	else
382 		gi.DPrintf("Unknown server command \"%s\"\n", cmd);
383 }
384