From 76361cda8460e28c1cdb39311818872bfc6ed46e Mon Sep 17 00:00:00 2001
From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Date: Fri, 18 Sep 2020 17:47:37 +0100
Subject: [PATCH] Ports TG's topic limiting (#14330)
---
code/modules/client/client defines.dm | 5 ++-
code/modules/client/client procs.dm | 50 ++++++++++++++++++++++++---
2 files changed, 50 insertions(+), 5 deletions(-)
diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm
index 72537f8ccc3..46ec605e71d 100644
--- a/code/modules/client/client defines.dm
+++ b/code/modules/client/client defines.dm
@@ -36,7 +36,10 @@
////////////
//SECURITY//
////////////
- var/next_allowed_topic_time = 10
+
+ ///Used for limiting the rate of topic sends by the client to avoid abuse
+ var/list/topiclimiter
+
// comment out the line below when debugging locally to enable the options & messages menu
//control_freak = 1
diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm
index ae503be111b..f8ef211d5de 100644
--- a/code/modules/client/client procs.dm
+++ b/code/modules/client/client procs.dm
@@ -11,6 +11,13 @@
#define SUGGESTED_CLIENT_VERSION 511 // only integers (e.g: 510, 511) useful here. Does not properly handle minor versions (e.g: 510.58, 511.848)
#define SSD_WARNING_TIMER 30 // cycles, not seconds, so 30=60s
+#define LIMITER_SIZE 5
+#define CURRENT_SECOND 1
+#define SECOND_COUNT 2
+#define CURRENT_MINUTE 3
+#define MINUTE_COUNT 4
+#define ADMINSWARNED_AT 5
+
/*
When somebody clicks a link in game, this Topic is called first.
It does the stuff in this proc and then is redirected to the Topic() proc for the src=[0xWhatever]
@@ -59,10 +66,38 @@
if(href_list["_src_"] == "chat")
return chatOutput.Topic(href, href_list)
- //Reduces spamming of links by dropping calls that happen during the delay period
- if(next_allowed_topic_time > world.time)
- return
- next_allowed_topic_time = world.time + TOPIC_SPAM_DELAY
+ // Rate limiting
+ var/mtl = 100 // 100 topics per minute
+ if (!holder) // Admins are allowed to spam click, deal with it.
+ var/minute = round(world.time, 600)
+ if (!topiclimiter)
+ topiclimiter = new(LIMITER_SIZE)
+ if (minute != topiclimiter[CURRENT_MINUTE])
+ topiclimiter[CURRENT_MINUTE] = minute
+ topiclimiter[MINUTE_COUNT] = 0
+ topiclimiter[MINUTE_COUNT] += 1
+ if (topiclimiter[MINUTE_COUNT] > mtl)
+ var/msg = "Your previous action was ignored because you've done too many in a minute."
+ if (minute != topiclimiter[ADMINSWARNED_AT]) //only one admin message per-minute. (if they spam the admins can just boot/ban them)
+ topiclimiter[ADMINSWARNED_AT] = minute
+ msg += " Administrators have been informed."
+ log_game("[key_name(src)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute")
+ message_admins("[ADMIN_LOOKUPFLW(usr)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute")
+ to_chat(src, "[msg]")
+ return
+
+ var/stl = 10 // 10 topics a second
+ if (!holder) // Admins are allowed to spam click, deal with it.
+ var/second = round(world.time, 10)
+ if (!topiclimiter)
+ topiclimiter = new(LIMITER_SIZE)
+ if (second != topiclimiter[CURRENT_SECOND])
+ topiclimiter[CURRENT_SECOND] = second
+ topiclimiter[SECOND_COUNT] = 0
+ topiclimiter[SECOND_COUNT] += 1
+ if (topiclimiter[SECOND_COUNT] > stl)
+ to_chat(src, "Your previous action was ignored because you've done too many in a second")
+ return
//search the href for script injection
if( findtext(href,"