diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 65f3e8b79565293a63137200a932969313d0cc91..ec2ce776cd821eebc233b0dc84a9f4c60c73ac97 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 0ce20f1a3177faceb12d74c7265b1abf748bb074..c51af62b63366894c9ac9aaeab034a8cadcb30c3 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 b28aab7a068a29a273d78eb0f76b3dcb43c5b3ae..70086ef2b25e839b44921afb02a1741f1d9d6446 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 3608d81bc98b26a1ac905787d435660bea7fc0fe..9a2e11f4d7fe0fb2c3d0c7f530cb7996e92d5072 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 75e3aaa6d7c0b7d93f93a352c31619bc27f648af..f425bbce69583f337d56dd02ed20f1fb66228e7d 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;
 }