1 /*
2 	sv_ccmds.c
3 
4 	(description)
5 
6 	Copyright (C) 1996-1997  Id Software, Inc.
7 
8 	This program is free software; you can redistribute it and/or
9 	modify it under the terms of the GNU General Public License
10 	as published by the Free Software Foundation; either version 2
11 	of the License, or (at your option) any later version.
12 
13 	This program is distributed in the hope that it will be useful,
14 	but WITHOUT ANY WARRANTY; without even the implied warranty of
15 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 	See the GNU General Public License for more details.
18 
19 	You should have received a copy of the GNU General Public License
20 	along with this program; if not, write to:
21 
22 		Free Software Foundation, Inc.
23 		59 Temple Place - Suite 330
24 		Boston, MA  02111-1307, USA
25 
26 */
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30 
31 #ifdef HAVE_STRING_H
32 # include <string.h>
33 #endif
34 #ifdef HAVE_STRINGS_H
35 # include <strings.h>
36 #endif
37 
38 #include <stdlib.h>
39 #include <ctype.h>
40 
41 #include "QF/cmd.h"
42 #include "QF/console.h"
43 #include "QF/cvar.h"
44 #include "QF/dstring.h"
45 #include "QF/msg.h"
46 #include "QF/qargs.h"
47 #include "QF/qendian.h"
48 #include "QF/quakefs.h"
49 #include "QF/sys.h"
50 #include "QF/va.h"
51 
52 #include "qw/bothdefs.h"
53 #include "compat.h"
54 #include "server.h"
55 #include "sv_demo.h"
56 #include "sv_progs.h"
57 #include "sv_qtv.h"
58 #include "sv_recorder.h"
59 
60 qboolean    sv_allow_cheats;
61 
62 int         fp_messages = 4, fp_persecond = 4, fp_secondsdead = 10;
63 char        fp_msg[255] = { 0 };
64 cvar_t     *sv_leetnickmatch;
65 
66 static qboolean
match_char(char a,char b)67 match_char (char a, char b)
68 {
69 	a = tolower ((byte) sys_char_map[(byte) a]);
70 	b = tolower ((byte) sys_char_map[(byte) b]);
71 
72 	if (a == b || (sv_leetnickmatch->int_val
73 		&& (   (a == '1' && b == 'i') || (a == 'i' && b == '1')
74 			|| (a == '1' && b == 'l') || (a == 'l' && b == '1')
75 			|| (a == '3' && b == 'e') || (a == 'e' && b == '3')
76 			|| (a == '4' && b == 'a') || (a == 'a' && b == '4')
77 			|| (a == '5' && b == 'g') || (a == 'g' && b == '5')
78 			|| (a == '5' && b == 'r') || (a == 'r' && b == '5')
79 			|| (a == '7' && b == 't') || (a == 't' && b == '7')
80 			|| (a == '9' && b == 'p') || (a == 'p' && b == '9')
81 			|| (a == '0' && b == 'o') || (a == 'o' && b == '0')
82 			|| (a == '$' && b == 's') || (a == 's' && b == '$'))))
83 		return true;
84 	return false;
85 }
86 
87 /*
88     FIXME: this function and it's callers are getting progressively
89     uglier as more features are added :)
90 */
91 static client_t *
SV_Match_User(const char * substr)92 SV_Match_User (const char *substr)
93 {
94 	int         i, j, uid;
95 	int         count = 0;
96 	char       *str;
97 	client_t   *cl;
98 	client_t   *match = 0;
99 
100 	if (!substr || !*substr)
101 		return 0;
102 	uid = strtol (substr, &str, 10);
103 
104 	if (!*str) {
105 		for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
106 			if (cl->state < cs_zombie)
107 				continue;
108 			if (cl->userid == uid) {
109 				SV_Printf ("User %04d matches with name: %s\n",
110 							cl->userid, cl->name);
111 				return cl;
112 			}
113 		}
114 		SV_Printf ("No such uid: %d\n", uid);
115 		return 0;
116 	}
117 
118 	for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
119 		if (cl->state < cs_zombie)
120 			continue;
121 		for (str = cl->name; *str && !match_char (*str, substr[0]); str++)
122 			;
123 		while (*str) {
124 			for (j = 0; substr[j] && str[j]; j++)
125 				if (!match_char (substr[j], str[j]))
126 					break;
127 			if (!substr[j]) {		// found a match;
128 				match = cl;
129 				count++;
130 				SV_Printf ("User %04d matches with name: %s\n",
131 							cl->userid, cl->name);
132 				break;
133 			}
134 			str++;
135 		}
136 	}
137 	if (count != 1) {
138 		SV_Printf ("No unique matches, ignoring command!\n");
139 		return 0;
140 	}
141 	return match;
142 }
143 
144 /*
145 	OPERATOR CONSOLE ONLY COMMANDS
146 
147 	These commands can be entered only from stdin or by a remote operator
148 	datagram
149 */
150 
151 /*
152 	SV_SetMaster_f
153 
154 	Make a master server current
155 */
156 static void
SV_SetMaster_f(void)157 SV_SetMaster_f (void)
158 {
159 	char        data[2];
160 	int         i;
161 
162 	memset (&master_adr, 0, sizeof (master_adr));
163 
164 	for (i = 1; i < Cmd_Argc (); i++) {
165 		if (i > MAX_MASTERS) {
166 			SV_Printf ("Too many masters specified. Using only the first %d\n",
167 					   MAX_MASTERS);
168 			break;
169 		}
170 		if (!strcmp (Cmd_Argv (i), "none")
171 			|| !NET_StringToAdr (Cmd_Argv (i), &master_adr[i - 1])) {
172 			SV_Printf ("Setting nomaster mode.\n");
173 			return;
174 		}
175 		if (master_adr[i - 1].port == 0)
176 			master_adr[i - 1].port = BigShort (27000);
177 
178 		SV_Printf ("Master server at %s\n",
179 					NET_AdrToString (master_adr[i - 1]));
180 
181 		SV_Printf ("Sending a ping.\n");
182 
183 		data[0] = A2A_PING;
184 		data[1] = 0;
185 		Netchan_SendPacket (2, data, master_adr[i - 1]);
186 	}
187 
188 	svs.last_heartbeat = -99999;
189 }
190 
191 #define RESTART_CLSTUFF \
192 	"wait;wait;wait;wait;wait;"\
193 	"wait;wait;wait;wait;wait;"\
194 	"wait;wait;wait;reconnect\n"
195 
196 static void
SV_Restart_f(void)197 SV_Restart_f (void)
198 {
199 	client_t   *client;
200 	int         j;
201 
202 	SZ_Clear (net_message->message);
203 
204 	MSG_WriteByte (net_message->message, svc_print);
205 	MSG_WriteByte (net_message->message, PRINT_HIGH);
206 	MSG_WriteString (net_message->message,
207 		"\x9d\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
208 		"\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
209 		"\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
210 		"\x9e\x9e\x9f\n"
211 		" Server \xf2\xe5\xf3\xf4\xe1\xf2\xf4 engaged\xae\xae\xae\n"
212 		"\x9d\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
213 		"\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
214 		"\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
215 		"\x9e\x9e\x9f\n\n");
216 	MSG_WriteByte (net_message->message, svc_stufftext);
217 	MSG_WriteString (net_message->message, RESTART_CLSTUFF);
218 	MSG_WriteByte (net_message->message, svc_disconnect);
219 
220 	for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) {
221 		if (client->state >= cs_spawned)
222 			Netchan_Transmit (&client->netchan, net_message->message->cursize,
223 							  net_message->message->data);
224 	}
225 	Sys_Printf ("Shutting down: server restart, shell must relaunch server\n");
226 	SV_Shutdown ();
227 	// Error code 2 on exit, indication shell must restart the server
228 	exit (2);
229 }
230 
231 static void
SV_Quit_f(void)232 SV_Quit_f (void)
233 {
234 	SV_FinalMessage ("server shutdown\n");
235 	SV_Printf ("Shutting down.\n");
236 	Sys_Quit ();
237 }
238 
239 static void
SV_Fraglogfile_f(void)240 SV_Fraglogfile_f (void)
241 {
242 	dstring_t  *name;
243 
244 	if (sv_fraglogfile) {
245 		SV_Printf ("Frag file logging off.\n");
246 		Qclose (sv_fraglogfile);
247 		sv_fraglogfile = NULL;
248 		return;
249 	}
250 	name = dstring_new ();
251 	if (!QFS_NextFilename (name,
252 						   va ("%s/frag_", qfs_gamedir->dir.def), ".log")) {
253 		SV_Printf ("Can't open any logfiles.\n");
254 		sv_fraglogfile = NULL;
255 	} else {
256 		SV_Printf ("Logging frags to %s.\n", name->str);
257 		sv_fraglogfile = QFS_WOpen (name->str, 0);
258 	}
259 	dstring_delete (name);
260 }
261 
262 /*
263 	SV_SetPlayer
264 
265 	Sets host_client and sv_player to the player with idnum Cmd_Argv (1)
266 */
267 static qboolean
SV_SetPlayer(void)268 SV_SetPlayer (void)
269 {
270 	client_t   *cl;
271 	int         i, idnum;
272 
273 	idnum = atoi (Cmd_Argv (1));
274 
275 	for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
276 		if (cl->state < cs_zombie)
277 			continue;
278 		if (cl->userid == idnum) {
279 			host_client = cl;
280 			sv_player = host_client->edict;
281 			return true;
282 		}
283 	}
284 	SV_Printf ("Userid %i is not on the server\n", idnum);
285 	return false;
286 }
287 
288 /*
289 	SV_God_f
290 
291 	Sets client to godmode
292 */
293 static void
SV_God_f(void)294 SV_God_f (void)
295 {
296 	if (!sv_allow_cheats) {
297 		SV_Printf
298 			("You must run the server with -cheats to enable this command.\n");
299 		return;
300 	}
301 
302 	if (!SV_SetPlayer ())
303 		return;
304 
305 	SVfloat (sv_player, flags) = (int) SVfloat (sv_player, flags) ^ FL_GODMODE;
306 	if (!((int) SVfloat (sv_player, flags) & FL_GODMODE))
307 		SV_ClientPrintf (1, host_client, PRINT_HIGH, "godmode OFF\n");
308 	else
309 		SV_ClientPrintf (1, host_client, PRINT_HIGH, "godmode ON\n");
310 }
311 
312 static void
SV_Noclip_f(void)313 SV_Noclip_f (void)
314 {
315 	if (!sv_allow_cheats) {
316 		SV_Printf
317 			("You must run the server with -cheats to enable this command.\n");
318 		return;
319 	}
320 
321 	if (!SV_SetPlayer ())
322 		return;
323 
324 	if (SVfloat (sv_player, movetype) != MOVETYPE_NOCLIP) {
325 		SVfloat (sv_player, movetype) = MOVETYPE_NOCLIP;
326 		SV_ClientPrintf (1, host_client, PRINT_HIGH, "noclip ON\n");
327 	} else {
328 		SVfloat (sv_player, movetype) = MOVETYPE_WALK;
329 		SV_ClientPrintf (1, host_client, PRINT_HIGH, "noclip OFF\n");
330 	}
331 }
332 
333 static void
SV_Give_f(void)334 SV_Give_f (void)
335 {
336 	const char *t;
337 	int         v;
338 
339 	if (!sv_allow_cheats) {
340 		SV_Printf
341 			("You must run the server with -cheats to enable this command.\n");
342 		return;
343 	}
344 
345 	if (!SV_SetPlayer ())
346 		return;
347 
348 	t = Cmd_Argv (2);
349 	v = atoi (Cmd_Argv (3));
350 
351 	switch (t[0]) {
352 		case '2':
353 		case '3':
354 		case '4':
355 		case '5':
356 		case '6':
357 		case '7':
358 		case '8':
359 		case '9':
360 			SVfloat (sv_player, items) =
361 				(int) SVfloat (sv_player, items) | IT_SHOTGUN << (t[0] - '2');
362 			break;
363 
364 		case 's':
365 			SVfloat (sv_player, ammo_shells) = v;
366 			break;
367 		case 'n':
368 			SVfloat (sv_player, ammo_nails) = v;
369 			break;
370 		case 'r':
371 			SVfloat (sv_player, ammo_rockets) = v;
372 			break;
373 		case 'h':
374 			SVfloat (sv_player, health) = v;
375 			break;
376 		case 'c':
377 			SVfloat (sv_player, ammo_cells) = v;
378 			break;
379 	}
380 }
381 
382 // Use this to keep track of current level  --KB
383 static dstring_t *curlevel;
384 
385 const char *
SV_Current_Map(void)386 SV_Current_Map (void)
387 {
388 	return curlevel ? curlevel->str : "";
389 }
390 
391 static const char *
nice_time(float time)392 nice_time (float time)
393 {
394 	int         t = time + 0.5;
395 
396 #if 0 //FIXME ditch or cvar?
397 	if (t < 60) {
398 		return va ("%ds", t);
399 	} else if (t < 600) {
400 		return va ("%dm%02ds", t / 60, t % 60);
401 	} else if (t < 3600) {
402 		return va ("%dm", t / 60);
403 	} else if (t < 36000) {
404 		t /= 60;
405 		return va ("%dh%02dm", t / 60, t % 60);
406 	} else if (t < 86400) {
407 		return va ("%dh", t / 3600);
408 	} else {
409 		t /= 3600;
410 		return va ("%dd%02dh", t / 24, t % 24);
411 	}
412 #endif
413 	if (t < 60) {
414 		return va ("%ds", t);
415 	} else if (t < 3600) {
416 		return va ("%dm%02ds", t / 60, t % 60);
417 	} else if (t < 86400) {
418 		return va ("%dh%02dm%02ds", t / 3600, (t / 60) % 60, t % 60);
419 	} else {
420 		return va ("%dd%02dh%02dm%02ds",
421 				   t / 86400, (t / 3600) % 24, (t / 60) % 60, t % 60);
422 	}
423 }
424 
425 /*
426 	SV_Map_f
427 
428 	handle a
429 	map <mapname>
430 	command from the console or progs.
431 */
432 static void
SV_Map_f(void)433 SV_Map_f (void)
434 {
435 	const char *level;
436 	char       *expanded;
437 	QFile      *f;
438 
439 	if (!curlevel)
440 		curlevel = dstring_newstr ();
441 
442 	if (Cmd_Argc () > 2) {
443 		SV_Printf ("map <levelname> : continue game on a new level\n");
444 		return;
445 	}
446 	if (Cmd_Argc () == 1) {
447 		SV_Printf ("map is %s \"%s\" (%s)\n", curlevel->str,
448 				   PR_GetString (&sv_pr_state, SVstring (sv.edicts, message)),
449 				   nice_time (sv.time));
450 		return;
451 	}
452 	level = Cmd_Argv (1);
453 
454 	// check to make sure the level exists
455 	expanded = nva ("maps/%s.bsp", level);
456 	QFS_FOpenFile (expanded, &f);
457 	if (!f) {
458 		SV_Printf ("Can't find %s\n", expanded);
459 		free (expanded);
460 		return;
461 	}
462 	Qclose (f);
463 	free (expanded);
464 
465 	if (sv.recording_demo)
466 		SV_Stop (0);
467 
468 	SV_qtvChanging ();
469 	SV_BroadcastCommand ("changing\n");
470 	SV_SendMessagesToAll ();
471 
472 	dstring_copystr (curlevel, level);
473 	SV_SpawnServer (level);
474 
475 	SV_qtvReconnect ();
476 	SV_BroadcastCommand ("reconnect\n");
477 }
478 
479 /*
480 	SV_Kick_f
481 
482 	Kick a user off of the server
483 */
484 static void
SV_Kick_f(void)485 SV_Kick_f (void)
486 {
487 	client_t   *cl;
488 	int         argc = Cmd_Argc ();
489 	const char *reason;
490 
491 	if (argc < 2) {
492 		SV_Printf ("usage: kick <name/userid>\n");
493 		return;
494 	}
495 	if (!(cl = SV_Match_User (Cmd_Argv (1))))
496 		return;
497 
498 	// print directly, because the dropped client won't get the
499 	// SV_BroadcastPrintf message
500 	if (argc > 2) {
501 		reason = Cmd_Args (2);
502 		SV_BroadcastPrintf (PRINT_HIGH, "%s was kicked: %s\n", cl->name,
503 							reason);
504 		SV_ClientPrintf (1, cl, PRINT_HIGH,
505 						 "You were kicked from the game: %s\n", reason);
506 	} else {
507 		SV_BroadcastPrintf (PRINT_HIGH, "%s was kicked\n", cl->name);
508 		SV_ClientPrintf (1, cl, PRINT_HIGH, "You were kicked from the game\n");
509 	}
510 	SV_DropClient (cl);
511 }
512 
513 void
SV_Status_f(void)514 SV_Status_f (void)
515 {
516 	int         i;
517 	client_t   *cl;
518 	float       cpu, avg, pak, demo = 0;
519 	const char *s;
520 
521 
522 	cpu = (svs.stats.latched_active + svs.stats.latched_idle);
523 	if (cpu) {
524 		demo = 100 * svs.stats.latched_demo / cpu;
525 		cpu = 100 * svs.stats.latched_active / cpu;
526 	}
527 	avg = 1000 * svs.stats.latched_active / STATFRAMES;
528 	pak = (float) svs.stats.latched_packets / STATFRAMES;
529 
530 	SV_Printf ("net address      : %s\n", NET_AdrToString (net_local_adr));
531 	SV_Printf ("uptime           : %s\n", nice_time (Sys_DoubleTime ()));
532 	SV_Printf ("cpu utilization  : %3i%% (%3i%%)\n", (int) cpu, (int)demo);
533 	SV_Printf ("avg response time: %i ms\n", (int) avg);
534 	SV_Printf ("packets/frame    : %5.2f\n", pak);
535 
536 	// min fps lat drp
537 	if (sv_redirected != RD_NONE && sv_redirected != RD_MOD) {
538 		// most remote clients are 40 columns
539 		//          0123456789012345678901234567890123456789
540 		SV_Printf ("name               userid frags\n");
541 		SV_Printf ("  address          rate ping drop\n");
542 		SV_Printf ("  ---------------- ---- ---- -----\n");
543 		for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
544 			if (!cl->state)
545 				continue;
546 
547 			SV_Printf ("%-16.16s  ", cl->name);
548 
549 			SV_Printf ("%6i %5i", cl->userid, (int) SVfloat (cl->edict, frags));
550 			if (cl->spectator)
551 				SV_Printf (" (s)\n");
552 			else
553 				SV_Printf ("\n");
554 
555 			s = NET_BaseAdrToString (cl->netchan.remote_address);
556 			SV_Printf ("  %-16.16s", s);
557 			if (cl->state == cs_connected) {
558 				SV_Printf ("CONNECTING\n");
559 				continue;
560 			}
561 			if (cl->state == cs_zombie) {
562 				SV_Printf ("ZOMBIE\n");
563 				continue;
564 			}
565 			if (cl->state == cs_server) {
566 				SV_Printf ("SERVER %d\n", cl->ping);
567 				continue;
568 			}
569 			SV_Printf ("%4i %4i %5.2f\n",
570 					   (int) (1000 * cl->netchan.frame_rate),
571 					   (int) SV_CalcPing (cl),
572 					   100.0 * cl->netchan.drop_count /
573 							cl->netchan.incoming_sequence);
574 		}
575 	} else {
576 		SV_Printf ("frags userid address         name            rate ping "
577 				   "drop  qport\n");
578 		SV_Printf ("----- ------ --------------- --------------- ---- ---- "
579 				   "----- -----\n");
580 		for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
581 			if (!cl->state)
582 				continue;
583 			SV_Printf ("%5i %6i ", (int) SVfloat (cl->edict, frags),
584 					   cl->userid);
585 
586 			s = NET_BaseAdrToString (cl->netchan.remote_address);
587 
588 			SV_Printf ("%-15.15s ", s);
589 
590 			SV_Printf ("%-15.15s ", cl->name);
591 
592 			if (cl->state == cs_connected) {
593 				SV_Printf ("CONNECTING\n");
594 				continue;
595 			}
596 			if (cl->state == cs_zombie) {
597 				SV_Printf ("ZOMBIE\n");
598 				continue;
599 			}
600 			if (cl->state == cs_server) {
601 				SV_Printf ("SERVER %d\n", cl->ping);
602 				continue;
603 			}
604 			SV_Printf ("%4i %4i %3.1f %4i",
605 						(int) (1000 * cl->netchan.frame_rate),
606 						(int) SV_CalcPing (cl),
607 						100.0 * cl->netchan.drop_count /
608 						cl->netchan.incoming_sequence, cl->netchan.qport);
609 			if (cl->spectator)
610 				SV_Printf (" (s)\n");
611 			else
612 				SV_Printf ("\n");
613 		}
614 	}
615 	SV_Printf ("\n");
616 }
617 
618 #define MAXPENALTY 10.0
619 
620 static void
SV_Punish(int mode)621 SV_Punish (int mode)
622 {
623 	int         i;
624 	double      mins = 0.5;
625 	qboolean    all = false, done = false;
626 	client_t    *cl = 0;
627 	dstring_t  *text = dstring_new();
628 	const char *cmd = 0;
629 	const char *cmd_do = 0;
630 	//FIXME const char *cmd_undo = 0;
631 	int         field_offs = 0;
632 
633 	switch (mode) {
634 		case 0:
635 			cmd = "cuff";
636 			cmd_do = "cuffed";
637 			//FIXME cmd_undo = "un-cuffed";
638 			field_offs = field_offset (client_t, cuff_time);
639 			break;
640 		case 1:
641 			cmd = "mute";
642 			cmd_do = "muted";
643 			//FIXME cmd_undo = "can speak";
644 			field_offs = field_offset (client_t, lockedtill);
645 			break;
646 	}
647 
648 	if (Cmd_Argc () != 2 && Cmd_Argc () != 3) {
649 		SV_Printf ("usage: %s <name/userid/ALL> [minutes]\n"
650 				   "       (default = 0.5, 0 = cancel cuff).\n", cmd);
651 		return;
652 	}
653 
654 	if (strequal (Cmd_Argv (1), "ALL")) {
655 		all = true;
656 	} else {
657 		cl = SV_Match_User (Cmd_Argv (1));
658 	}
659 	if (!all && !cl)
660 		return;
661 	if (Cmd_Argc () == 3) {
662 		mins = atof (Cmd_Argv (2));
663 		if (mins < 0.0 || mins > MAXPENALTY)
664 			mins = MAXPENALTY;
665 	}
666 	if (cl) {
667 		*(double *)((char *)cl + field_offs) = realtime + mins * 60.0;
668 		if (mins) {
669 			dsprintf (text, "You are %s for %.1f minutes\n\n"
670 					 "reconnecting won't help...\n", cmd_do, mins);
671 			MSG_ReliableWrite_Begin (&cl->backbuf, svc_centerprint,
672 									 2 + strlen (text->str));
673 			MSG_ReliableWrite_String (&cl->backbuf, text->str);
674 		}
675 	}
676 	if (all) {
677 		for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
678 			if (cl->state < cs_zombie)
679 				continue;
680 			*(double *)((char *)cl + field_offs) = realtime + mins * 60.0;
681 			done = true;
682 			if (mins) {
683 				dsprintf (text, "You are %s for %.1f minutes\n\n"
684 						 "reconnecting won't help...\n", cmd_do, mins);
685 				MSG_ReliableWrite_Begin (&cl->backbuf, svc_centerprint,
686 										 2 + strlen (text->str));
687 				MSG_ReliableWrite_String (&cl->backbuf, text->str);
688 			}
689 		}
690 	}
691 	if (done) {
692 		if (mins)
693 			SV_BroadcastPrintf (PRINT_HIGH, "%s %s for %.1f minutes.\n",
694 								all? "All Users" : cl->name, cmd_do, mins);
695 		else
696 			SV_BroadcastPrintf (PRINT_HIGH, "%s %s.\n",
697 								all? "All Users" : cl->name, cmd_do);
698 	}
699 	dstring_delete (text);
700 }
701 
702 static void
SV_Cuff_f(void)703 SV_Cuff_f (void)
704 {
705 	SV_Punish (0);
706 }
707 
708 
709 static void
SV_Mute_f(void)710 SV_Mute_f (void)
711 {
712 	SV_Punish (1);
713 }
714 
715 static void
SV_Ban_f(void)716 SV_Ban_f (void)
717 {
718 	double      mins = 30.0, m;
719 	client_t   *cl;
720 	char       *e;
721 	const char *a, *reason;
722 	int         argc = Cmd_Argc (), argr = 2;
723 
724 	if (argc < 2) {
725 		SV_Printf ("usage: ban <name/userid> [minutes] [reason]\n"
726 				   "       (default = 30, 0 = permanent).\n");
727 		return;
728 	}
729 	if (!(cl = SV_Match_User (Cmd_Argv (1))))
730 		return;
731 	if (argc >= 3) {
732 		a = Cmd_Argv (2);
733 		m = strtod (a, &e);
734 		if (e != a) {
735 			argr++;
736 			mins = m;
737 			if (mins < 0.0 || mins > 1000000.0)			// bout 2 yrs
738 				mins = 0.0;
739 		}
740 	}
741 	if (argc > argr) {
742 		reason = Cmd_Args (argr);
743 		SV_BroadcastPrintf (PRINT_HIGH, "Admin Banned user %s %s: %s\n",
744 							cl->name, mins ? va ("for %.1f minutes", mins)
745 										   : "permanently", reason);
746 	} else {
747 		SV_BroadcastPrintf (PRINT_HIGH, "Admin Banned user %s %s\n",
748 							cl->name, mins ? va ("for %.1f minutes", mins)
749 										   : "permanently");
750 	}
751 	SV_DropClient (cl);
752 	Cmd_ExecuteString (va ("addip %s %f",
753 						   NET_BaseAdrToString (cl->netchan.remote_address),
754 						   mins), src_command);
755 }
756 
757 static void
SV_Match_f(void)758 SV_Match_f (void)
759 {
760 	if (Cmd_Argc () != 2) {
761 		SV_Printf ("usage: match <name/userid>\n");
762 		return;
763 	}
764 
765 	if (!SV_Match_User (Cmd_Argv (1)))
766 		SV_Printf ("No unique uid/name matched\n");
767 }
768 
769 
770 static void
SV_ConSay(const char * prefix,client_t * client)771 SV_ConSay (const char *prefix, client_t *client)
772 {
773 	char       *p;
774 	dstring_t  *text;
775 	int         j;
776 
777 	if (Cmd_Argc () < 2)
778 		return;
779 
780 	p = Hunk_TempAlloc (strlen (Cmd_Args (1)) + 1);
781 	strcpy (p, Cmd_Args (1));
782 	if (*p == '"') {
783 		p++;
784 		p[strlen (p) - 1] = 0;
785 	}
786 	text = dstring_new ();
787 	dstring_copystr (text, prefix);
788 	dstring_appendstr (text, "\x8d ");				// arrow
789 	j = strlen (text->str);
790 	dstring_appendstr (text, p);
791 	SV_Printf ("%s\n", text->str);
792 	while (text->str[j])
793 		text->str[j++] |= 0x80;				// non-bold text
794 
795 	if (client) {
796 		if (client->state >= cs_zombie) {
797 			SV_ClientPrintf (1, client, PRINT_CHAT, "\n"); // bell
798 			SV_ClientPrintf (1, client, PRINT_HIGH, "%s\n", text->str);
799 			SV_ClientPrintf (1, client, PRINT_CHAT, "%s", ""); // bell
800 		}
801 	} else {
802 		for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) {
803 			if (client->state < cs_zombie)
804 				continue;
805 			SV_ClientPrintf (0, client, PRINT_HIGH, "%s\n", text->str);
806 			if (*prefix != 'I')		// beep, except for Info says
807 				SV_ClientPrintf (0, client, PRINT_CHAT, "%s", "");
808 		}
809 		if (sv.recorders) {
810 			sizebuf_t *dbuf;
811 			dbuf = SVR_WriteBegin (dem_all, 0, strlen (text->str) + 7);
812 			MSG_WriteByte (dbuf, svc_print);
813 			MSG_WriteByte (dbuf, PRINT_HIGH);
814 			MSG_WriteString (dbuf, va ("%s\n", text->str));
815 			MSG_WriteByte (dbuf, svc_print);
816 			MSG_WriteByte (dbuf, PRINT_CHAT);
817 			MSG_WriteString (dbuf, "");
818 		}
819 	}
820 }
821 
822 static void
SV_Tell_f(void)823 SV_Tell_f (void)
824 {
825 	client_t   *cl;
826 
827 	if (Cmd_Argc () < 3) {
828 		SV_Printf ("usage: tell <name/userid> <text...>\n");
829 		return;
830 	}
831 	if (!(cl = SV_Match_User (Cmd_Argv (1))))
832 		return;
833 
834 	if (rcon_from_user)
835 		SV_ConSay ("[\xd0\xd2\xc9\xd6\xc1\xd4\xc5] Admin", cl);
836 	else
837 		SV_ConSay ("[\xd0\xd2\xc9\xd6\xc1\xd4\xc5] Console", cl);
838 }
839 
840 static void
SV_ConSay_f(void)841 SV_ConSay_f (void)
842 {
843 	if (rcon_from_user)
844 		SV_ConSay ("Admin", 0);
845 	else
846 		SV_ConSay ("Console", 0);
847 }
848 
849 static void
SV_ConSay_Info_f(void)850 SV_ConSay_Info_f (void)
851 {
852 	SV_ConSay ("Info", 0);
853 }
854 
855 static void
SV_Heartbeat_f(void)856 SV_Heartbeat_f (void)
857 {
858 	svs.last_heartbeat = -9999;
859 }
860 
861 static void
SV_SendServerInfoChange(const char * key,const char * value)862 SV_SendServerInfoChange (const char *key, const char *value)
863 {
864 	if (!sv.state)
865 		return;
866 
867 	MSG_WriteByte (&sv.reliable_datagram, svc_serverinfo);
868 	MSG_WriteString (&sv.reliable_datagram, key);
869 	MSG_WriteString (&sv.reliable_datagram, value);
870 }
871 
872 void
Cvar_Info(cvar_t * var)873 Cvar_Info (cvar_t *var)
874 {
875 	if (var->flags & CVAR_SERVERINFO) {
876 		Info_SetValueForKey (svs.info, var->name, var->string,
877 							 (!sv_highchars || !sv_highchars->int_val));
878 
879 		SV_SendServerInfoChange (var->name, var->string);
880 	}
881 }
882 
883 /*
884 	SV_Serverinfo_f
885 
886 	Examine or change the serverinfo string
887 */
888 static void
SV_Serverinfo_f(void)889 SV_Serverinfo_f (void)
890 {
891 	cvar_t     *var;
892 	const char *key;
893 	const char *value;
894 
895 	switch (Cmd_Argc ()) {
896 		case 1:
897 			SV_Printf ("Server info settings:\n");
898 			Info_Print (svs.info);
899 			return;
900 		case 2:
901 			key = Cmd_Argv (1);
902 			value = Info_ValueForKey (svs.info, key);
903 			if (Info_Key (svs.info, key))
904 				SV_Printf ("Server info %s: \"%s\"\n", key, value);
905 			else
906 				SV_Printf ("No such key %s\n", Cmd_Argv(1));
907 			return;
908 		case 3:
909 			key = Cmd_Argv (1);
910 			value = Cmd_Argv (2);
911 			break;
912 		default:
913 			SV_Printf ("usage: serverinfo [ <key> <value> ]\n");
914 			return;
915 	}
916 
917 	if (Cmd_Argv (1)[0] == '*') {
918 		SV_Printf ("Star variables cannot be changed.\n");
919 		return;
920 	}
921 
922 	// if this is a cvar, change it too
923 	var = Cvar_FindVar (key);
924 	if (var && (var->flags & CVAR_SERVERINFO)) {
925 		Cvar_Set (var, value);
926 	} else {
927 		Info_SetValueForKey (svs.info, key, value, !sv_highchars->int_val);
928 		SV_SendServerInfoChange (key, value);
929 	}
930 }
931 
932 void
SV_SetLocalinfo(const char * key,const char * value)933 SV_SetLocalinfo (const char *key, const char *value)
934 {
935 	char       *oldvalue = 0;
936 
937 	if (sv_funcs.LocalinfoChanged)
938 		oldvalue = strdup (Info_ValueForKey (localinfo, key));
939 
940 	if (*value)
941 		Info_SetValueForKey (localinfo, key, value, !sv_highchars->int_val);
942 	else
943 		Info_RemoveKey (localinfo, key);
944 
945 	if (sv_funcs.LocalinfoChanged) {
946 		*sv_globals.time = sv.time;
947 		*sv_globals.self = 0;
948 		PR_PushFrame (&sv_pr_state);
949 		PR_RESET_PARAMS (&sv_pr_state);
950 		P_STRING (&sv_pr_state, 0) = PR_SetTempString (&sv_pr_state, key);
951 		P_STRING (&sv_pr_state, 1) = PR_SetTempString (&sv_pr_state, oldvalue);
952 		P_STRING (&sv_pr_state, 2) = PR_SetTempString (&sv_pr_state, value);
953 		PR_ExecuteProgram (&sv_pr_state, sv_funcs.LocalinfoChanged);
954 		PR_PopFrame (&sv_pr_state);
955 	}
956 
957 	if (oldvalue)
958 		free (oldvalue);
959 }
960 
961 /*
962 	SV_Serverinfo_f
963 
964 	Examine or change the serverinfo string
965 */
966 static void
SV_Localinfo_f(void)967 SV_Localinfo_f (void)
968 {
969 	const char *key;
970 	const char *value;
971 
972 	switch (Cmd_Argc ()) {
973 		case 1:
974 			SV_Printf ("Local info settings:\n");
975 			Info_Print (localinfo);
976 			return;
977 		case 2:
978 			key = Cmd_Argv (1);
979 			value = Info_ValueForKey (localinfo, key);
980 			if (Info_Key (localinfo, key))
981 				SV_Printf ("Localinfo %s: \"%s\"\n", key, value);
982 			else
983 				SV_Printf ("No such key %s\n", Cmd_Argv(1));
984 			return;
985 		case 3:
986 			key = Cmd_Argv (1);
987 			value = Cmd_Argv (2);
988 			break;
989 		default:
990 			SV_Printf ("usage: localinfo [ <key> <value> ]\n");
991 			return;
992 	}
993 
994 	if (key[0] == '*') {
995 		SV_Printf ("Star variables cannot be changed.\n");
996 		return;
997 	}
998 	SV_SetLocalinfo (key, value);
999 }
1000 
1001 /*
1002 	SV_User_f
1003 
1004 	Examine a users info strings
1005 */
1006 static void
SV_User_f(void)1007 SV_User_f (void)
1008 {
1009 	if (Cmd_Argc () != 2) {
1010 		SV_Printf ("Usage: user <userid>\n");
1011 		return;
1012 	}
1013 
1014 	if (!SV_SetPlayer ())
1015 		return;
1016 
1017 	Info_Print (host_client->userinfo);
1018 }
1019 
1020 /*
1021 	SV_Gamedir
1022 
1023 	Sets the fake *gamedir to a different directory.
1024 */
1025 static void
SV_Gamedir(void)1026 SV_Gamedir (void)
1027 {
1028 	const char *dir;
1029 
1030 	if (Cmd_Argc () == 1) {
1031 		SV_Printf ("Current *gamedir: %s\n",
1032 					Info_ValueForKey (svs.info, "*gamedir"));
1033 		return;
1034 	}
1035 
1036 	if (Cmd_Argc () != 2) {
1037 		SV_Printf ("Usage: sv_gamedir <newgamedir>\n");
1038 		return;
1039 	}
1040 
1041 	dir = Cmd_Argv (1);
1042 
1043 	if (strstr (dir, "..") || strstr (dir, "/")
1044 		|| strstr (dir, "\\") || strstr (dir, ":")) {
1045 		SV_Printf ("*Gamedir should be a single filename, not a path\n");
1046 		return;
1047 	}
1048 
1049 	Info_SetValueForStarKey (svs.info, "*gamedir", dir,
1050 							 !sv_highchars->int_val);
1051 }
1052 
1053 /*
1054 	SV_Floodport_f
1055 
1056 	Sets the gamedir and path to a different directory.
1057 */
1058 static void
SV_Floodprot_f(void)1059 SV_Floodprot_f (void)
1060 {
1061 	int         arg1, arg2, arg3;
1062 
1063 	if (Cmd_Argc () == 1) {
1064 		if (fp_messages) {
1065 			SV_Printf
1066 				("Current floodprot settings: \nAfter %d msgs per %d seconds, "
1067 				 "silence for %d seconds\n",
1068 				 fp_messages, fp_persecond, fp_secondsdead);
1069 			return;
1070 		} else
1071 			SV_Printf ("No floodprots enabled.\n");
1072 	}
1073 
1074 	if (Cmd_Argc () != 4) {
1075 		SV_Printf
1076 			("Usage: floodprot <# of messages> <per # of seconds> <seconds to "
1077 			 "silence>\n");
1078 		SV_Printf
1079 			("Use floodprotmsg to set a custom message to say to the "
1080 			 "flooder.\n");
1081 		return;
1082 	}
1083 
1084 	arg1 = atoi (Cmd_Argv (1));
1085 	arg2 = atoi (Cmd_Argv (2));
1086 	arg3 = atoi (Cmd_Argv (3));
1087 
1088 	if (arg1 <= 0 || arg2 <= 0 || arg3 <= 0) {
1089 		SV_Printf ("All values must be positive numbers\n");
1090 		return;
1091 	}
1092 
1093 	if (arg1 > 10) {
1094 		SV_Printf ("Can track up to only 10 messages.\n");
1095 		return;
1096 	}
1097 
1098 	fp_messages = arg1;
1099 	fp_persecond = arg2;
1100 	fp_secondsdead = arg3;
1101 }
1102 
1103 static void
SV_Floodprotmsg_f(void)1104 SV_Floodprotmsg_f (void)
1105 {
1106 	if (Cmd_Argc () == 1) {
1107 		SV_Printf ("Current msg: %s\n", fp_msg);
1108 		return;
1109 	} else if (Cmd_Argc () != 2) {
1110 		SV_Printf ("Usage: floodprotmsg \"<message>\"\n");
1111 		return;
1112 	}
1113 	snprintf (fp_msg, sizeof (fp_msg), "%s", Cmd_Argv (1));
1114 }
1115 
1116 static void
SV_Snap(int uid)1117 SV_Snap (int uid)
1118 {
1119 	client_t   *cl;
1120 	int         i;
1121 
1122 	for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
1123 		if (cl->state < cs_zombie)
1124 			continue;
1125 		if (cl->userid == uid)
1126 			break;
1127 	}
1128 	if (i >= MAX_CLIENTS) {
1129 		SV_Printf ("userid not found\n");
1130 		return;
1131 	}
1132 
1133 	if (!cl->uploadfn)
1134 		cl->uploadfn = dstring_new ();
1135 
1136 	if (!QFS_NextFilename (cl->uploadfn,
1137 						   va ("%s/snap/%d-", qfs_gamedir->dir.def, uid),
1138 						   ".pcx")) {
1139 		SV_Printf ("Snap: Couldn't create a file, clean some out.\n");
1140 		dstring_delete (cl->uploadfn);
1141 		cl->uploadfn = 0;
1142 		return;
1143 	}
1144 
1145 	memcpy (&cl->snap_from, &net_from, sizeof (net_from));
1146 	if (sv_redirected != RD_NONE)
1147 		cl->remote_snap = true;
1148 	else
1149 		cl->remote_snap = false;
1150 
1151 	MSG_ReliableWrite_Begin (&cl->backbuf, svc_stufftext, 24);
1152 	MSG_ReliableWrite_String (&cl->backbuf, "cmd snap\n");
1153 	SV_Printf ("Requesting snap from user %d...\n", uid);
1154 }
1155 
1156 static void
SV_Snap_f(void)1157 SV_Snap_f (void)
1158 {
1159 	int         uid;
1160 
1161 	if (Cmd_Argc () != 2) {
1162 		SV_Printf ("Usage:  snap <userid>\n");
1163 		return;
1164 	}
1165 
1166 	uid = atoi (Cmd_Argv (1));
1167 
1168 	SV_Snap (uid);
1169 }
1170 
1171 static void
SV_SnapAll_f(void)1172 SV_SnapAll_f (void)
1173 {
1174 	client_t   *cl;
1175 	int         i;
1176 
1177 	for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
1178 		if (cl->state < cs_connected || cl->spectator)
1179 			continue;
1180 		SV_Snap (cl->userid);
1181 	}
1182 }
1183 
1184 void
SV_InitOperatorCommands(void)1185 SV_InitOperatorCommands (void)
1186 {
1187 	if (COM_CheckParm ("-cheats")) {
1188 		sv_allow_cheats = true;
1189 		Info_SetValueForStarKey (svs.info, "*cheats", "ON", 0);
1190 	}
1191 
1192 	Cmd_AddCommand ("fraglogfile", SV_Fraglogfile_f, "Enables logging of kills "
1193 					"to frag_##.log");
1194 
1195 	Cmd_AddCommand ("snap", SV_Snap_f, "Take a screenshot of userid");
1196 	Cmd_AddCommand ("snapall", SV_SnapAll_f, "Take a screenshot of all users");
1197 	Cmd_AddCommand ("kick", SV_Kick_f, "Remove a user from the server (kick "
1198 					"userid)");
1199 	Cmd_AddCommand ("status", SV_Status_f, "Report information on the current "
1200 					"connected clients and the server - displays userids");
1201 
1202 	Cmd_AddCommand ("map", SV_Map_f, "Change to a new map (map mapname)");
1203 	Cmd_AddCommand ("setmaster", SV_SetMaster_f, "Lists the server with up to "
1204 					"eight masters.\n"
1205 					"When a server is listed with a master, the master is "
1206 					"aware of the server's IP address and port and it is added "
1207 					"to the\n"
1208 					"list of current servers connected to a master. A "
1209 					"heartbeat is sent to the master from the server to "
1210 					"indicated that the\n"
1211 					"server is still running and alive.\n\n"
1212 					"Examples:\n"
1213 					"setmaster 192.246.40.12:27002\n"
1214 					"setmaster 192.246.40.12:27002 192.246.40.12:27004");
1215 	Cmd_AddCommand ("heartbeat", SV_Heartbeat_f, "Force a heartbeat to be sent "
1216 					"to the master server.\n"
1217 					"A heartbeat tells the Master the server's IP address and "
1218 					"that it is still alive.");
1219 	Cmd_AddCommand ("restart", SV_Restart_f, "Restart the server (with shell "
1220 					"support)");
1221 	Cmd_AddCommand ("quit", SV_Quit_f, "Shut down the server");
1222 	Cmd_AddCommand ("god", SV_God_f, "Toggle god cheat to userid (god userid) "
1223 					"Requires cheats are enabled");
1224 	Cmd_AddCommand ("give", SV_Give_f, "Give userid items, or health.\n"
1225 					"Items: 1 Axe, 2 Shotgun, 3 Double-Barrelled Shotgun, 4 "
1226 					"Nailgun, 5 Super Nailgun, 6 Grenade Launcher, 7 Rocket "
1227 					"Launcher,\n"
1228 					"8 ThunderBolt, C Cells, H Health, N Nails, R Rockets, S "
1229 					"Shells. Requires cheats to be enabled. (give userid item "
1230 					"amount)");
1231 	Cmd_AddCommand ("noclip", SV_Noclip_f, "Toggle no clipping cheat for "
1232 					"userid. Requires cheats to be enabled. (noclip userid)");
1233 	Cmd_AddCommand ("serverinfo", SV_Serverinfo_f, "Reports or sets "
1234 					"information about server.\n"
1235 					"The information stored in this space is broadcast on the "
1236 					"network to all players.\n"
1237 					"Values:\n"
1238 					"dq - Drop Quad Damage when a player dies.\n"
1239 					"dr - Drop Ring of Shadows when a player dies.\n"
1240 					"rj - Sets the multiplier rate for splash damage kick.\n"
1241 					"needpass - Displays the passwords enabled on the server.\n"
1242 					"watervis - Toggle the use of r_watervis by OpenGL "
1243 					"clients.\n"
1244 					"Note: Keys with (*) in front cannot be changed. Maximum "
1245 					"key size cannot exceed 64-bytes.\n"
1246 					"Maximum size for all keys cannot exceed 512-bytes.\n"
1247 					"(serverinfo key value)");
1248 	Cmd_AddCommand ("localinfo", SV_Localinfo_f, "Shows or sets localinfo "
1249 					"variables.\n"
1250 					"Useful for mod programmers who need to allow the admin to "
1251 					"change settings.\n"
1252 					"This is an alternative storage space to the serverinfo "
1253 					"space for mod variables.\n"
1254 					"The variables stored in this space are not broadcast on "
1255 					"the network.\n"
1256 					"This space also has a 32-kilobyte limit which is much "
1257 					"greater then the 512-byte limit on the serverinfo space.\n"
1258 					"Special Keys: (current map) (next map) - Using this "
1259 					"combination will allow the creation of a custom map cycle "
1260 					"without editing code.\n\n"
1261 					"Example:\n"
1262 					"localinfo dm2 dm4\n"
1263 					"localinfo dm4 dm6\n"
1264 					"localinfo dm6 dm2\n"
1265 					"(localinfo key value)");
1266 	Cmd_AddCommand ("user", SV_User_f, "Report information about the user "
1267 					"(user userid)");
1268 	Cmd_AddCommand ("sv_gamedir", SV_Gamedir, "Displays or determines the "
1269 					"value of the serverinfo *gamedir variable.\n"
1270 					"Note: Useful when the physical gamedir directory has a "
1271 					"different name than the widely accepted gamedir "
1272 					"directory.\n"
1273 					"Example:\n"
1274 					"gamedir tf2_5; sv_gamedir fortress\n"
1275 					"gamedir ctf4_2; sv_gamedir ctf\n"
1276 					"(sv_gamedir dirname)");
1277 	Cmd_AddCommand ("floodprot", SV_Floodprot_f, "Sets the options for flood "
1278 					"protection.\n"
1279 					"Default: 4 4 10\n"
1280 					"(floodprot (number of messages) (number of seconds) "
1281 					"(silence time in seconds))");
1282 	Cmd_AddCommand ("floodprotmsg", SV_Floodprotmsg_f, "Sets the message "
1283 					"displayed after flood protection is invoked (floodprotmsg "
1284 					"message)");
1285 	Cmd_AddCommand ("maplist", Con_Maplist_f, "List all maps on the server");
1286 	Cmd_AddCommand ("say", SV_ConSay_f, "Say something to everyone on the "
1287 					"server. Will show up as the name 'Console' (or 'Admin') "
1288 					"in game");
1289 	Cmd_AddCommand ("sayinfo", SV_ConSay_Info_f, "Say something to everyone on "
1290 					"the server. Will show up as the name 'Info' in game");
1291 	Cmd_AddCommand ("tell", SV_Tell_f, "Say something to a specific user on "
1292 					"the server. Will show up as the name 'Console' (or "
1293 					"'Admin') in game");
1294 	Cmd_AddCommand ("ban", SV_Ban_f, "ban a player for a specified time");
1295 	Cmd_AddCommand ("cuff", SV_Cuff_f, "\"hand-cuff\" a player for a "
1296 					"specified time");
1297 	Cmd_AddCommand ("mute", SV_Mute_f, "silience a player for a specified "
1298 					"time");
1299 	Cmd_AddCommand ("match", SV_Match_f, "matches nicks as ban/cuff/mute "
1300 					"commands do, so you can check safely");
1301 
1302 	// poor description
1303 	sv_leetnickmatch = Cvar_Get ("sv_3133735_7h4n_7h0u", "1", CVAR_NONE, NULL,
1304 								 "Match '1' as 'i' and such in nicks");
1305 }
1306