1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22
23 #include "server.h"
24
25 /*
26 ===============================================================================
27
28 OPERATOR CONSOLE ONLY COMMANDS
29
30 These commands can only be entered from stdin or by a remote operator datagram
31 ===============================================================================
32 */
33
34
35 /*
36 ==================
37 SV_GetPlayerByHandle
38
39 Returns the player with player id or name from Cmd_Argv(1)
40 ==================
41 */
SV_GetPlayerByHandle(void)42 static client_t *SV_GetPlayerByHandle( void ) {
43 client_t *cl;
44 int i;
45 char *s;
46 char cleanName[64];
47
48 // make sure server is running
49 if ( !com_sv_running->integer ) {
50 return NULL;
51 }
52
53 if ( Cmd_Argc() < 2 ) {
54 Com_Printf( "No player specified.\n" );
55 return NULL;
56 }
57
58 s = Cmd_Argv(1);
59
60 // Check whether this is a numeric player handle
61 for(i = 0; s[i] >= '0' && s[i] <= '9'; i++);
62
63 if(!s[i])
64 {
65 int plid = atoi(s);
66
67 // Check for numeric playerid match
68 if(plid >= 0 && plid < sv_maxclients->integer)
69 {
70 cl = &svs.clients[plid];
71
72 if(cl->state)
73 return cl;
74 }
75 }
76
77 // check for a name match
78 for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
79 if ( !cl->state ) {
80 continue;
81 }
82 if ( !Q_stricmp( cl->name, s ) ) {
83 return cl;
84 }
85
86 Q_strncpyz( cleanName, cl->name, sizeof(cleanName) );
87 Q_CleanStr( cleanName );
88 if ( !Q_stricmp( cleanName, s ) ) {
89 return cl;
90 }
91 }
92
93 Com_Printf( "Player %s is not on the server\n", s );
94
95 return NULL;
96 }
97
98 /*
99 ==================
100 SV_GetPlayerByNum
101
102 Returns the player with idnum from Cmd_Argv(1)
103 ==================
104 */
SV_GetPlayerByNum(void)105 static client_t *SV_GetPlayerByNum( void ) {
106 client_t *cl;
107 int i;
108 int idnum;
109 char *s;
110
111 // make sure server is running
112 if ( !com_sv_running->integer ) {
113 return NULL;
114 }
115
116 if ( Cmd_Argc() < 2 ) {
117 Com_Printf( "No player specified.\n" );
118 return NULL;
119 }
120
121 s = Cmd_Argv(1);
122
123 for (i = 0; s[i]; i++) {
124 if (s[i] < '0' || s[i] > '9') {
125 Com_Printf( "Bad slot number: %s\n", s);
126 return NULL;
127 }
128 }
129 idnum = atoi( s );
130 if ( idnum < 0 || idnum >= sv_maxclients->integer ) {
131 Com_Printf( "Bad client slot: %i\n", idnum );
132 return NULL;
133 }
134
135 cl = &svs.clients[idnum];
136 if ( !cl->state ) {
137 Com_Printf( "Client %i is not active\n", idnum );
138 return NULL;
139 }
140 return cl;
141 }
142
143 //=========================================================
144
145
146 /*
147 ==================
148 SV_Map_f
149
150 Restart the server on a different map
151 ==================
152 */
SV_Map_f(void)153 static void SV_Map_f( void ) {
154 char *cmd;
155 char *map;
156 qboolean killBots, cheat;
157 char expanded[MAX_QPATH];
158 char mapname[MAX_QPATH];
159
160 map = Cmd_Argv(1);
161 if ( !map ) {
162 return;
163 }
164
165 // make sure the level exists before trying to change, so that
166 // a typo at the server console won't end the game
167 Com_sprintf (expanded, sizeof(expanded), "maps/%s.bsp", map);
168 if ( FS_ReadFile (expanded, NULL) == -1 ) {
169 Com_Printf ("Can't find map %s\n", expanded);
170 return;
171 }
172
173 // force latched values to get set
174 Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH );
175
176 //Notice that we have done a restart
177 sv_dorestart->integer = 0;
178
179 cmd = Cmd_Argv(0);
180 if( Q_stricmpn( cmd, "sp", 2 ) == 0 ) {
181 Cvar_SetValue( "g_gametype", GT_SINGLE_PLAYER );
182 Cvar_SetValue( "g_doWarmup", 0 );
183 // may not set sv_maxclients directly, always set latched
184 Cvar_SetLatched( "sv_maxclients", "8" );
185 cmd += 2;
186 if (!Q_stricmp( cmd, "devmap" ) ) {
187 cheat = qtrue;
188 } else {
189 cheat = qfalse;
190 }
191 killBots = qtrue;
192 }
193 else {
194 if ( !Q_stricmp( cmd, "devmap" ) ) {
195 cheat = qtrue;
196 killBots = qtrue;
197 } else {
198 cheat = qfalse;
199 killBots = qfalse;
200 }
201 if( sv_gametype->integer == GT_SINGLE_PLAYER ) {
202 Cvar_SetValue( "g_gametype", GT_FFA );
203 }
204 }
205
206 // save the map name here cause on a map restart we reload the q3config.cfg
207 // and thus nuke the arguments of the map command
208 Q_strncpyz(mapname, map, sizeof(mapname));
209
210 // start up the map
211 SV_SpawnServer( mapname, killBots );
212
213 // set the cheat value
214 // if the level was started with "map <levelname>", then
215 // cheats will not be allowed. If started with "devmap <levelname>"
216 // then cheats will be allowed
217 if ( cheat ) {
218 Cvar_Set( "sv_cheats", "1" );
219 } else {
220 Cvar_Set( "sv_cheats", "0" );
221 }
222 }
223
224 /*
225 ================
226 SV_MapRestart_f
227
228 Completely restarts a level, but doesn't send a new gamestate to the clients.
229 This allows fair starts with variable load times.
230 ================
231 */
SV_MapRestart_f(void)232 static void SV_MapRestart_f( void ) {
233 int i;
234 client_t *client;
235 char *denied;
236 qboolean isBot;
237 int delay;
238
239 // make sure we aren't restarting twice in the same frame
240 if ( com_frameTime == sv.serverId ) {
241 return;
242 }
243
244 // make sure server is running
245 if ( !com_sv_running->integer ) {
246 Com_Printf( "Server is not running.\n" );
247 return;
248 }
249
250 if ( sv.restartTime ) {
251 return;
252 }
253
254 if (Cmd_Argc() > 1 ) {
255 delay = atoi( Cmd_Argv(1) );
256 }
257 else {
258 delay = 5;
259 }
260 if( delay && !Cvar_VariableValue("g_doWarmup") ) {
261 sv.restartTime = sv.time + delay * 1000;
262 SV_SetConfigstring( CS_WARMUP, va("%i", sv.restartTime) );
263 return;
264 }
265
266 // check for changes in variables that can't just be restarted
267 // check for maxclients change
268 if ( sv_maxclients->modified || sv_gametype->modified || sv_dorestart->integer ) {
269 char mapname[MAX_QPATH];
270
271 sv_dorestart->integer = 0;
272 Com_Printf( "variable change -- restarting.\n" );
273 // restart the map the slow way
274 Q_strncpyz( mapname, Cvar_VariableString( "mapname" ), sizeof( mapname ) );
275
276 SV_SpawnServer( mapname, qfalse );
277 return;
278 }
279
280 // toggle the server bit so clients can detect that a
281 // map_restart has happened
282 svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT;
283
284 // generate a new serverid
285 // TTimo - don't update restartedserverId there, otherwise we won't deal correctly with multiple map_restart
286 sv.serverId = com_frameTime;
287 Cvar_Set( "sv_serverid", va("%i", sv.serverId ) );
288
289 // if a map_restart occurs while a client is changing maps, we need
290 // to give them the correct time so that when they finish loading
291 // they don't violate the backwards time check in cl_cgame.c
292 for (i=0 ; i<sv_maxclients->integer ; i++) {
293 if (svs.clients[i].state == CS_PRIMED) {
294 svs.clients[i].oldServerTime = sv.restartTime;
295 }
296 }
297
298 // reset all the vm data in place without changing memory allocation
299 // note that we do NOT set sv.state = SS_LOADING, so configstrings that
300 // had been changed from their default values will generate broadcast updates
301 sv.state = SS_LOADING;
302 sv.restarting = qtrue;
303
304 SV_RestartGameProgs();
305
306 // run a few frames to allow everything to settle
307 for (i = 0; i < 3; i++)
308 {
309 VM_Call (gvm, GAME_RUN_FRAME, sv.time);
310 sv.time += 100;
311 svs.time += 100;
312 }
313
314 sv.state = SS_GAME;
315 sv.restarting = qfalse;
316
317 // connect and begin all the clients
318 for (i=0 ; i<sv_maxclients->integer ; i++) {
319 client = &svs.clients[i];
320
321 // send the new gamestate to all connected clients
322 if ( client->state < CS_CONNECTED) {
323 continue;
324 }
325
326 if ( client->netchan.remoteAddress.type == NA_BOT ) {
327 isBot = qtrue;
328 } else {
329 isBot = qfalse;
330 }
331
332 // add the map_restart command
333 SV_AddServerCommand( client, "map_restart\n" );
334
335 // connect the client again, without the firstTime flag
336 denied = VM_ExplicitArgPtr( gvm, VM_Call( gvm, GAME_CLIENT_CONNECT, i, qfalse, isBot ) );
337 if ( denied ) {
338 // this generally shouldn't happen, because the client
339 // was connected before the level change
340 SV_DropClient( client, denied );
341 Com_Printf( "SV_MapRestart_f(%d): dropped client %i - denied!\n", delay, i );
342 continue;
343 }
344
345 if(client->state == CS_ACTIVE)
346 SV_ClientEnterWorld(client, &client->lastUsercmd);
347 else
348 {
349 // If we don't reset client->lastUsercmd and are restarting during map load,
350 // the client will hang because we'll use the last Usercmd from the previous map,
351 // which is wrong obviously.
352 SV_ClientEnterWorld(client, NULL);
353 }
354 }
355
356 // run another frame to allow things to look at all the players
357 VM_Call (gvm, GAME_RUN_FRAME, sv.time);
358 sv.time += 100;
359 svs.time += 100;
360 }
361
362 //===============================================================
363
364 /*
365 ==================
366 SV_Kick_f
367
368 Kick a user off of the server FIXME: move to game
369 ==================
370 */
SV_Kick_f(void)371 static void SV_Kick_f( void ) {
372 client_t *cl;
373 int i;
374
375 // make sure server is running
376 if ( !com_sv_running->integer ) {
377 Com_Printf( "Server is not running.\n" );
378 return;
379 }
380
381 if ( Cmd_Argc() != 2 ) {
382 Com_Printf ("Usage: kick <player name>\nkick all = kick everyone\nkick allbots = kick all bots\n");
383 return;
384 }
385
386 cl = SV_GetPlayerByHandle();
387 if ( !cl ) {
388 if ( !Q_stricmp(Cmd_Argv(1), "all") ) {
389 for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
390 if ( !cl->state ) {
391 continue;
392 }
393 if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
394 continue;
395 }
396 SV_DropClient( cl, "was kicked" );
397 cl->lastPacketTime = svs.time; // in case there is a funny zombie
398 }
399 }
400 else if ( !Q_stricmp(Cmd_Argv(1), "allbots") ) {
401 for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
402 if ( !cl->state ) {
403 continue;
404 }
405 if( cl->netchan.remoteAddress.type != NA_BOT ) {
406 continue;
407 }
408 SV_DropClient( cl, "was kicked" );
409 cl->lastPacketTime = svs.time; // in case there is a funny zombie
410 }
411 }
412 return;
413 }
414 if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
415 SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n");
416 return;
417 }
418
419 SV_DropClient( cl, "was kicked" );
420 cl->lastPacketTime = svs.time; // in case there is a funny zombie
421 }
422
423 #ifndef STANDALONE
424 // these functions require the auth server which of course is not available anymore for stand-alone games.
425
426 /*
427 ==================
428 SV_Ban_f
429
430 Ban a user from being able to play on this server through the auth
431 server
432 ==================
433 */
SV_Ban_f(void)434 static void SV_Ban_f( void ) {
435 client_t *cl;
436
437 // make sure server is running
438 if ( !com_sv_running->integer ) {
439 Com_Printf( "Server is not running.\n" );
440 return;
441 }
442
443 if ( Cmd_Argc() != 2 ) {
444 Com_Printf ("Usage: banUser <player name>\n");
445 return;
446 }
447
448 cl = SV_GetPlayerByHandle();
449
450 if (!cl) {
451 return;
452 }
453
454 if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
455 SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n");
456 return;
457 }
458
459 // look up the authorize server's IP
460 if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) {
461 Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME );
462 if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress, NA_IP ) ) {
463 Com_Printf( "Couldn't resolve address\n" );
464 return;
465 }
466 svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE );
467 Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME,
468 svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1],
469 svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3],
470 BigShort( svs.authorizeAddress.port ) );
471 }
472
473 // otherwise send their ip to the authorize server
474 if ( svs.authorizeAddress.type != NA_BAD ) {
475 NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress,
476 "banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1],
477 cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] );
478 Com_Printf("%s was banned from coming back\n", cl->name);
479 }
480 }
481
482 /*
483 ==================
484 SV_BanNum_f
485
486 Ban a user from being able to play on this server through the auth
487 server
488 ==================
489 */
SV_BanNum_f(void)490 static void SV_BanNum_f( void ) {
491 client_t *cl;
492
493 // make sure server is running
494 if ( !com_sv_running->integer ) {
495 Com_Printf( "Server is not running.\n" );
496 return;
497 }
498
499 if ( Cmd_Argc() != 2 ) {
500 Com_Printf ("Usage: banClient <client number>\n");
501 return;
502 }
503
504 cl = SV_GetPlayerByNum();
505 if ( !cl ) {
506 return;
507 }
508 if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
509 SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n");
510 return;
511 }
512
513 // look up the authorize server's IP
514 if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) {
515 Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME );
516 if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress, NA_IP ) ) {
517 Com_Printf( "Couldn't resolve address\n" );
518 return;
519 }
520 svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE );
521 Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME,
522 svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1],
523 svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3],
524 BigShort( svs.authorizeAddress.port ) );
525 }
526
527 // otherwise send their ip to the authorize server
528 if ( svs.authorizeAddress.type != NA_BAD ) {
529 NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress,
530 "banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1],
531 cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] );
532 Com_Printf("%s was banned from coming back\n", cl->name);
533 }
534 }
535 #endif
536
537 /*
538 ==================
539 SV_RehashBans_f
540
541 Load saved bans from file.
542 ==================
543 */
SV_RehashBans_f(void)544 static void SV_RehashBans_f(void)
545 {
546 int index, filelen;
547 fileHandle_t readfrom;
548 char *textbuf, *curpos, *maskpos, *newlinepos, *endpos;
549 char filepath[MAX_QPATH];
550
551 serverBansCount = 0;
552
553 if(!sv_banFile->string || !*sv_banFile->string)
554 return;
555
556 Com_sprintf(filepath, sizeof(filepath), "%s/%s", FS_GetCurrentGameDir(), sv_banFile->string);
557
558 if((filelen = FS_SV_FOpenFileRead(filepath, &readfrom)) >= 0)
559 {
560 if(filelen < 2)
561 {
562 // Don't bother if file is too short.
563 FS_FCloseFile(readfrom);
564 return;
565 }
566
567 curpos = textbuf = Z_Malloc(filelen);
568
569 filelen = FS_Read(textbuf, filelen, readfrom);
570 FS_FCloseFile(readfrom);
571
572 endpos = textbuf + filelen;
573
574 for(index = 0; index < SERVER_MAXBANS && curpos + 2 < endpos; index++)
575 {
576 // find the end of the address string
577 for(maskpos = curpos + 2; maskpos < endpos && *maskpos != ' '; maskpos++);
578
579 if(maskpos + 1 >= endpos)
580 break;
581
582 *maskpos = '\0';
583 maskpos++;
584
585 // find the end of the subnet specifier
586 for(newlinepos = maskpos; newlinepos < endpos && *newlinepos != '\n'; newlinepos++);
587
588 if(newlinepos >= endpos)
589 break;
590
591 *newlinepos = '\0';
592
593 if(NET_StringToAdr(curpos + 2, &serverBans[index].ip, NA_UNSPEC))
594 {
595 serverBans[index].isexception = (curpos[0] != '0');
596 serverBans[index].subnet = atoi(maskpos);
597
598 if(serverBans[index].ip.type == NA_IP &&
599 (serverBans[index].subnet < 1 || serverBans[index].subnet > 32))
600 {
601 serverBans[index].subnet = 32;
602 }
603 else if(serverBans[index].ip.type == NA_IP6 &&
604 (serverBans[index].subnet < 1 || serverBans[index].subnet > 128))
605 {
606 serverBans[index].subnet = 128;
607 }
608 }
609
610 curpos = newlinepos + 1;
611 }
612
613 serverBansCount = index;
614
615 Z_Free(textbuf);
616 }
617 }
618
619 /*
620 ==================
621 SV_WriteBans_f
622
623 Save bans to file.
624 ==================
625 */
SV_WriteBans(void)626 static void SV_WriteBans(void)
627 {
628 int index;
629 fileHandle_t writeto;
630 char filepath[MAX_QPATH];
631
632 if(!sv_banFile->string || !*sv_banFile->string)
633 return;
634
635 Com_sprintf(filepath, sizeof(filepath), "%s/%s", FS_GetCurrentGameDir(), sv_banFile->string);
636
637 if((writeto = FS_SV_FOpenFileWrite(filepath)))
638 {
639 char writebuf[128];
640 serverBan_t *curban;
641
642 for(index = 0; index < serverBansCount; index++)
643 {
644 curban = &serverBans[index];
645
646 Com_sprintf(writebuf, sizeof(writebuf), "%d %s %d\n",
647 curban->isexception, NET_AdrToString(curban->ip), curban->subnet);
648 FS_Write(writebuf, strlen(writebuf), writeto);
649 }
650
651 FS_FCloseFile(writeto);
652 }
653 }
654
655 /*
656 ==================
657 SV_DelBanEntryFromList
658
659 Remove a ban or an exception from the list.
660 ==================
661 */
662
SV_DelBanEntryFromList(int index)663 static qboolean SV_DelBanEntryFromList(int index)
664 {
665 if(index == serverBansCount - 1)
666 serverBansCount--;
667 else if(index < ARRAY_LEN(serverBans) - 1)
668 {
669 memmove(serverBans + index, serverBans + index + 1, (serverBansCount - index - 1) * sizeof(*serverBans));
670 serverBansCount--;
671 }
672 else
673 return qtrue;
674
675 return qfalse;
676 }
677
678 /*
679 ==================
680 SV_ParseCIDRNotation
681
682 Parse a CIDR notation type string and return a netadr_t and suffix by reference
683 ==================
684 */
685
SV_ParseCIDRNotation(netadr_t * dest,int * mask,char * adrstr)686 static qboolean SV_ParseCIDRNotation(netadr_t *dest, int *mask, char *adrstr)
687 {
688 char *suffix;
689
690 suffix = strchr(adrstr, '/');
691 if(suffix)
692 {
693 *suffix = '\0';
694 suffix++;
695 }
696
697 if(!NET_StringToAdr(adrstr, dest, NA_UNSPEC))
698 return qtrue;
699
700 if(suffix)
701 {
702 *mask = atoi(suffix);
703
704 if(dest->type == NA_IP)
705 {
706 if(*mask < 1 || *mask > 32)
707 *mask = 32;
708 }
709 else
710 {
711 if(*mask < 1 || *mask > 128)
712 *mask = 128;
713 }
714 }
715 else if(dest->type == NA_IP)
716 *mask = 32;
717 else
718 *mask = 128;
719
720 return qfalse;
721 }
722
723 /*
724 ==================
725 SV_AddBanToList
726
727 Ban a user from being able to play on this server based on his ip address.
728 ==================
729 */
730
SV_AddBanToList(qboolean isexception)731 static void SV_AddBanToList(qboolean isexception)
732 {
733 char *banstring;
734 char addy2[NET_ADDRSTRMAXLEN];
735 netadr_t ip;
736 int index, argc, mask;
737 serverBan_t *curban;
738
739 argc = Cmd_Argc();
740
741 if(argc < 2 || argc > 3)
742 {
743 Com_Printf ("Usage: %s (ip[/subnet] | clientnum [subnet])\n", Cmd_Argv(0));
744 return;
745 }
746
747 if(serverBansCount > ARRAY_LEN(serverBans))
748 {
749 Com_Printf ("Error: Maximum number of bans/exceptions exceeded.\n");
750 return;
751 }
752
753 banstring = Cmd_Argv(1);
754
755 if(strchr(banstring, '.') || strchr(banstring, ':'))
756 {
757 // This is an ip address, not a client num.
758
759 if(SV_ParseCIDRNotation(&ip, &mask, banstring))
760 {
761 Com_Printf("Error: Invalid address %s\n", banstring);
762 return;
763 }
764 }
765 else
766 {
767 client_t *cl;
768
769 // client num.
770 if(!com_sv_running->integer)
771 {
772 Com_Printf("Server is not running.\n");
773 return;
774 }
775
776 cl = SV_GetPlayerByNum();
777
778 if(!cl)
779 {
780 Com_Printf("Error: Playernum %s does not exist.\n", Cmd_Argv(1));
781 return;
782 }
783
784 ip = cl->netchan.remoteAddress;
785
786 if(argc == 3)
787 {
788 mask = atoi(Cmd_Argv(2));
789
790 if(ip.type == NA_IP)
791 {
792 if(mask < 1 || mask > 32)
793 mask = 32;
794 }
795 else
796 {
797 if(mask < 1 || mask > 128)
798 mask = 128;
799 }
800 }
801 else
802 mask = (ip.type == NA_IP6) ? 128 : 32;
803 }
804
805 if(ip.type != NA_IP && ip.type != NA_IP6)
806 {
807 Com_Printf("Error: Can ban players connected via the internet only.\n");
808 return;
809 }
810
811 // first check whether a conflicting ban exists that would supersede the new one.
812 for(index = 0; index < serverBansCount; index++)
813 {
814 curban = &serverBans[index];
815
816 if(curban->subnet <= mask)
817 {
818 if((curban->isexception || !isexception) && NET_CompareBaseAdrMask(curban->ip, ip, curban->subnet))
819 {
820 Q_strncpyz(addy2, NET_AdrToString(ip), sizeof(addy2));
821
822 Com_Printf("Error: %s %s/%d supersedes %s %s/%d\n", curban->isexception ? "Exception" : "Ban",
823 NET_AdrToString(curban->ip), curban->subnet,
824 isexception ? "exception" : "ban", addy2, mask);
825 return;
826 }
827 }
828 if(curban->subnet >= mask)
829 {
830 if(!curban->isexception && isexception && NET_CompareBaseAdrMask(curban->ip, ip, mask))
831 {
832 Q_strncpyz(addy2, NET_AdrToString(curban->ip), sizeof(addy2));
833
834 Com_Printf("Error: %s %s/%d supersedes already existing %s %s/%d\n", isexception ? "Exception" : "Ban",
835 NET_AdrToString(ip), mask,
836 curban->isexception ? "exception" : "ban", addy2, curban->subnet);
837 return;
838 }
839 }
840 }
841
842 // now delete bans that are superseded by the new one
843 index = 0;
844 while(index < serverBansCount)
845 {
846 curban = &serverBans[index];
847
848 if(curban->subnet > mask && (!curban->isexception || isexception) && NET_CompareBaseAdrMask(curban->ip, ip, mask))
849 SV_DelBanEntryFromList(index);
850 else
851 index++;
852 }
853
854 serverBans[serverBansCount].ip = ip;
855 serverBans[serverBansCount].subnet = mask;
856 serverBans[serverBansCount].isexception = isexception;
857
858 serverBansCount++;
859
860 SV_WriteBans();
861
862 Com_Printf("Added %s: %s/%d\n", isexception ? "ban exception" : "ban",
863 NET_AdrToString(ip), mask);
864 }
865
866 /*
867 ==================
868 SV_DelBanFromList
869
870 Remove a ban or an exception from the list.
871 ==================
872 */
873
SV_DelBanFromList(qboolean isexception)874 static void SV_DelBanFromList(qboolean isexception)
875 {
876 int index, count = 0, todel, mask;
877 netadr_t ip;
878 char *banstring;
879
880 if(Cmd_Argc() != 2)
881 {
882 Com_Printf ("Usage: %s (ip[/subnet] | num)\n", Cmd_Argv(0));
883 return;
884 }
885
886 banstring = Cmd_Argv(1);
887
888 if(strchr(banstring, '.') || strchr(banstring, ':'))
889 {
890 serverBan_t *curban;
891
892 if(SV_ParseCIDRNotation(&ip, &mask, banstring))
893 {
894 Com_Printf("Error: Invalid address %s\n", banstring);
895 return;
896 }
897
898 index = 0;
899
900 while(index < serverBansCount)
901 {
902 curban = &serverBans[index];
903
904 if(curban->isexception == isexception &&
905 curban->subnet >= mask &&
906 NET_CompareBaseAdrMask(curban->ip, ip, mask))
907 {
908 Com_Printf("Deleting %s %s/%d\n",
909 isexception ? "exception" : "ban",
910 NET_AdrToString(curban->ip), curban->subnet);
911
912 SV_DelBanEntryFromList(index);
913 }
914 else
915 index++;
916 }
917 }
918 else
919 {
920 todel = atoi(Cmd_Argv(1));
921
922 if(todel < 1 || todel > serverBansCount)
923 {
924 Com_Printf("Error: Invalid ban number given\n");
925 return;
926 }
927
928 for(index = 0; index < serverBansCount; index++)
929 {
930 if(serverBans[index].isexception == isexception)
931 {
932 count++;
933
934 if(count == todel)
935 {
936 Com_Printf("Deleting %s %s/%d\n",
937 isexception ? "exception" : "ban",
938 NET_AdrToString(serverBans[index].ip), serverBans[index].subnet);
939
940 SV_DelBanEntryFromList(index);
941
942 break;
943 }
944 }
945 }
946 }
947
948 SV_WriteBans();
949 }
950
951
952 /*
953 ==================
954 SV_ListBans_f
955
956 List all bans and exceptions on console
957 ==================
958 */
959
SV_ListBans_f(void)960 static void SV_ListBans_f(void)
961 {
962 int index, count;
963 serverBan_t *ban;
964
965 // List all bans
966 for(index = count = 0; index < serverBansCount; index++)
967 {
968 ban = &serverBans[index];
969 if(!ban->isexception)
970 {
971 count++;
972
973 Com_Printf("Ban #%d: %s/%d\n", count,
974 NET_AdrToString(ban->ip), ban->subnet);
975 }
976 }
977 // List all exceptions
978 for(index = count = 0; index < serverBansCount; index++)
979 {
980 ban = &serverBans[index];
981 if(ban->isexception)
982 {
983 count++;
984
985 Com_Printf("Except #%d: %s/%d\n", count,
986 NET_AdrToString(ban->ip), ban->subnet);
987 }
988 }
989 }
990
991 /*
992 ==================
993 SV_FlushBans_f
994
995 Delete all bans and exceptions.
996 ==================
997 */
998
SV_FlushBans_f(void)999 static void SV_FlushBans_f(void)
1000 {
1001 serverBansCount = 0;
1002
1003 // empty the ban file.
1004 SV_WriteBans();
1005
1006 Com_Printf("All bans and exceptions have been deleted.\n");
1007 }
1008
SV_BanAddr_f(void)1009 static void SV_BanAddr_f(void)
1010 {
1011 SV_AddBanToList(qfalse);
1012 }
1013
SV_ExceptAddr_f(void)1014 static void SV_ExceptAddr_f(void)
1015 {
1016 SV_AddBanToList(qtrue);
1017 }
1018
SV_BanDel_f(void)1019 static void SV_BanDel_f(void)
1020 {
1021 SV_DelBanFromList(qfalse);
1022 }
1023
SV_ExceptDel_f(void)1024 static void SV_ExceptDel_f(void)
1025 {
1026 SV_DelBanFromList(qtrue);
1027 }
1028
1029 /*
1030 ==================
1031 SV_KickNum_f
1032
1033 Kick a user off of the server FIXME: move to game
1034 ==================
1035 */
SV_KickNum_f(void)1036 static void SV_KickNum_f( void ) {
1037 client_t *cl;
1038
1039 // make sure server is running
1040 if ( !com_sv_running->integer ) {
1041 Com_Printf( "Server is not running.\n" );
1042 return;
1043 }
1044
1045 if ( Cmd_Argc() != 2 ) {
1046 Com_Printf ("Usage: kicknum <client number>\n");
1047 return;
1048 }
1049
1050 cl = SV_GetPlayerByNum();
1051 if ( !cl ) {
1052 return;
1053 }
1054 if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
1055 SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n");
1056 return;
1057 }
1058
1059 SV_DropClient( cl, "was kicked" );
1060 cl->lastPacketTime = svs.time; // in case there is a funny zombie
1061 }
1062
1063 /*
1064 ================
1065 SV_Status_f
1066 ================
1067 */
SV_Status_f(void)1068 static void SV_Status_f( void ) {
1069 int i, j, l;
1070 client_t *cl;
1071 playerState_t *ps;
1072 const char *s;
1073 int ping;
1074
1075 // make sure server is running
1076 if ( !com_sv_running->integer ) {
1077 Com_Printf( "Server is not running.\n" );
1078 return;
1079 }
1080
1081 Com_Printf ("map: %s\n", sv_mapname->string );
1082
1083 Com_Printf ("num score ping name lastmsg address qport rate\n");
1084 Com_Printf ("--- ----- ---- --------------- ------- --------------------- ----- -----\n");
1085 for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++)
1086 {
1087 if (!cl->state)
1088 continue;
1089 Com_Printf ("%3i ", i);
1090 ps = SV_GameClientNum( i );
1091 Com_Printf ("%5i ", ps->persistant[PERS_SCORE]);
1092
1093 if (cl->state == CS_CONNECTED)
1094 Com_Printf ("CNCT ");
1095 else if (cl->state == CS_ZOMBIE)
1096 Com_Printf ("ZMBI ");
1097 else
1098 {
1099 ping = cl->ping < 9999 ? cl->ping : 9999;
1100 Com_Printf ("%4i ", ping);
1101 }
1102
1103 Com_Printf ("%s", cl->name);
1104
1105 // TTimo adding a ^7 to reset the color
1106 // NOTE: colored names in status breaks the padding (WONTFIX)
1107 Com_Printf ("^7");
1108 l = 14 - strlen(cl->name);
1109 j = 0;
1110
1111 do
1112 {
1113 Com_Printf (" ");
1114 j++;
1115 } while(j < l);
1116
1117 Com_Printf ("%7i ", svs.time - cl->lastPacketTime );
1118
1119 s = NET_AdrToString( cl->netchan.remoteAddress );
1120 Com_Printf ("%s", s);
1121 l = 22 - strlen(s);
1122 j = 0;
1123
1124 do
1125 {
1126 Com_Printf(" ");
1127 j++;
1128 } while(j < l);
1129
1130 Com_Printf ("%5i", cl->netchan.qport);
1131
1132 Com_Printf (" %5i", cl->rate);
1133
1134 Com_Printf ("\n");
1135 }
1136 Com_Printf ("\n");
1137 }
1138
1139 /*
1140 ==================
1141 SV_ConSay_f
1142 ==================
1143 */
SV_ConSay_f(void)1144 static void SV_ConSay_f(void) {
1145 char *p;
1146 char text[1024];
1147
1148 // make sure server is running
1149 if ( !com_sv_running->integer ) {
1150 Com_Printf( "Server is not running.\n" );
1151 return;
1152 }
1153
1154 if ( Cmd_Argc () < 2 ) {
1155 return;
1156 }
1157
1158 strcpy (text, "console: ");
1159 p = Cmd_Args();
1160
1161 if ( *p == '"' ) {
1162 p++;
1163 p[strlen(p)-1] = 0;
1164 }
1165
1166 strcat(text, p);
1167
1168 SV_SendServerCommand(NULL, "chat \"%s\"", text);
1169 }
1170
1171
1172 /*
1173 ==================
1174 SV_Heartbeat_f
1175
1176 Also called by SV_DropClient, SV_DirectConnect, and SV_SpawnServer
1177 ==================
1178 */
SV_Heartbeat_f(void)1179 void SV_Heartbeat_f( void ) {
1180 svs.nextHeartbeatTime = -9999999;
1181 }
1182
1183
1184 /*
1185 ===========
1186 SV_Serverinfo_f
1187
1188 Examine the serverinfo string
1189 ===========
1190 */
SV_Serverinfo_f(void)1191 static void SV_Serverinfo_f( void ) {
1192 Com_Printf ("Server info settings:\n");
1193 Info_Print ( Cvar_InfoString( CVAR_SERVERINFO ) );
1194 }
1195
1196
1197 /*
1198 ===========
1199 SV_Systeminfo_f
1200
1201 Examine or change the serverinfo string
1202 ===========
1203 */
SV_Systeminfo_f(void)1204 static void SV_Systeminfo_f( void ) {
1205 Com_Printf ("System info settings:\n");
1206 Info_Print ( Cvar_InfoString_Big( CVAR_SYSTEMINFO ) );
1207 }
1208
1209
1210 /*
1211 ===========
1212 SV_DumpUser_f
1213
1214 Examine all a users info strings FIXME: move to game
1215 ===========
1216 */
SV_DumpUser_f(void)1217 static void SV_DumpUser_f( void ) {
1218 client_t *cl;
1219
1220 // make sure server is running
1221 if ( !com_sv_running->integer ) {
1222 Com_Printf( "Server is not running.\n" );
1223 return;
1224 }
1225
1226 if ( Cmd_Argc() != 2 ) {
1227 Com_Printf ("Usage: info <userid>\n");
1228 return;
1229 }
1230
1231 cl = SV_GetPlayerByHandle();
1232 if ( !cl ) {
1233 return;
1234 }
1235
1236 Com_Printf( "userinfo\n" );
1237 Com_Printf( "--------\n" );
1238 Info_Print( cl->userinfo );
1239 }
1240
1241
1242 /*
1243 =================
1244 SV_KillServer
1245 =================
1246 */
SV_KillServer_f(void)1247 static void SV_KillServer_f( void ) {
1248 SV_Shutdown( "killserver" );
1249 }
1250
1251 //===========================================================
1252
1253 /*
1254 ==================
1255 SV_CompleteMapName
1256 ==================
1257 */
SV_CompleteMapName(char * args,int argNum)1258 static void SV_CompleteMapName( char *args, int argNum ) {
1259 if( argNum == 2 ) {
1260 Field_CompleteFilename( "maps", "bsp", qtrue, qfalse );
1261 }
1262 }
1263
1264 /*
1265 ==================
1266 SV_AddOperatorCommands
1267 ==================
1268 */
SV_AddOperatorCommands(void)1269 void SV_AddOperatorCommands( void ) {
1270 static qboolean initialized;
1271
1272 if ( initialized ) {
1273 return;
1274 }
1275 initialized = qtrue;
1276
1277 Cmd_AddCommand ("heartbeat", SV_Heartbeat_f);
1278 Cmd_AddCommand ("kick", SV_Kick_f);
1279 #ifndef STANDALONE
1280 if(!com_standalone->integer)
1281 {
1282 Cmd_AddCommand ("banUser", SV_Ban_f);
1283 Cmd_AddCommand ("banClient", SV_BanNum_f);
1284 }
1285 #endif
1286 Cmd_AddCommand ("clientkick", SV_KickNum_f);
1287 Cmd_AddCommand ("status", SV_Status_f);
1288 Cmd_AddCommand ("serverinfo", SV_Serverinfo_f);
1289 Cmd_AddCommand ("systeminfo", SV_Systeminfo_f);
1290 Cmd_AddCommand ("dumpuser", SV_DumpUser_f);
1291 Cmd_AddCommand ("map_restart", SV_MapRestart_f);
1292 Cmd_AddCommand ("sectorlist", SV_SectorList_f);
1293 Cmd_AddCommand ("map", SV_Map_f);
1294 Cmd_SetCommandCompletionFunc( "map", SV_CompleteMapName );
1295 #ifndef PRE_RELEASE_DEMO
1296 Cmd_AddCommand ("devmap", SV_Map_f);
1297 Cmd_SetCommandCompletionFunc( "devmap", SV_CompleteMapName );
1298 Cmd_AddCommand ("spmap", SV_Map_f);
1299 Cmd_SetCommandCompletionFunc( "spmap", SV_CompleteMapName );
1300 Cmd_AddCommand ("spdevmap", SV_Map_f);
1301 Cmd_SetCommandCompletionFunc( "spdevmap", SV_CompleteMapName );
1302 #endif
1303 Cmd_AddCommand ("killserver", SV_KillServer_f);
1304 if( com_dedicated->integer ) {
1305 Cmd_AddCommand ("say", SV_ConSay_f);
1306 }
1307
1308 Cmd_AddCommand("rehashbans", SV_RehashBans_f);
1309 Cmd_AddCommand("listbans", SV_ListBans_f);
1310 Cmd_AddCommand("banaddr", SV_BanAddr_f);
1311 Cmd_AddCommand("exceptaddr", SV_ExceptAddr_f);
1312 Cmd_AddCommand("bandel", SV_BanDel_f);
1313 Cmd_AddCommand("exceptdel", SV_ExceptDel_f);
1314 Cmd_AddCommand("flushbans", SV_FlushBans_f);
1315 }
1316
1317 /*
1318 ==================
1319 SV_RemoveOperatorCommands
1320 ==================
1321 */
SV_RemoveOperatorCommands(void)1322 void SV_RemoveOperatorCommands( void ) {
1323 #if 0
1324 // removing these won't let the server start again
1325 Cmd_RemoveCommand ("heartbeat");
1326 Cmd_RemoveCommand ("kick");
1327 Cmd_RemoveCommand ("banUser");
1328 Cmd_RemoveCommand ("banClient");
1329 Cmd_RemoveCommand ("status");
1330 Cmd_RemoveCommand ("serverinfo");
1331 Cmd_RemoveCommand ("systeminfo");
1332 Cmd_RemoveCommand ("dumpuser");
1333 Cmd_RemoveCommand ("map_restart");
1334 Cmd_RemoveCommand ("sectorlist");
1335 Cmd_RemoveCommand ("say");
1336 #endif
1337 }
1338
1339