diff --git a/README b/README index dc4131f2..affde4ca 100644 --- a/README +++ b/README @@ -143,11 +143,34 @@ New cvars sv_dlURL - the base of the HTTP or FTP site that holds custom pk3 files for your server + net_ip6 - IPv6 address to bind to + net_port6 - port to bind to using the ipv6 address + net_enabled - enable networking, bitmask. Add up + number for option to enable it: + enable ipv4 networking: 1 + enable ipv6 networking: 2 + prioritise ipv6 over ipv4: 4 + disable multicast support: 8 + net_mcast6addr - multicast address to use for scanning for + ipv6 servers on the local network + net_mcastiface - outgoing interface to use for scan + + New commands video [filename] - start video capture (use with demo command) stopvideo - stop video capture + print - print out the contents of a cvar + banaddr - ban an ip address range from joining a game on this + server, valid is either playernum or CIDR + notation address range. + exceptaddr - exempt an ip address range from a ban. + bandel - delete ban + exceptdel - delete exception + listbans - list all currently active bans and exceptions + rehashbans - reload the banlist from serverbans.dat + flushbans - delete all bans ------------------------------------------------------------ Miscellaneous ----- diff --git a/code/client/cl_main.c b/code/client/cl_main.c index c2edd027..3c8625e0 100644 --- a/code/client/cl_main.c +++ b/code/client/cl_main.c @@ -2784,7 +2784,7 @@ void CL_Init( void ) { SCR_Init (); - Cbuf_Execute (); +// Cbuf_Execute (); Cvar_Set( "cl_running", "1" ); diff --git a/code/qcommon/files.c b/code/qcommon/files.c index a5efb7e9..07ecfac4 100644 --- a/code/qcommon/files.c +++ b/code/qcommon/files.c @@ -715,7 +715,8 @@ int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) { if (f) { return FS_filelength(f); } - return 0; + + return -1; } diff --git a/code/qcommon/qcommon.h b/code/qcommon/qcommon.h index 8746f3bd..396042ec 100644 --- a/code/qcommon/qcommon.h +++ b/code/qcommon/qcommon.h @@ -583,6 +583,7 @@ int FS_GetFileList( const char *path, const char *extension, char *listbuf, in int FS_GetModList( char *listbuf, int bufsize ); fileHandle_t FS_FOpenFileWrite( const char *qpath ); +fileHandle_t FS_FOpenFileAppend( const char *filename ); // will properly create any needed paths and deal with seperater character issues int FS_filelength( fileHandle_t f ); diff --git a/code/server/server.h b/code/server/server.h index 6a7612b5..3d03aee5 100644 --- a/code/server/server.h +++ b/code/server/server.h @@ -213,6 +213,18 @@ typedef struct { netadr_t authorizeAddress; // for rcon return messages } serverStatic_t; +#define SERVER_MAXBANS 1024 +#define SERVER_BANFILE "serverbans.dat" +// Structure for managing bans +typedef struct +{ + netadr_t ip; + // For a CIDR-Notation type suffix + int subnet; + + qboolean isexception; +} serverBan_t; + //============================================================================= extern serverStatic_t svs; // persistant server info across maps @@ -249,6 +261,9 @@ extern cvar_t *sv_floodProtect; extern cvar_t *sv_lanForceRate; extern cvar_t *sv_strictAuth; +extern serverBan_t serverBans[SERVER_MAXBANS]; +extern int serverBansCount; + //=========================================================== // diff --git a/code/server/sv_ccmds.c b/code/server/sv_ccmds.c index e2bcd939..ad49334f 100644 --- a/code/server/sv_ccmds.c +++ b/code/server/sv_ccmds.c @@ -511,6 +511,333 @@ static void SV_BanNum_f( void ) { } #endif +/* +================== +SV_RehashBans_f + +Load saved bans from file. +================== +*/ +void SV_RehashBans_f(void) +{ + int index, filelen; + fileHandle_t readfrom; + char *textbuf, *curpos, *maskpos, *newlinepos, *endpos; + char filepath[MAX_QPATH]; + + serverBansCount = 0; + + if(!(curpos = Cvar_VariableString("fs_game")) || !*curpos) + curpos = BASEGAME; + + Com_sprintf(filepath, sizeof(filepath), "%s/%s", curpos, SERVER_BANFILE); + + if((filelen = FS_SV_FOpenFileRead(filepath, &readfrom)) >= 0) + { + if(filelen < 2) + { + // Don't bother if file is too short. + FS_FCloseFile(readfrom); + return; + } + + curpos = textbuf = Z_Malloc(filelen); + + filelen = FS_Read(textbuf, filelen, readfrom); + FS_FCloseFile(readfrom); + + endpos = textbuf + filelen; + + for(index = 0; index < SERVER_MAXBANS && curpos + 2 < endpos; index++) + { + // find the end of the address string + for(maskpos = curpos + 2; maskpos < endpos && *maskpos != ' '; maskpos++); + + if(maskpos + 1 >= endpos) + break; + + *maskpos = '\0'; + maskpos++; + + // find the end of the subnet specifier + for(newlinepos = maskpos; newlinepos < endpos && *newlinepos != '\n'; newlinepos++); + + if(newlinepos >= endpos) + break; + + *newlinepos = '\0'; + + if(NET_StringToAdr(curpos + 2, &serverBans[index].ip, NA_UNSPEC)) + { + serverBans[index].isexception = !(curpos[0] == '0'); + serverBans[index].subnet = atoi(maskpos); + + if(serverBans[index].ip.type == NA_IP && + (serverBans[index].subnet < 0 || serverBans[index].subnet > 32)) + { + serverBans[index].subnet = 32; + } + else if(serverBans[index].ip.type == NA_IP6 && + (serverBans[index].subnet < 0 || serverBans[index].subnet > 128)) + { + serverBans[index].subnet = 128; + } + } + + curpos = newlinepos + 1; + } + + serverBansCount = index; + + Z_Free(textbuf); + } +} + +/* +================== +SV_BanAddr_f + +Ban a user from being able to play on this server based on his ip address. +================== +*/ + +static void SV_AddBanToList(qboolean isexception) +{ + char *banstring, *suffix; + netadr_t ip; + int argc, mask; + fileHandle_t writeto; + + argc = Cmd_Argc(); + + if(argc < 2 || argc > 3) + { + Com_Printf ("Usage: %s (ip[/subnet] | clientnum [subnet])\n", Cmd_Argv(0)); + return; + } + + if(serverBansCount > sizeof(serverBans) / sizeof(*serverBans)) + { + Com_Printf ("Error: Maximum number of bans/exceptions exceeded.\n"); + return; + } + + banstring = Cmd_Argv(1); + + if(strchr(banstring, '.') || strchr(banstring, ':')) + { + // This is an ip address, not a client num. + + // Look for a CIDR-Notation suffix + suffix = strchr(banstring, '/'); + if(suffix) + { + *suffix = '\0'; + suffix++; + } + + if(!NET_StringToAdr(banstring, &ip, NA_UNSPEC)) + { + Com_Printf("Error: Invalid address %s\n", banstring); + return; + } + } + else + { + client_t *cl; + + // client num. + if(!com_sv_running->integer) + { + Com_Printf("Server is not running.\n"); + return; + } + + cl = SV_GetPlayerByNum(); + + if(!cl) + { + Com_Printf("Error: Playernum %s does not exist.\n", Cmd_Argv(1)); + return; + } + + ip = cl->netchan.remoteAddress; + + if(argc == 3) + suffix = Cmd_Argv(2); + else + suffix = NULL; + } + + if(ip.type != NA_IP && ip.type != NA_IP6) + { + Com_Printf("Error: Can ban players connected via the internet only.\n"); + return; + } + + if(suffix) + { + mask = atoi(suffix); + + if(ip.type == NA_IP) + { + if(mask < 0 || mask > 32) + mask = 32; + } + else + { + if(mask < 0 || mask > 128) + mask = 128; + } + } + else if(ip.type == NA_IP) + mask = 32; + else + mask = 128; + + serverBans[serverBansCount].ip = ip; + serverBans[serverBansCount].subnet = mask; + serverBans[serverBansCount].isexception = isexception; + + Com_Printf("Added %s: %s/%d\n", isexception ? "ban exception" : "ban", + NET_AdrToString(ip), mask); + + // Write out the ban information. + if((writeto = FS_FOpenFileAppend(SERVER_BANFILE))) + { + char writebuf[128]; + + Com_sprintf(writebuf, sizeof(writebuf), "%d %s %d\n", isexception, NET_AdrToString(ip), mask); + FS_Write(writebuf, strlen(writebuf), writeto); + FS_FCloseFile(writeto); + } + + serverBansCount++; +} + +static void SV_DelBanFromList(qboolean isexception) +{ + int index, count, todel; + fileHandle_t writeto; + + if(Cmd_Argc() != 2) + { + Com_Printf ("Usage: %s \n", Cmd_Argv(0)); + return; + } + + todel = atoi(Cmd_Argv(1)); + + if(todel < 0 || todel > serverBansCount) + return; + + for(index = count = 0; index < serverBansCount; index++) + { + if(serverBans[index].isexception == isexception) + { + count++; + + if(count == todel) + break; + } + } + + if(index == serverBansCount - 1) + serverBansCount--; + else if(index < sizeof(serverBans) / sizeof(*serverBans) - 1) + { + memmove(serverBans + index, serverBans + index + 1, (serverBansCount - index - 1) * sizeof(*serverBans)); + serverBansCount--; + } + else + { + Com_Printf("Error: No such entry #%d\n", todel); + return; + } + + // Write out the ban information. + if((writeto = FS_FOpenFileWrite(SERVER_BANFILE))) + { + char writebuf[128]; + serverBan_t *curban; + + for(index = 0; index < serverBansCount; index++) + { + curban = &serverBans[index]; + + Com_sprintf(writebuf, sizeof(writebuf), "%d %s %d\n", + curban->isexception, NET_AdrToString(curban->ip), curban->subnet); + FS_Write(writebuf, strlen(writebuf), writeto); + } + + FS_FCloseFile(writeto); + } +} + +static void SV_ListBans_f(void) +{ + int index, count; + serverBan_t *ban; + + // List all bans + for(index = count = 0; index < serverBansCount; index++) + { + ban = &serverBans[index]; + if(!ban->isexception) + { + count++; + + Com_Printf("Ban #%d: %s/%d\n", count, + NET_AdrToString(ban->ip), ban->subnet); + } + } + // List all exceptions + for(index = count = 0; index < serverBansCount; index++) + { + ban = &serverBans[index]; + if(ban->isexception) + { + count++; + + Com_Printf("Except #%d: %s/%d\n", count, + NET_AdrToString(ban->ip), ban->subnet); + } + } +} + +static void SV_FlushBans_f(void) +{ + fileHandle_t blankf; + + serverBansCount = 0; + + // empty the ban file. + blankf = FS_FOpenFileWrite(SERVER_BANFILE); + + if(blankf) + FS_FCloseFile(blankf); +} + +static void SV_BanAddr_f(void) +{ + SV_AddBanToList(qfalse); +} + +static void SV_ExceptAddr_f(void) +{ + SV_AddBanToList(qtrue); +} + +static void SV_BanDel_f(void) +{ + SV_DelBanFromList(qfalse); +} + +static void SV_ExceptDel_f(void) +{ + SV_DelBanFromList(qtrue); +} + /* ================== SV_KickNum_f @@ -763,6 +1090,14 @@ void SV_AddOperatorCommands( void ) { if( com_dedicated->integer ) { Cmd_AddCommand ("say", SV_ConSay_f); } + + Cmd_AddCommand("rehashbans", SV_RehashBans_f); + Cmd_AddCommand("listbans", SV_ListBans_f); + Cmd_AddCommand("banaddr", SV_BanAddr_f); + Cmd_AddCommand("exceptaddr", SV_ExceptAddr_f); + Cmd_AddCommand("bandel", SV_BanDel_f); + Cmd_AddCommand("exceptdel", SV_ExceptDel_f); + Cmd_AddCommand("flushbans", SV_FlushBans_f); } /* diff --git a/code/server/sv_client.c b/code/server/sv_client.c index 8230036a..eced3d92 100644 --- a/code/server/sv_client.c +++ b/code/server/sv_client.c @@ -235,6 +235,86 @@ A "connect" OOB command has been received ================== */ +qboolean SV_IsBanned(netadr_t *from, qboolean isexception) +{ + int index, addrlen, curbyte, netmask, cmpmask; + serverBan_t *curban; + byte *addrfrom, *addrban; + qboolean differed; + + if(from->type == NA_IP) + addrlen = sizeof(from->ip); + else if(from->type == NA_IP6) + addrlen = sizeof(from->ip6); + else + return qfalse; + + if(!isexception) + { + // If this is a query for a ban, first check whether the client is excepted + if(SV_IsBanned(from, qtrue)) + return qfalse; + } + + for(index = 0; index < serverBansCount; index++) + { + curban = &serverBans[index]; + + if(curban->isexception == isexception && from->type == curban->ip.type) + { + if(from->type == NA_IP) + { + addrfrom = from->ip; + addrban = curban->ip.ip; + } + else + { + addrfrom = from->ip6; + addrban = curban->ip.ip6; + } + + differed = qfalse; + curbyte = 0; + + for(netmask = curban->subnet; netmask > 7; netmask -= 8) + { + if(addrfrom[curbyte] != addrban[curbyte]) + { + differed = qtrue; + break; + } + + curbyte++; + } + + if(differed) + continue; + + if(netmask) + { + cmpmask = (1 << netmask) - 1; + cmpmask <<= 8 - netmask; + + if((addrfrom[curbyte] & cmpmask) == (addrban[curbyte] & cmpmask)) + return qtrue; + } + else + return qtrue; + + } + } + + return qfalse; +} + +/* +================== +SV_DirectConnect + +A "connect" OOB command has been received +================== +*/ + void SV_DirectConnect( netadr_t from ) { char userinfo[MAX_INFO_STRING]; int i; @@ -252,6 +332,13 @@ void SV_DirectConnect( netadr_t from ) { char *ip; Com_DPrintf ("SVC_DirectConnect ()\n"); + + // Check whether this client is banned. + if(SV_IsBanned(&from, qfalse)) + { + NET_OutOfBandPrint(NS_SERVER, from, "print\nYou are banned from this server.\n"); + return; + } Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) ); @@ -821,11 +908,13 @@ void SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) } } + cl->download = 0; + // We open the file here if ( !(sv_allowDownload->integer & DLF_ENABLE) || (sv_allowDownload->integer & DLF_NO_UDP) || idPack || unreferenced || - ( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) <= 0 ) { + ( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) < 0 ) { // cannot auto-download file if(unreferenced) { @@ -868,6 +957,10 @@ void SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) MSG_WriteString( msg, errorMessage ); *cl->downloadName = 0; + + if(cl->download) + FS_FCloseFile(cl->download); + return; } diff --git a/code/server/sv_init.c b/code/server/sv_init.c index 3b9cf3d1..aca76917 100644 --- a/code/server/sv_init.c +++ b/code/server/sv_init.c @@ -687,6 +687,9 @@ void SV_Init (void) { // init the botlib here because we need the pre-compiler in the UI SV_BotInitBotLib(); + + // Load saved bans + Cbuf_AddText("rehashbans\n"); } diff --git a/code/server/sv_main.c b/code/server/sv_main.c index 96a65153..552c12b1 100644 --- a/code/server/sv_main.c +++ b/code/server/sv_main.c @@ -54,6 +54,9 @@ cvar_t *sv_floodProtect; cvar_t *sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to 99999 (bug #491) cvar_t *sv_strictAuth; +serverBan_t serverBans[SERVER_MAXBANS]; +int serverBansCount = 0; + /* =============================================================================