diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 65f3e8b7..ec2ce776 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -180,6 +180,8 @@ consvar_t cv_playbackspeed = {"playbackspeed", "1", 0, playbackspeed_cons_t, NUL consvar_t cv_httpsource = {"http_source", "", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_kicktime = CVAR_INIT ("kicktime", "10", CV_SAVE, CV_Unsigned, NULL); + static inline void *G_DcpyTiccmd(void* dest, const ticcmd_t* src, const size_t n) { const size_t d = n / sizeof(ticcmd_t); @@ -2654,6 +2656,13 @@ static void Command_ShowBan(void) //Print out ban list else CONS_Printf("%s: %s/%s ", sizeu1(i+1), address, mask); + if (I_GetUnbanTime && I_GetUnbanTime(i) != NO_BAN_TIME) + { + // todo: maybe try to actually print out the time remaining, + // and/or the datetime of the unbanning? + CONS_Printf("(temporary) "); + } + if (I_GetBanReason && (reason = I_GetBanReason(i)) != NULL) CONS_Printf("(%s)\n", reason); else @@ -2669,6 +2678,8 @@ void D_SaveBan(void) FILE *f; size_t i; const char *address, *mask, *reason; + const time_t curTime = time(NULL); + time_t unbanTime = NO_BAN_TIME; const char *path = va("%s"PATHSEP"%s", srb2home, "ban.txt"); f = fopen(path, "w"); @@ -2681,11 +2692,24 @@ void D_SaveBan(void) for (i = 0; (address = I_GetBanAddress(i)) != NULL; i++) { + if (I_GetUnbanTime) + { + unbanTime = I_GetUnbanTime(i); + } + + if (unbanTime != NO_BAN_TIME && curTime >= unbanTime) + { + // Don't need to save this one anymore. + continue; + } + if (!I_GetBanMask || (mask = I_GetBanMask(i)) == NULL) fprintf(f, "%s 0", address); else fprintf(f, "%s %s", address, mask); + fprintf(f, " %ld", (long)unbanTime); + if (I_GetBanReason && (reason = I_GetBanReason(i)) != NULL) fprintf(f, " %s\n", reason); else @@ -2709,6 +2733,7 @@ static void Ban_Load_File(boolean warning) FILE *f; size_t i; const char *address, *mask, *reason; + time_t unbanTime = NO_BAN_TIME; char buffer[MAX_WADPATH]; f = fopen(va("%s"PATHSEP"%s", srb2home, "ban.txt"), "r"); @@ -2726,9 +2751,12 @@ static void Ban_Load_File(boolean warning) { address = strtok(buffer, " \t\r\n"); mask = strtok(NULL, " \t\r\n"); + unbanTime = atoi(strtok(NULL, " \t\r\n")); reason = strtok(NULL, "\r\n"); I_SetBanAddress(address, mask); + if (I_SetUnbanTime) + I_SetUnbanTime(unbanTime); if (I_SetBanReason) I_SetBanReason(reason); } @@ -3229,6 +3257,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) XBOXSTATIC char buf[3 + MAX_REASONLENGTH]; char *reason = buf; kickreason_t kickreason = KR_KICK; + UINT32 banMinutes = 0; pnum = READUINT8(*p); msg = READUINT8(*p); @@ -3292,17 +3321,35 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) // Save bans here. Used to be split between here and the actual command, depending on // whenever the server did it or a remote admin did it, but it's simply more convenient // to keep it all in one place. - if (server && (msg == KICK_MSG_BANNED || msg == KICK_MSG_CUSTOM_BAN)) + if (server) { - if (I_Ban && !I_Ban(playernode[(INT32)pnum])) + if (msg == KICK_MSG_GO_AWAY || msg == KICK_MSG_CUSTOM_KICK) { - CONS_Alert(CONS_WARNING, M_GetText("Ban failed. Invalid node?\n")); + // Kick as a temporary ban. + banMinutes = cv_kicktime.value; } - else + + if (msg == KICK_MSG_BANNED || msg == KICK_MSG_CUSTOM_BAN || banMinutes) { - if (I_SetBanReason) - I_SetBanReason(reason); - D_SaveBan(); + if (I_Ban && !I_Ban(playernode[(INT32)pnum])) + { + CONS_Alert(CONS_WARNING, M_GetText("Ban failed. Invalid node?\n")); + } + else + { + if (I_SetBanReason) + I_SetBanReason(reason); + + if (I_SetUnbanTime) + { + if (banMinutes) + I_SetUnbanTime(time(NULL) + (banMinutes * 60)); + else + I_SetUnbanTime(NO_BAN_TIME); + } + + D_SaveBan(); + } } } @@ -3388,9 +3435,13 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) #ifdef DUMPCONSISTENCY if (msg == KICK_MSG_CON_FAIL) SV_SavedGame(); #endif + + LUAh_GameQuit(false); + D_QuitNetGame(); CL_Reset(); D_StartTitle(); + if (msg == KICK_MSG_CON_FAIL) M_StartMessage(M_GetText("Server closed connection\n(Synch failure)\nPress ESC\n"), NULL, MM_NOTHING); else if (msg == KICK_MSG_PING_HIGH) @@ -3566,6 +3617,7 @@ void D_ClientServerInit(void) #ifndef NONET COM_AddCommand("getplayernum", Command_GetPlayerNum); COM_AddCommand("kick", Command_Kick); + CV_RegisterVar(&cv_kicktime); COM_AddCommand("ban", Command_Ban); COM_AddCommand("banip", Command_BanIP); COM_AddCommand("clearbans", Command_ClearBans); @@ -4066,26 +4118,65 @@ static void HandleConnect(SINT8 node) UINT8 maxplayers = min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxplayers.value); if (bannednode && bannednode[node]) - SV_SendRefuse(node, M_GetText("You have been banned\nfrom the server")); + { + if (bannednodetimeleft && bannednodetimeleft[node] != NO_BAN_TIME) + { + int minutes = bannednodetimeleft[node] / 60; + int hours = minutes / 60; + + if (hours) + { + SV_SendRefuse(node, va(M_GetText("You have been temporarily\nkicked from the server.\n(Time remaining: %d hour%s)"), hours, hours > 1 ? "s" : "")); + } + else if (minutes) + { + SV_SendRefuse(node, va(M_GetText("You have been temporarily\nkicked from the server.\n(Time remaining: %d minute%s)"), minutes, minutes > 1 ? "s" : "")); + } + else + { + SV_SendRefuse(node, M_GetText("You have been temporarily\nkicked from the server.\n(Time remaining: <1 minute)")); + } + } + else + { + SV_SendRefuse(node, M_GetText("You have been banned\nfrom the server.")); + } + } else if (netbuffer->u.clientcfg._255 != 255 || netbuffer->u.clientcfg.packetversion != PACKETVERSION) + { SV_SendRefuse(node, "Incompatible packet formats."); + } else if (strncmp(netbuffer->u.clientcfg.application, SRB2APPLICATION, sizeof netbuffer->u.clientcfg.application)) - SV_SendRefuse(node, "Different SRB2 modifications\nare not compatible."); + { + SV_SendRefuse(node, "Different SRB2Kart modifications\nare not compatible."); + } else if (netbuffer->u.clientcfg.version != VERSION || netbuffer->u.clientcfg.subversion != SUBVERSION) + { SV_SendRefuse(node, va(M_GetText("Different SRB2Kart versions cannot\nplay a netgame!\n(server version %d.%d)"), VERSION, SUBVERSION)); + } else if (!cv_allownewplayer.value && node) - SV_SendRefuse(node, M_GetText("The server is not accepting\njoins for the moment")); + { + SV_SendRefuse(node, M_GetText("The server is not accepting\njoins for the moment.")); + } else if (D_NumPlayers() >= maxplayers) + { SV_SendRefuse(node, va(M_GetText("Maximum players reached: %d"), maxplayers)); - else if (netgame && D_NumPlayers() + netbuffer->u.clientcfg.localplayers > maxplayers) - SV_SendRefuse(node, va(M_GetText("Number of local players\nwould exceed maximum: %d"), maxplayers)); + } else if (netgame && netbuffer->u.clientcfg.localplayers > 4) // Hacked client? + { SV_SendRefuse(node, M_GetText("Too many players from\nthis node.")); + } + else if (netgame && D_NumPlayers() + netbuffer->u.clientcfg.localplayers > maxplayers) + { + SV_SendRefuse(node, va(M_GetText("Number of local players\nwould exceed maximum: %d"), maxplayers)); + } else if (netgame && !netbuffer->u.clientcfg.localplayers) // Stealth join? + { SV_SendRefuse(node, M_GetText("No players from\nthis node.")); + } else { #ifndef NONET @@ -4319,13 +4410,13 @@ static void HandlePacketFromAwayNode(SINT8 node) break; } - M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"), - reason), NULL, MM_NOTHING); - D_QuitNetGame(); CL_Reset(); D_StartTitle(); + M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"), + reason), NULL, MM_NOTHING); + free(reason); // Will be reset by caller. Signals refusal. diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 0ce20f1a..c51af62b 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -504,6 +504,8 @@ extern INT32 mapchangepending; extern doomdata_t *netbuffer; extern consvar_t cv_stunserver; extern consvar_t cv_httpsource; +extern consvar_t cv_kicktime; + extern consvar_t cv_showjoinaddress; extern consvar_t cv_playbackspeed; diff --git a/src/d_net.c b/src/d_net.c index b28aab7a..70086ef2 100644 --- a/src/d_net.c +++ b/src/d_net.c @@ -80,9 +80,12 @@ const char *(*I_GetNodeAddress) (INT32 node) = NULL; const char *(*I_GetBanAddress) (size_t ban) = NULL; const char *(*I_GetBanMask) (size_t ban) = NULL; const char *(*I_GetBanReason) (size_t ban) = NULL; +time_t (*I_GetUnbanTime) (size_t ban) = NULL; boolean (*I_SetBanAddress) (const char *address, const char *mask) = NULL; boolean (*I_SetBanReason) (const char *reason) = NULL; +boolean (*I_SetUnbanTime) (time_t timestamp) = NULL; boolean *bannednode = NULL; +time_t *bannednodetimeleft = NULL; // network stats diff --git a/src/i_net.h b/src/i_net.h index 3608d81b..9a2e11f4 100644 --- a/src/i_net.h +++ b/src/i_net.h @@ -31,6 +31,8 @@ /// For use on the internet #define INETPACKETLENGTH 1024 +#define NO_BAN_TIME (time_t)(-1) + extern INT16 hardware_MAXPACKETLENGTH; extern INT32 net_bandwidth; // in byte/s @@ -146,9 +148,12 @@ extern const char *(*I_GetNodeAddress) (INT32 node); extern const char *(*I_GetBanAddress) (size_t ban); extern const char *(*I_GetBanMask) (size_t ban); extern const char *(*I_GetBanReason) (size_t ban); +extern time_t (*I_GetUnbanTime) (size_t ban); extern boolean (*I_SetBanAddress) (const char *address,const char *mask); extern boolean (*I_SetBanReason) (const char *reason); +extern boolean (*I_SetUnbanTime) (time_t timestamp); extern boolean *bannednode; +extern time_t *bannednodetimeleft; /// \brief Called by D_SRB2Main to be defined by extern network driver boolean I_InitNetwork(void); diff --git a/src/i_tcp.c b/src/i_tcp.c index 75e3aaa6..f425bbce 100644 --- a/src/i_tcp.c +++ b/src/i_tcp.c @@ -236,7 +236,7 @@ typedef struct mysockaddr_t address; UINT8 mask; char *reason; - // TODO: timestamp, for tempbans! + time_t timestamp; } banned_t; static SOCKET_TYPE mysockets[MAXNETNODES+1] = {ERRSOCKET}; @@ -254,6 +254,7 @@ static size_t numbans = 0; static size_t banned_size = 0; static boolean SOCK_bannednode[MAXNETNODES+1]; /// \note do we really need the +1? +static time_t SOCK_bannednodetimeleft[MAXNETNODES+1]; static boolean init_tcp_driver = false; static const char *serverport_name = DEFAULTPORT; @@ -505,10 +506,24 @@ static const char *SOCK_GetBanReason(size_t ban) (void)ban; return NULL; #else + if (ban >= numbans) + return NULL; return banned[ban].reason; #endif } +static time_t SOCK_GetUnbanTime(size_t ban) +{ +#ifdef NONET + (void)ban; + return NO_BAN_TIME; +#else + if (ban >= numbans) + return NO_BAN_TIME; + return banned[ban].timestamp; +#endif +} + #ifndef NONET static boolean SOCK_cmpaddr(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask) { @@ -656,6 +671,8 @@ static boolean SOCK_Get(void) j = getfreenode(); if (j > 0) { + const time_t curTime = time(NULL); + M_Memcpy(&clientaddress[j], &fromaddress, fromlen); nodesocket[j] = mysockets[n]; DEBFILE(va("New node detected: node:%d address:%s\n", j, @@ -668,13 +685,37 @@ static boolean SOCK_Get(void) { if (SOCK_cmpaddr(&fromaddress, &banned[i].address, banned[i].mask)) { - SOCK_bannednode[j] = true; - DEBFILE("This dude has been banned\n"); - break; + if (banned[i].timestamp != NO_BAN_TIME) + { + if (curTime >= banned[i].timestamp) + { + SOCK_bannednodetimeleft[j] = NO_BAN_TIME; + SOCK_bannednode[j] = false; + DEBFILE("This dude was banned, but enough time has passed\n"); + break; + } + + SOCK_bannednodetimeleft[j] = banned[i].timestamp - curTime; + SOCK_bannednode[j] = true; + DEBFILE("This dude has been temporarily banned\n"); + break; + } + else + { + SOCK_bannednodetimeleft[j] = NO_BAN_TIME; + SOCK_bannednode[j] = true; + DEBFILE("This dude has been banned\n"); + break; + } } } + if (i == numbans) + { + SOCK_bannednodetimeleft[j] = NO_BAN_TIME; SOCK_bannednode[j] = false; + } + return true; } else @@ -1538,7 +1579,6 @@ static boolean SOCK_SetBanReason(const char *reason) (void)reason; return false; #else - if (!reason) { reason = "No reason given"; @@ -1549,6 +1589,17 @@ static boolean SOCK_SetBanReason(const char *reason) #endif } +static boolean SOCK_SetUnbanTime(time_t timestamp) +{ +#ifdef NONET + (void)reason; + return false; +#else + banned[numbans - 1].timestamp = timestamp; + return true; +#endif +} + static void SOCK_ClearBans(void) { numbans = 0; @@ -1646,9 +1697,12 @@ boolean I_InitTcpNetwork(void) I_GetBanAddress = SOCK_GetBanAddress; I_GetBanMask = SOCK_GetBanMask; I_GetBanReason = SOCK_GetBanReason; + I_GetUnbanTime = SOCK_GetUnbanTime; I_SetBanAddress = SOCK_SetBanAddress; I_SetBanReason = SOCK_SetBanReason; + I_SetUnbanTime = SOCK_SetUnbanTime; bannednode = SOCK_bannednode; + bannednodetimeleft = SOCK_bannednodetimeleft; return ret; }