1 /*
2  * XPilot NG, a multiplayer space war game.
3  *
4  * Copyright (C) 2000-2004 by
5  *
6  *      Uoti Urpala          <uau@users.sourceforge.net>
7  *      Erik Andersson       <maximan@users.sourceforge.net>
8  *      Kristian S�derblom   <kps@users.sourceforge.net>
9  *
10  * Copyright (C) 1991-2001 by
11  *
12  *      Bj�rn Stabell        <bjoern@xpilot.org>
13  *      Ken Ronny Schouten   <ken@xpilot.org>
14  *      Bert Gijsbers        <bert@xpilot.org>
15  *      Dick Balaska         <dick@xpilot.org>
16  *
17  * This program is free software; you can redistribute it and/or modify
18  * it under the terms of the GNU General Public License as published by
19  * the Free Software Foundation; either version 2 of the License, or
20  * (at your option) any later version.
21  *
22  * This program is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25  * GNU General Public License for more details.
26  *
27  * You should have received a copy of the GNU General Public License
28  * along with this program; if not, write to the Free Software
29  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
30  */
31 
32 #include "xpserver.h"
33 
34 
35 /*
36  * Look if any player's name is exactly 'str',
37  * If not, look if any player's name contains 'str'.
38  * The matching is case insensitive. If there is an
39  * error (no matches or several matches) NULL is returned
40  * and the error code is stored in 'error' if that is not NULL
41  * and a string describing the error is stored in
42  * 'errorstr_p' if that is not NULL.
43  */
Get_player_by_name(const char * str,int * error_p,const char ** errorstr_p)44 player_t *Get_player_by_name(const char *str,
45 			     int *error_p, const char **errorstr_p)
46 {
47     int i, id;
48     player_t *found_pl = NULL, *pl;
49     size_t len;
50 
51     if (str == NULL || (len = strlen(str)) == 0)
52 	goto match_none;
53 
54     /* Get player by id */
55     id = atoi(str);
56     if (id > 0) {
57 	found_pl = Player_by_id(id);
58 	if (!found_pl)
59 	    goto match_none;
60 	return found_pl;
61     }
62 
63     /* Look for an exact match on player nickname. */
64     for (i = 0; i < NumPlayers; i++) {
65 	pl = Player_by_index(i);
66 	if (!strcasecmp(pl->name, str))
67 	    return pl;
68     }
69 
70     /* Look if 'str' matches beginning of only one nick. */
71     for (i = 0; i < NumPlayers; i++) {
72 	pl = Player_by_index(i);
73 
74 	if (!strncasecmp(pl->name, str, len)) {
75 	    if (found_pl)
76 		goto match_several;
77 	    found_pl = pl;
78 	    continue;
79 	}
80     }
81     if (found_pl)
82 	return found_pl;
83 
84     /*
85      * Check what players' name 'str' is a substring of (case insensitively).
86      */
87     for (i = 0; i < NumPlayers; i++) {
88 	int j;
89 
90 	pl = Player_by_index(i);
91 
92 	for (j = 0; j < 1 + (int)strlen(pl->name) - (int)len; j++) {
93 	    if (!strncasecmp(pl->name + j, str, len)) {
94 		if (found_pl)
95 		    goto match_several;
96 		found_pl = pl;
97 		break;
98 	    }
99 	}
100     }
101     if (found_pl)
102 	return found_pl;
103 
104  match_none:
105     if (error_p != NULL)
106 	*error_p = -1;
107     if (errorstr_p != NULL)
108 	*errorstr_p = "Name does not match any player.";
109     return NULL;
110 
111  match_several:
112     if (error_p != NULL)
113 	*error_p = -2;
114     if (errorstr_p != NULL)
115 	*errorstr_p = "Name matches several players.";
116     return NULL;
117 }
118 
119 
Send_info_about_player(player_t * pl)120 void Send_info_about_player(player_t *pl)
121 {
122     int i;
123 
124     for (i = 0; i < spectatorStart + NumSpectators; i++) {
125 	player_t *pl_i;
126 
127 	if (i == NumPlayers) {
128 	    i = spectatorStart - 1;
129 	    continue;
130 	}
131 	pl_i = Player_by_index(i);
132 	if (pl_i->conn != NULL) {
133 	    Send_team(pl_i->conn, pl->id, pl->team);
134 	    /*if we do either, we do both... but is either necessary?*/
135 	    updateScores = true;
136 	    pl->update_score = true;
137 	    if (pl->home_base != NULL)
138 		Send_base(pl_i->conn, pl->id, pl->home_base->ind);
139 	}
140     }
141 }
142 
143 
Set_swapper_state(player_t * pl)144 void Set_swapper_state(player_t *pl)
145 {
146     if (BIT(pl->have, HAS_BALL))
147 	Detach_ball(pl, NULL);
148 
149     if (BIT(world->rules->mode, LIMITED_LIVES)) {
150 	int i;
151 
152 	for (i = 0; i < NumPlayers; i++) {
153 	    player_t *pl_i = Player_by_index(i);
154 
155 	    if (!Players_are_teammates(pl, pl_i) &&
156 		!Player_is_paused(pl_i)) {
157 		/* put team swapping player waiting mode. */
158 		/* kps - there used to be a if (pl->mychar == ' ') here,
159 		 * not sure what that was good for.
160 		 */
161 		Player_set_state(pl, PL_STATE_WAITING);
162 		Player_self_destruct(pl, false);
163 		pl->pause_count = 0;
164 		pl->recovery_count = 0;
165 		break;
166 	    }
167 	}
168     }
169 }
170 
171 
172 #define CMD_RESULT_SUCCESS		0
173 #define CMD_RESULT_ERROR		(-1)
174 #define CMD_RESULT_NOT_OPERATOR		(-2)
175 #define CMD_RESULT_NO_NAME		(-3)
176 
177 
178 static int Cmd_addr(char *arg, player_t *pl, bool oper, char *msg, size_t size);
179 static int Cmd_advance(char *arg, player_t *pl, bool oper, char *msg, size_t size);
180 static int Cmd_ally(char *arg, player_t *pl, bool oper, char *msg, size_t size);
181 static int Cmd_get(char *arg, player_t *pl, bool oper, char *msg, size_t size);
182 static int Cmd_help(char *arg, player_t *pl, bool oper, char *msg, size_t size);
183 static int Cmd_kick(char *arg, player_t *pl, bool oper, char *msg, size_t size);
184 static int Cmd_lock(char *arg, player_t *pl, bool oper, char *msg, size_t size);
185 static int Cmd_maxturnsps(char *arg, player_t *pl, bool oper, char *msg, size_t size);
186 static int Cmd_mute(char *arg, player_t *pl, bool oper, char *msg, size_t size);
187 static int Cmd_op(char *arg, player_t *pl, bool oper, char *msg, size_t size);
188 static int Cmd_password(char *arg, player_t *pl, bool oper, char *msg, size_t size);
189 static int Cmd_pause(char *arg, player_t *pl, bool oper, char *msg, size_t size);
190 static int Cmd_plinfo(char *arg, player_t *pl, bool oper, char *msg, size_t size);
191 static int Cmd_queue(char *arg, player_t *pl, bool oper, char *msg, size_t size);
192 static int Cmd_reset(char *arg, player_t *pl, bool oper, char *msg, size_t size);
193 static int Cmd_set(char *arg, player_t *pl, bool oper, char *msg, size_t size);
194 static int Cmd_shutdown(char *arg, player_t *pl, bool oper, char *msg, size_t size);
195 static int Cmd_stats(char *arg, player_t *pl, bool oper, char *msg, size_t size);
196 static int Cmd_team(char *arg, player_t *pl, bool oper, char *msg, size_t size);
197 static int Cmd_version(char *arg, player_t *pl, bool oper, char *msg, size_t size);
198 
199 
200 typedef struct {
201     const char *name;
202     const char *abbrev;
203     const char *help;
204     bool oper_only;
205     int (*cmd)(char *arg, player_t *pl, bool oper, char *msg, size_t size);
206 } Command_info;
207 
208 
209 /*
210  * A list of all the commands sorted alphabetically.
211  */
212 static Command_info commands[] = {
213     {
214 	"addr",
215 	"addr",
216 	"/addr <player name or ID number>. Show IP-address of player.  "
217 	"(operator)",
218 	true,
219 	Cmd_addr
220     },
221     {
222 	"advance",
223 	"ad",
224 	"/advance <name of player in the queue>. "
225 	"Move the player to the front of the queue.  (operator)",
226 	true,
227 	Cmd_advance
228     },
229     {
230 	"ally",
231 	"al",
232 	"/ally {invite|cancel|refuse|accept|leave|list} [<player name>]. "
233 	"Manages alliances and invitations for them.",
234 	false,
235 	Cmd_ally
236     },
237     {
238 	"get",
239 	"g",
240 	"/get <option>.  Gets a server option.",
241 	false,
242 	Cmd_get
243     },
244     {
245 	"help",
246 	"h",
247 	"Print command list.  /help <command> gives more info.",
248 	false,
249 	Cmd_help
250     },
251     {
252 	"kick",
253 	"k",
254 	"/kick <player name or ID number>.  Remove a player from game.  "
255 	"(operator)",
256 	true,
257 	Cmd_kick
258     },
259     {
260 	"lock",
261 	"l",
262 	"Just /lock tells lock status.  /lock 1 locks, /lock 0 unlocks.  "
263 	"(operator)",
264 	false,      /* checked in the function */
265 	Cmd_lock
266     },
267     {
268 	"maxturnsps",
269 	"maxturns",
270 	"/maxturnsps <number> set max amount of turns per second.  "
271 	"(EXPERIMENTAL FEATURE)",
272 	false,
273 	Cmd_maxturnsps
274     },
275     {
276 	"mute",
277 	"m",
278 	"Just /mute 1 mutes, /mute 0 unmutes paused players WITHOUT BASE /mute <name> toggles muting of player.  "
279 	"(operator)",
280 	false,      /* checked in the function */
281 	Cmd_mute
282     },
283     {
284 	"op",
285 	"o",
286 	"/op <command> [player name or ID number]. Operator commands.  "
287 	"(operator)",
288 	true,
289 	Cmd_op
290     },
291     {
292 	"password",
293 	"pas",
294 	"/password <string>.  If string matches -password option "
295 	"gives operator status.",
296 	false,
297 	Cmd_password
298     },
299     {
300 	"pause",
301 	"pau",
302 	"/pause <player name or ID number>.  Pauses player.  (operator)",
303 	true,
304 	Cmd_pause
305     },
306     {
307 	"plinfo",
308 	"pl",
309 	"/plinfo <player name or ID number>.  Show misc. player info.",
310 	false,
311 	Cmd_plinfo
312     },
313     {
314 	"queue",
315 	"q",
316 	"/queue.  Show the names of players waiting to enter.",
317 	false,
318 	Cmd_queue
319     },
320     {
321 	"reset",
322 	"r",
323 	"Just /reset re-starts the round. "
324 	"'/reset all' also resets all scores to 0.  (operator)",
325 	true,
326 	Cmd_reset
327     },
328     {
329 	"set",
330 	"s",
331 	"/set <option> <value>.  Sets a server option.  (operator)",
332 	true,
333 	Cmd_set
334     },
335     {
336 	"shutdown",
337 	"shutd",
338 	"/shutdown <delay in seconds> [reason].  Shutdown server. "
339 	"Use delay <= 0 to cancel. (operator)  "
340 	"Just /shutdown to query.",
341 	false,      /* checked in the function */
342 	Cmd_shutdown
343     },
344     {
345 	"stats",
346 	"st",
347 	"/stats <player name or ID number>.  Show player ranking info.",
348 	false,
349 	Cmd_stats
350     },
351     {
352 	"team",
353 	"t",
354 	"/team <team number> [name] swaps you to given team. "
355 	"Can be used with full teams too.",
356 	false,
357 	Cmd_team
358     },
359     {
360 	"version",
361 	"v",
362 	"Print server version.",
363 	false,
364 	Cmd_version
365     },
366 };
367 
368 
369 /*
370  * cmd parameter has no leading slash.
371  */
Handle_player_command(player_t * pl,char * cmd)372 void Handle_player_command(player_t *pl, char *cmd)
373 {
374     int i, result;
375     char *args, msg[MSG_LEN];
376 
377     if (!*cmd) {
378 	strlcpy(msg,
379 		"No command given.  Type /help for help.  [*Server reply*]",
380 		sizeof(msg));
381 	Set_player_message(pl, msg);
382 	return;
383     }
384 
385     args = strchr(cmd + 1, ' ');
386     if (!args)
387 	/* point to end of string. */
388 	args = cmd + strlen(cmd);
389     else {
390 	/* zero terminate cmd and advance 1 byte. */
391 	*args++ = '\0';
392 	while (isspace(*args))
393 	    args++;
394     }
395 
396     for (i = 0; i < NELEM(commands); i++) {
397 	size_t len1 = strlen(commands[i].abbrev);
398 	size_t len2 = strlen(cmd);
399 
400 	if (!strncasecmp(cmd, commands[i].name, MAX(len1, len2)))
401 	    break;
402     }
403 
404     if (i == NELEM(commands)) {
405 	snprintf(msg, sizeof(msg),
406 		 "Unknown command '%s'.  [*Server reply*]", cmd);
407 	Set_player_message(pl, msg);
408 	return;
409     }
410 #if 0 /* kps - recording related stuff too obscure */
411     else if (!pl->isoperator && (commands[i].operOnly || rplayback && !playback && commands[i].number != PASSWORD_CMD)) {
412 	i = NO_CMD;
413 	sprintf(msg, "You need operator status to use this command.");
414     }
415 #endif
416 
417     msg[0] = '\0';
418     result = (*commands[i].cmd)(args, pl, pl->isoperator, msg, sizeof(msg));
419     switch (result) {
420     case CMD_RESULT_SUCCESS:
421 	break;
422 
423     case CMD_RESULT_ERROR:
424 	if (msg[0] == '\0')
425 	    strlcpy(msg, "Error.", sizeof(msg));
426 	break;
427 
428     case CMD_RESULT_NOT_OPERATOR:
429 	if (msg[0] == '\0')
430 	    strlcpy(msg,
431 		    "You need operator status to use this command.",
432 		    sizeof(msg));
433 	break;
434 
435     case CMD_RESULT_NO_NAME:
436 	if (msg[0] == '\0')
437 	    strlcpy(msg,
438 		    "You must give a player name as an argument.",
439 		    sizeof(msg));
440 	break;
441 
442     default:
443 	strlcpy(msg, "Bug.", sizeof(msg));
444 	break;
445     }
446 
447     if (msg[0]) {
448 	strlcat(msg, " [*Server reply*]", sizeof(msg));
449 	Set_player_message(pl, msg);
450     }
451 }
452 
453 
454 
Cmd_addr(char * arg,player_t * pl,bool oper,char * msg,size_t size)455 static int Cmd_addr(char *arg, player_t *pl, bool oper, char *msg, size_t size)
456 {
457     player_t *pl2 = NULL;
458     const char *errorstr;
459 
460     UNUSED_PARAM(pl);
461 
462     if (!oper)
463 	return CMD_RESULT_NOT_OPERATOR;
464 
465     if (!arg || !*arg)
466 	return CMD_RESULT_NO_NAME;
467 
468     pl2 = Get_player_by_name(arg, NULL, &errorstr);
469     if (pl2) {
470 	const char *addr = Player_get_addr(pl2);
471 
472 	if (addr == NULL)
473 	    snprintf(msg, size, "Unable to get address for %s.", pl2->name);
474 	else
475 	    snprintf(msg, size, "%s plays from: %s.", pl2->name, addr);
476     } else {
477 	strlcpy(msg, errorstr, size);
478 	return CMD_RESULT_ERROR;
479     }
480 
481     return CMD_RESULT_SUCCESS;
482 }
483 
484 
485 /*
486  * The queue system from the original server is not replicated
487  * during playback. Therefore interactions with it in the
488  * recording can cause problems (at least different message
489  * lengths in acks from client). It would be possible to work
490  * around this, but not implemented now. Currently queue and advance
491  * commands are disabled during recording.
492  */
Cmd_advance(char * arg,player_t * pl,bool oper,char * msg,size_t size)493 static int Cmd_advance(char *arg, player_t *pl, bool oper,
494 		       char *msg, size_t size)
495 {
496     int result;
497 
498     UNUSED_PARAM(pl);
499 
500     if (!oper)
501 	return CMD_RESULT_NOT_OPERATOR;
502 
503     if (record || playback) {
504 	strlcpy(msg, "Command currently disabled during recording for "
505 	       "technical reasons.", size);
506 	return CMD_RESULT_ERROR;
507     }
508 
509     if (!arg || !*arg)
510 	return CMD_RESULT_NO_NAME;
511 
512     result = Queue_advance_player(arg, msg, size);
513 
514     if (result < 0)
515 	return CMD_RESULT_ERROR;
516 
517     return CMD_RESULT_SUCCESS;
518 }
519 
520 
Cmd_ally(char * arg,player_t * pl,bool oper,char * msg,size_t size)521 static int Cmd_ally(char *arg, player_t *pl, bool oper, char *msg, size_t size)
522 {
523     char *command;
524     int result = CMD_RESULT_SUCCESS;
525     static const char usage[] =
526 	"Usage: /ally {invite|cancel|refuse|accept|leave|list} "
527 	"[<player name>]";
528     static const char *cmds[] = {
529 	"invite",
530 	"cancel",
531 	"refuse",
532 	"accept",
533 	"leave",
534 	"list",
535     };
536     enum AllyCmds {
537 	AllyInvite  = 0,
538 	AllyCancel  = 1,
539 	AllyRefuse  = 2,
540 	AllyAccept  = 3,
541 	AllyLeave   = 4,
542 	AllyList    = 5,
543 	NumAllyCmds = 6
544     };
545     int			i, cmd;
546 
547     UNUSED_PARAM(pl); UNUSED_PARAM(oper);
548 
549     if (!BIT(world->rules->mode, ALLIANCES)) {
550 	strlcpy(msg, "Alliances are not allowed.", size);
551 	result = CMD_RESULT_ERROR;
552     }
553     else if (!arg || !(command = strtok(arg, " \t"))) {
554 	strlcpy(msg, usage, size);
555 	result = CMD_RESULT_ERROR;
556     }
557     else {
558 	if ((arg = strtok(NULL, "")) != NULL) {
559 	    while (*arg == ' ')
560 		++arg;
561 	}
562 	cmd = -1;
563 	for (i = 0; i < NumAllyCmds; i++) {
564 	    if (!strncasecmp(cmds[i], command, strlen(command)))
565 		cmd = (cmd == -1) ? i : (-2);
566 	}
567 	if (cmd < 0) {
568 	    strlcpy(msg, usage, size);
569 	    result = CMD_RESULT_ERROR;
570 	}
571 	else if (arg) {
572 	    /* a name is specified */
573 	    const char *errorstr;
574 	    player_t *pl2 = Get_player_by_name(arg, NULL, &errorstr);
575 
576 	    if (pl2) {
577 		if (cmd == AllyInvite)
578 		    Invite_player(pl, pl2);
579 		else if (cmd == AllyRefuse)
580 		    Refuse_alliance(pl, pl2);
581 		else if (cmd == AllyAccept)
582 		    Accept_alliance(pl, pl2);
583 		else {
584 		    strlcpy(msg, usage, size);
585 		    result = CMD_RESULT_ERROR;
586 		}
587 	    } else {
588 		strlcpy(msg, errorstr, size);
589 		result = CMD_RESULT_ERROR;
590 	    }
591 	} else {
592 	    /* no player name is specified */
593 	    if (cmd == AllyCancel)
594 		Cancel_invitation(pl);
595 	    else if (cmd == AllyRefuse)
596 		Refuse_all_alliances(pl);
597 	    else if (cmd == AllyAccept)
598 		Accept_all_alliances(pl);
599 	    else if (cmd == AllyLeave)
600 		Leave_alliance(pl);
601 	    else if (cmd == AllyList)
602 		Alliance_player_list(pl);
603 	    else {
604 		strlcpy(msg, usage, size);
605 		result = CMD_RESULT_ERROR;
606 	    }
607 	}
608     }
609     return result;
610 }
611 
Cmd_get(char * arg,player_t * pl,bool oper,char * msg,size_t size)612 static int Cmd_get(char *arg, player_t *pl, bool oper, char *msg, size_t size)
613 {
614     char value[MAX_CHARS];
615     char *valcpy, *name;
616     int i, retval = CMD_RESULT_ERROR;
617 
618     UNUSED_PARAM(pl); UNUSED_PARAM(oper);
619 
620     if (!arg || !*arg) {
621 	strlcpy(msg, "Usage: /get option.", size);
622 	return CMD_RESULT_ERROR;
623     }
624 
625     valcpy = xp_safe_strdup(arg);
626     name = strtok(valcpy, " \t\r\n");
627     i = Get_option_value(name, value, sizeof(value));
628 
629     switch (i) {
630     case 1:
631 	snprintf(msg, size, "The value of %s is %s.", name, value);
632 	retval = CMD_RESULT_SUCCESS;
633 	break;
634     case -2:
635 	snprintf(msg, size, "No server option named %s.", name);
636 	break;
637     case -3:
638 	snprintf(msg, size, "Cannot show the value of this option.");
639 	break;
640     case -4:
641 	snprintf(msg, size, "No value has been set for option %s.", name);
642 	break;
643     default:
644 	strlcpy(msg, "Generic error.", size);
645 	break;
646     }
647 
648     XFREE(valcpy);
649 
650     return retval;
651 }
652 
653 
Cmd_help(char * arg,player_t * pl,bool oper,char * msg,size_t size)654 static int Cmd_help(char *arg, player_t *pl, bool oper, char *msg, size_t size)
655 {
656     int			i;
657 
658     UNUSED_PARAM(pl); UNUSED_PARAM(oper);
659 
660     if (!*arg) {
661 	strlcpy(msg, "Commands: ", size);
662 	for (i = 0; i < NELEM(commands); i++) {
663 	    if (!commands[i].oper_only) {
664 		strlcat(msg, commands[i].name, size);
665 		strlcat(msg, " ", size);
666 	    }
667 	}
668 
669 	if (pl->isoperator) {
670 	    strlcat(msg, " [*Server reply*]", size);
671 	    Set_player_message(pl, msg);
672 
673 	    strlcpy(msg, "Operator commands: ", size);
674 	    for (i = 0; i < NELEM(commands); i++) {
675 		if (commands[i].oper_only) {
676 		    strlcat(msg, commands[i].name, size);
677 		    strlcat(msg, " ", size);
678 		}
679 	    }
680 	}
681     }
682     else {
683 	for (i = 0; i < NELEM(commands); i++) {
684 	    size_t len1 = strlen(commands[i].abbrev);
685 	    size_t len2 = strlen(arg);
686 
687 	    if (!strncasecmp(arg, commands[i].name, MAX(len1, len2)))
688 		break;
689 	}
690 	if (i == NELEM(commands))
691 	    snprintf(msg, size, "No help for nonexistent command '%s'.", arg);
692 	else
693 	    strlcpy(msg, commands[i].help, size);
694     }
695 
696     return CMD_RESULT_SUCCESS;
697 }
698 
699 
Cmd_kick(char * arg,player_t * pl,bool oper,char * msg,size_t size)700 static int Cmd_kick(char *arg, player_t *pl, bool oper, char *msg, size_t size)
701 {
702     player_t *kicked_pl;
703     const char *errorstr;
704 
705     if (!oper)
706 	return CMD_RESULT_NOT_OPERATOR;
707 
708     if (!arg || !*arg)
709 	return CMD_RESULT_NO_NAME;
710 
711     kicked_pl = Get_player_by_name(arg, NULL, &errorstr);
712     if (kicked_pl) {
713 	snprintf(msg, size, "%s kicked %s out! [*Server notice*]",
714 		 pl->name, kicked_pl->name);
715 	if (kicked_pl->conn == NULL)
716 	    Delete_player(kicked_pl);
717 	else
718 	    Destroy_connection(kicked_pl->conn, "kicked out");
719 	Set_message(msg);
720 	strlcpy(msg, "", size);
721 	return CMD_RESULT_SUCCESS;
722     }
723     strlcpy(msg, errorstr, size);
724 
725     return CMD_RESULT_ERROR;
726 }
727 
728 
Cmd_lock(char * arg,player_t * pl,bool oper,char * msg,size_t size)729 static int Cmd_lock(char *arg, player_t *pl, bool oper, char *msg, size_t size)
730 {
731     bool new_lock;
732 
733     if (!arg || !*arg) {
734 	snprintf(msg, size, "The game is currently %s.",
735 		 game_lock ? "locked" : "unlocked");
736 	return CMD_RESULT_SUCCESS;
737     }
738 
739     if (!oper)
740 	return CMD_RESULT_NOT_OPERATOR;
741 
742     if (!strcmp(arg, "1"))
743 	new_lock = true;
744     else if (!strcmp(arg, "0"))
745 	new_lock = false;
746     else {
747 	snprintf(msg, size, "Invalid argument '%s'.  Specify either 0 or 1.",
748 		 arg);
749 	return CMD_RESULT_ERROR;
750     }
751 
752     if (new_lock == game_lock)
753 	snprintf(msg, size, "Game is already %s.",
754 		 game_lock ? "locked" : "unlocked");
755     else {
756 	game_lock = new_lock;
757 	snprintf(msg, size, " < The game has been %s by %s! >",
758 		 game_lock ? "locked" : "unlocked",
759 		 pl->name);
760 	Set_message(msg);
761 	strlcpy(msg, "", size);
762     }
763 
764     return CMD_RESULT_SUCCESS;
765 }
766 
767 /* temporary hack */
Cmd_maxturnsps(char * arg,player_t * pl,bool oper,char * msg,size_t size)768 static int Cmd_maxturnsps(char *arg, player_t *pl, bool oper, char *msg, size_t size)
769 {
770     int new_maxturnsps;
771 
772     UNUSED_PARAM(oper);
773 
774     if (!arg || !*arg) {
775 	snprintf(msg, size, "Your current maxturnsps is %d.", pl->maxturnsps);
776 	return CMD_RESULT_SUCCESS;
777     }
778 
779     new_maxturnsps = atoi(arg);
780     if (new_maxturnsps <= 0) {
781 	snprintf(msg, size, "Value of maxturnsps must be > 0.");
782 	return CMD_RESULT_ERROR;
783     }
784 
785     pl->maxturnsps = new_maxturnsps;
786     Set_player_message_f(pl, "Max number of turns/s is now %d. "
787 			 "[*Server reply*]", pl->maxturnsps);
788 
789     return CMD_RESULT_SUCCESS;
790 }
791 
Cmd_mute(char * arg,player_t * pl,bool oper,char * msg,size_t size)792 static int Cmd_mute(char *arg, player_t *pl, bool oper, char *msg, size_t size)
793 {
794     int new_mute;
795     player_t *mutee;
796     const char *errorstr;
797 
798     if (!arg || !*arg) {
799 	snprintf(msg, size, "Baseless paused players are currently %s.",
800 		 mute_baseless ? "muted" : "unmuted");
801 	return CMD_RESULT_SUCCESS;
802     }
803 
804     if (!oper)
805 	return CMD_RESULT_NOT_OPERATOR;
806 
807     if (!strcmp(arg, "1"))
808 	new_mute = true;
809     else if (!strcmp(arg, "0"))
810 	new_mute = false;
811     else if ((mutee = Get_player_by_name(arg, NULL, &errorstr)) != NULL) {
812     	mutee->muted = mutee->muted ? false : true;
813 	snprintf(msg, size, "Player %s is now %s.",
814 		 mutee->name, mutee->muted ? "muted" : "unmuted");
815     	return CMD_RESULT_SUCCESS;
816     } else {
817     	strlcpy(msg, errorstr, size);
818 	return CMD_RESULT_ERROR;
819     }
820 
821     if (new_mute == mute_baseless)
822 	snprintf(msg, size, "Already %s.",
823 		 mute_baseless ? "muted" : "unmuted");
824     else {
825 	mute_baseless = new_mute;
826 	snprintf(msg, size, " < Baseless paused players have been %s by %s! >",
827 		 mute_baseless ? "muted" : "unmuted", pl->name);
828 	Set_message(msg);
829 	strlcpy(msg, "", size);
830     }
831 
832     return CMD_RESULT_SUCCESS;
833 }
834 
835 /* kps - this one is a bit obscure, maybe clean it up a bit ? */
Cmd_op(char * arg,player_t * pl,bool oper,char * msg,size_t size)836 static int Cmd_op(char *arg, player_t *pl, bool oper, char *msg, size_t size)
837 {
838     player_t *issuer = pl;
839     char *origarg = arg;
840     char *name;
841     int cmd, priv;
842 
843     if (!oper)
844 	return CMD_RESULT_NOT_OPERATOR;
845 
846     if (!arg || (*arg != '+' && *arg != '-')) {
847 	snprintf(msg, size, "Usage: /op {+|-}[nlo]+ <player name>");
848 	return CMD_RESULT_ERROR;
849     }
850 
851     name = strpbrk(arg, " \t");
852     if (name) {
853 	const char *errorstr;
854 
855 	*name++ = '\0';
856 	while (isspace(*name))
857 	    name++;
858 
859 	pl = Get_player_by_name(name, NULL, &errorstr);
860 	if (!pl) {
861 	    strlcpy(msg, errorstr, size);
862 	    return CMD_RESULT_ERROR;
863 	}
864     }
865 
866     priv = 0;
867     cmd = *arg;
868     arg++;
869     while (*arg) {
870 	switch (*arg) {
871 	case 'n':
872 	    priv |= PRIV_NOAUTOKICK;
873 	    break;
874 	case 'l':
875 	    priv |= PRIV_AUTOKICKLAST;
876 	    break;
877 	case 'o':
878 	    if (cmd == '+')
879 		pl->isoperator = true;
880 	    else
881 		pl->isoperator = false;
882 	    break;
883 	default:
884 	    snprintf(msg, size, "Invalid operator command '%c'.", *arg);
885 	    return CMD_RESULT_ERROR;
886 	}
887 	arg++;
888     }
889     if (cmd == '+')
890 	pl->privs |= priv;
891     else
892 	pl->privs &= ~priv;
893 
894     if (pl != issuer) {
895 	snprintf(msg, size, "%s executed '/op %s' on you. [*Server notice*]",
896 		 issuer->name, origarg);
897 	Set_player_message(pl, msg);
898     }
899     snprintf(msg, size, "Executed '/op %s' on %s", origarg, pl->name);
900 
901     return CMD_RESULT_SUCCESS;
902 }
903 
904 
Cmd_password(char * arg,player_t * pl,bool oper,char * msg,size_t size)905 static int Cmd_password(char *arg, player_t *pl, bool oper,
906 			 char *msg, size_t size)
907 {
908     UNUSED_PARAM(oper);
909 
910     if (!options.password || !arg || strcmp(arg, options.password)) {
911 	strlcpy(msg, "Wrong.", size);
912 	if (pl->isoperator && pl->rectype != 2) {
913 	    NumOperators--;
914 	    pl->isoperator = false;
915 	    strlcat(msg, "  You lost operator status.", size);
916 	}
917     }
918     else {
919 	if (!pl->isoperator && pl->rectype != 2) {
920 	    NumOperators++;
921 	    pl->isoperator = true;
922 	    pl->privs |= PRIV_AUTOKICKLAST;
923 	}
924 	strlcpy(msg, "You got operator status.", size);
925     }
926     return CMD_RESULT_SUCCESS;
927 }
928 
929 
Cmd_pause(char * arg,player_t * pl,bool oper,char * msg,size_t size)930 static int Cmd_pause(char *arg, player_t *pl, bool oper, char *msg, size_t size)
931 {
932     const char *errorstr;
933     player_t *pl2;
934 
935     if (!oper)
936 	return CMD_RESULT_NOT_OPERATOR;
937 
938     if (!arg || !*arg)
939 	return CMD_RESULT_NO_NAME;
940 
941     pl2 = Get_player_by_name(arg, NULL, &errorstr);
942     if (!pl2) {
943 	strlcpy(msg, errorstr, size);
944 	return CMD_RESULT_ERROR;
945     }
946 
947     if (pl2->conn != NULL) {
948 	if (Player_is_alive(pl2))
949 	    Kill_player(pl2, false);
950 	Pause_player(pl2, true);
951 	snprintf(msg, size, "%s was paused by %s. [*Server notice*]",
952 		 pl2->name, pl->name);
953 	Set_message(msg);
954 	strlcpy(msg, "", size);
955     } else {
956 	snprintf(msg, size, "Robots and tanks can't be paused.");
957 	return CMD_RESULT_ERROR;
958     }
959 
960     return CMD_RESULT_SUCCESS;
961 }
962 
Cmd_plinfo(char * arg,player_t * pl,bool oper,char * msg,size_t size)963 static int Cmd_plinfo(char *arg, player_t *pl, bool oper, char *msg, size_t size)
964 {
965     const char *errorstr;
966     player_t *pl2;
967 
968     UNUSED_PARAM(pl); UNUSED_PARAM(oper);
969 
970     if (!arg || !*arg)
971 	return CMD_RESULT_NO_NAME;
972 
973     pl2 = Get_player_by_name(arg, NULL, &errorstr);
974     if (!pl2) {
975 	strlcpy(msg, errorstr, size);
976 	return CMD_RESULT_ERROR;
977     }
978 
979     if (!Player_is_human(pl2)) {
980 	snprintf(msg, size, "Robots and tanks don't have player info.");
981 	return CMD_RESULT_ERROR;
982     }
983 
984     snprintf(msg, size,
985 	     "%-15s Ver: 0x%x MaxFPS: %d Turnspeed: %.2f Turnres: %.2f "
986 	     "RTT: %i ms RTT_dev: %i ms",
987 	     pl2->name, pl2->version,
988 	     pl2->player_fps, pl2->turnspeed, pl2->turnresistance,
989 	     (int)((pl2->conn->rtt_smoothed >> 3) * timePerFrame * 1000),
990 	     (int)((pl2->conn->rtt_dev >> 2) * timePerFrame * 1000));
991 
992     return CMD_RESULT_SUCCESS;
993 }
994 
Cmd_queue(char * arg,player_t * pl,bool oper,char * msg,size_t size)995 static int Cmd_queue(char *arg, player_t *pl, bool oper, char *msg, size_t size)
996 {
997     int result;
998 
999     UNUSED_PARAM(arg); UNUSED_PARAM(pl); UNUSED_PARAM(oper);
1000 
1001     if (record || playback) {
1002 	strlcpy(msg, "Command currently disabled during recording for "
1003 		"technical reasons.", size);
1004 	return CMD_RESULT_ERROR;
1005     }
1006 
1007     result = Queue_show_list(msg, size);
1008 
1009     if (result < 0)
1010 	return CMD_RESULT_ERROR;
1011 
1012     return CMD_RESULT_SUCCESS;
1013 }
1014 
1015 
Cmd_reset(char * arg,player_t * pl,bool oper,char * msg,size_t size)1016 static int Cmd_reset(char *arg, player_t *pl, bool oper, char *msg, size_t size)
1017 {
1018     int i;
1019 
1020     if (!oper)
1021 	return CMD_RESULT_NOT_OPERATOR;
1022 
1023         for (i = NumObjs - 1; i >= 0; i--) {
1024         object_t *obj = Obj[i];
1025         obj->life = 0;
1026         Delete_shot(i);
1027 
1028     }
1029 
1030     if (arg && !strcasecmp(arg, "all")) {
1031 	for (i = NumPlayers - 1; i >= 0; i--) {
1032 	    player_t *pl_i = Player_by_index(i);
1033 
1034 	    if (!Player_is_paused(pl_i))
1035 		Rank_set_score(pl_i, 0.0);
1036 	}
1037 	Reset_all_players();
1038 	if (options.gameDuration == -1)
1039 	    options.gameDuration = 0;
1040 	roundsPlayed = 0;
1041 
1042 	snprintf(msg, size, " < Total reset by %s! >", pl->name);
1043 	Set_message(msg);
1044 	strlcpy(msg, "", size);
1045 
1046 	teamcup_game_over();
1047 	teamcup_game_start();
1048     }
1049     else {
1050 	Reset_all_players();
1051 	if (options.gameDuration == -1)
1052 	    options.gameDuration = 0;
1053 
1054 	snprintf(msg, size, " < Round reset by %s! >", pl->name);
1055 	Set_message(msg);
1056 	strlcpy(msg, "", size);
1057     }
1058 
1059     return CMD_RESULT_SUCCESS;
1060 }
1061 
1062 
Cmd_stats(char * arg,player_t * pl,bool oper,char * msg,size_t size)1063 static int Cmd_stats(char *arg, player_t *pl, bool oper, char *msg, size_t size)
1064 {
1065     UNUSED_PARAM(pl); UNUSED_PARAM(oper);
1066 
1067     if (!arg || !*arg)
1068 	return CMD_RESULT_NO_NAME;
1069 
1070     if (!Rank_get_stats(arg, msg, size)) {
1071 	snprintf(msg, size, "Player \"%s\" doesn't have ranking stats.", arg);
1072 	return CMD_RESULT_ERROR;
1073     }
1074 
1075     return CMD_RESULT_SUCCESS;
1076 }
1077 
1078 
Cmd_team(char * arg,player_t * pl,bool oper,char * msg,size_t size)1079 static int Cmd_team(char *arg, player_t *pl, bool oper, char *msg, size_t size)
1080 {
1081     int i, team, swap_allowed;
1082     char *arg2;
1083 
1084     UNUSED_PARAM(oper);
1085 
1086     /*
1087      * Assume nothing will be said or done.
1088      */
1089     msg[0] = '\0';
1090     swap_allowed = false;
1091     team = pl->team;
1092 
1093     if (!BIT(world->rules->mode, TEAM_PLAY))
1094 	snprintf(msg, size, "No team play going on.");
1095     else if (pl->team >= MAX_TEAMS)
1096 	snprintf(msg, size, "You do not currently have a team.");
1097     else if (!arg)
1098 	snprintf(msg, size, "No team specified.");
1099     else if (!isdigit(*arg))
1100 	snprintf(msg, size, "Invalid team specification.");
1101     else {
1102 	team = strtoul(arg, &arg2, 0);
1103 	if (arg2 && *arg2) {
1104 	    const char *errorstr;
1105 
1106 	    if (!pl->isoperator) {
1107 		snprintf(msg, size,
1108 			"You need operator status to swap other players.");
1109 		return CMD_RESULT_NOT_OPERATOR;
1110 	    }
1111 	    while (isspace(*arg2))
1112 		arg2++;
1113 	    pl = Get_player_by_name(arg2, NULL, &errorstr);
1114 	    if (!pl) {
1115 		strlcpy(msg, errorstr, size);
1116 		return CMD_RESULT_ERROR;
1117 	    }
1118 	}
1119 
1120 	for (i = 0; i < MAX_TEAMS ; i++) {
1121 	    team_t *t = Team_by_index(i);
1122 
1123 	    /* Can't queue to two teams at once. */
1124 	    if (t->SwapperId == pl->id)
1125 		t->SwapperId = NO_ID;
1126 	}
1127 
1128 	if (game_lock && pl->home_base == NULL)
1129 	    snprintf(msg, size, "Playing teams are locked.");
1130 	else if (team < 0 || team >= MAX_TEAMS)
1131 	    snprintf(msg, size, "Team %d is not a valid team.", team);
1132 	else if (team == pl->team && pl->home_base != NULL)
1133 	    snprintf(msg, size, "You already are on team %d.", team);
1134 	else if (world->teams[team].NumBases == 0)
1135 	    snprintf(msg, size,
1136 		     "There are no bases for team %d on this map.", team);
1137 	else if (options.reserveRobotTeam && team == options.robotTeam)
1138 	    snprintf(msg, size,
1139 		     "You cannot join the robot team on this server.");
1140 	else if (pl->rectype == 2)
1141 	    snprintf(msg, size, "Spectators cannot change team.");
1142 	else
1143 	    swap_allowed = true;
1144     }
1145     if (!swap_allowed)
1146 	return CMD_RESULT_ERROR;
1147 
1148     if (world->teams[team].NumBases > world->teams[team].NumMembers) {
1149 	snprintf(msg, size, "%s has swapped to team %d.", pl->name, team);
1150 	Set_message(msg);
1151 	if (pl->home_base)
1152 	    world->teams[pl->team].NumMembers--;
1153 	pl->team = team;
1154 	world->teams[pl->team].NumMembers++;
1155 	Set_swapper_state(pl);
1156 	if (pl->home_base == NULL) {
1157 	    Pick_startpos(pl);
1158 	    Pause_player(pl, false);
1159 	}
1160 	else
1161 	    Pick_startpos(pl);
1162 	Send_info_about_player(pl);
1163 	strlcpy(msg, "", size);
1164 
1165 	return CMD_RESULT_SUCCESS;
1166     }
1167 
1168     i = world->teams[pl->team].SwapperId;
1169     while (i != -1 && pl->home_base != NULL) {
1170 	if ((i = Player_by_id(i)->team) != team)
1171 	    i = world->teams[i].SwapperId;
1172 	else {
1173 	    /* Found a cycle, now change the teams */
1174 	    base_t *xbase = pl->home_base, *xbase2;
1175 	    int xteam = pl->team, xteam2;
1176 	    player_t *pl2 = pl;
1177 
1178 	    do {
1179 		pl2 = Player_by_id(world->teams[xteam].SwapperId);
1180 		world->teams[xteam].SwapperId = -1;
1181 		xbase2 = pl2->home_base;
1182 		xteam2 = pl2->team;
1183 		pl2->team = xteam;
1184 		pl2->home_base = xbase;
1185 		Set_swapper_state(pl2);
1186 		Send_info_about_player(pl2);
1187 		/* This can send a huge amount of data if several
1188 		 * players swap. Unfortunately all player data, even
1189 		 * shipshape, has to be resent to change the team of
1190 		 * a player. This should probably be changed somehow
1191 		 * to prevent disturbing other players. */
1192 		xbase = xbase2;
1193 		xteam = xteam2;
1194 	    } while (xteam != team);
1195 	    xteam = pl->team;
1196 	    pl->team = team;
1197 	    pl->home_base = xbase;
1198 	    Set_swapper_state(pl);
1199 	    Send_info_about_player(pl);
1200 	    snprintf(msg, size, "Some players swapped teams.");
1201 	    Set_message(msg);
1202 	    strlcpy(msg, "", size);
1203 	    return CMD_RESULT_SUCCESS;
1204 	}
1205     }
1206     /* Swap a paused player away from the full team */
1207     for (i = NumPlayers - 1; i >= 0; i--) {
1208 	player_t *pl2 = Player_by_index(i);
1209 
1210 	if (pl2->conn != NULL && Player_is_paused(pl2)
1211 	    && (pl2->team == team) && pl2->home_base != NULL) {
1212 	    base_t *temp;
1213 
1214 	    pl2->team = pl->team;
1215 	    pl->team = team;
1216 	    temp = pl2->home_base;
1217 	    pl2->home_base = pl->home_base;
1218 	    pl->home_base = temp;
1219 	    Set_swapper_state(pl2);
1220 	    Set_swapper_state(pl);
1221 	    Send_info_about_player(pl2);
1222 	    Send_info_about_player(pl);
1223 	    snprintf(msg, size, "%s has swapped with paused %s.",
1224 		     pl->name, pl2->name);
1225 	    Set_message(msg);
1226 	    strlcpy(msg, "", size);
1227 	    return CMD_RESULT_SUCCESS;
1228 	}
1229     }
1230     snprintf(msg, size, "You are queued for swap to team %d.", team);
1231     world->teams[team].SwapperId = pl->id;
1232     return CMD_RESULT_SUCCESS;
1233 }
1234 
Cmd_set(char * arg,player_t * pl,bool oper,char * msg,size_t size)1235 static int Cmd_set(char *arg, player_t *pl, bool oper, char *msg, size_t size)
1236 {
1237     int i;
1238     char *option, *value;
1239 
1240     if (!oper)
1241 	return CMD_RESULT_NOT_OPERATOR;
1242 
1243     /*
1244      * Second argument of second strtok is " " instead of ""
1245      * which allows setting string options to values that contain spaces.
1246      */
1247     if (!arg
1248 	|| !(option = strtok(arg, " "))
1249 	|| !(value = strtok(NULL, ""))) {
1250 
1251 	snprintf(msg, size, "Usage: /set option value.");
1252 	return CMD_RESULT_ERROR;
1253     }
1254 
1255     i = Tune_option(option, value);
1256     if (i == 1) {
1257 	if (!strcasecmp(option, "password"))
1258 	    snprintf(msg, size, "Operation successful.");
1259 	else {
1260 	    char val[MAX_CHARS];
1261 
1262 	    Get_option_value(option, val, sizeof(val));
1263 	    snprintf(msg, size, " < Option %s set to %s by %s. >",
1264 		     option, val, pl->name);
1265 	    Set_message(msg);
1266 	    strlcpy(msg, "", size);
1267 
1268 	    return CMD_RESULT_SUCCESS;
1269 	}
1270     }
1271     else if (i == 0)
1272 	snprintf(msg, size, "Invalid value.");
1273     else if (i == -1)
1274 	snprintf(msg, size, "This option cannot be changed at runtime.");
1275     else if (i == -2)
1276 	snprintf(msg, size, "No option named '%s'.", option);
1277     else
1278 	snprintf(msg, size, "Error.");
1279 
1280     return CMD_RESULT_ERROR;
1281 }
1282 
Cmd_shutdown(char * arg,player_t * pl,bool oper,char * msg,size_t size)1283 static int Cmd_shutdown(char *arg, player_t *pl, bool oper,
1284 			char *msg, size_t size)
1285 {
1286     int delay;
1287     const char *delaystr, *reason;
1288     bool is_shutting_down = (ShutdownServer == -1 ? false : true);
1289 
1290     if (!arg || !*arg) {
1291 	if (is_shutting_down)
1292 	    snprintf(msg, size, "Shutting down in %d seconds: \"%s\"",
1293 		     ShutdownServer / FPS, ShutdownReason);
1294 	else
1295 	    strlcpy(msg, "The server is not shutting down.", size);
1296 	return CMD_RESULT_SUCCESS;
1297     }
1298 
1299     if (!oper)
1300 	return CMD_RESULT_NOT_OPERATOR;
1301 
1302     if (!arg
1303 	|| !(delaystr = strtok(arg, " "))) {
1304 
1305 	snprintf(msg, size, "Usage: /shutdown <delay> [reason].");
1306 	return CMD_RESULT_ERROR;
1307     }
1308 
1309     reason = strtok(NULL, "");
1310     if (reason == NULL)
1311 	reason = "";
1312 
1313     delay = atoi(delaystr);
1314 
1315     if (is_shutting_down || delay > 0) {
1316 	Server_shutdown(pl->name, delay, reason);
1317 	return CMD_RESULT_SUCCESS;
1318     }
1319     else
1320 	/* no need to cancel if not shutting down */
1321 	snprintf(msg, size, "The server is not shutting down.");
1322 
1323     return CMD_RESULT_ERROR;
1324 }
1325 
Cmd_version(char * arg,player_t * pl,bool oper,char * msg,size_t size)1326 static int Cmd_version(char *arg, player_t *pl, bool oper,
1327 		       char *msg, size_t size)
1328 {
1329     UNUSED_PARAM(arg); UNUSED_PARAM(pl); UNUSED_PARAM(oper);
1330     snprintf(msg, size, "%s version %s.", PACKAGE_NAME, VERSION);
1331     return CMD_RESULT_SUCCESS;
1332 }
1333