From e6e54763d9654d6c1012172d5af6cc3b4a0e40a3 Mon Sep 17 00:00:00 2001
From: Stephen Bennett <spb@exherbo.org>
Date: Sun, 27 Mar 2011 16:35:26 -0400
Subject: [PATCH] Make flood control settings configurable by those who know
 exactly what they're doing. From ircd-seven git changeset
 29aa4203150337925a4f5c6e7da47be5394c2125 .

---
 doc/example.conf   |   2 +-
 doc/reference.conf |  20 +++--
 include/packet.h   |   6 +-
 include/s_conf.h   |   8 +-
 modules/m_info.c   | 218 +++++++++++++++++++++++++--------------------
 src/newconf.c      |   6 +-
 src/packet.c       |  21 +++--
 src/s_conf.c       |  13 ++-
 8 files changed, 173 insertions(+), 121 deletions(-)

diff --git a/doc/example.conf b/doc/example.conf
index 558d51e5..1d49aa40 100755
--- a/doc/example.conf
+++ b/doc/example.conf
@@ -497,7 +497,7 @@ general {
 	disable_auth = no;
 	no_oper_flood = yes;
 	max_targets = 4;
-	client_flood = 20;
+	client_flood_max_lines = 20;
         use_whois_actually = no;
 	oper_only_umodes = operwall, locops, servnotice;
 	oper_umodes = locops, servnotice, operwall, wallop;
diff --git a/doc/reference.conf b/doc/reference.conf
index 93ee97b3..bed0a284 100755
--- a/doc/reference.conf
+++ b/doc/reference.conf
@@ -1167,11 +1167,6 @@ general {
 	 */
 	max_targets = 4;
 
-	/* client flood: maximum number of lines in a clients queue before
-	 * they are dropped for flooding.
-	 */
-	client_flood = 20;
-
         /* use_whois_actually: send clients requesting a whois a numeric
          * giving the real IP of non-spoofed clients to prevent DNS abuse.
          */
@@ -1255,6 +1250,21 @@ general {
 	/* throttle_count: Number of connections within throttle_duration that it takes
 	 * for throttling to take effect */
 	throttle_count = 4;
+
+   /* client flood_max_lines: maximum number of lines in a clients queue before
+    * they are dropped for flooding.
+    */
+   client_flood_max_lines = 20;
+
+   /* Flood control settings. DO NOT CHANGE THESE without extensive discussion
+    * and testing by someone who knows exactly what they do.
+    *
+    * These settings replicate charybdis-3.3 behaviour.
+    */
+   client_flood_burst_rate = 40;
+   client_flood_burst_max = 5;
+   client_flood_message_time = 1;
+   client_flood_message_num = 2;
 };
 
 modules {
diff --git a/include/packet.h b/include/packet.h
index e66dd528..35f6c41f 100644
--- a/include/packet.h
+++ b/include/packet.h
@@ -43,9 +43,13 @@
  * just connected.  this allows clients to rejoin multiple channels
  * without being so heavily penalised they excess flood.
  */
-#define MAX_FLOOD 5
+/*
+ * spb: Made these configurable
+ */
+#define MAX_FLOOD ConfigFileEntry.client_flood_burst_max
 #define MAX_FLOOD_BURST MAX_FLOOD * 8
 
+
 extern PF read_packet;
 extern EVH flood_recalc;
 extern void flood_endgrace(struct Client *);
diff --git a/include/s_conf.h b/include/s_conf.h
index df85c359..10aba3aa 100644
--- a/include/s_conf.h
+++ b/include/s_conf.h
@@ -205,7 +205,6 @@ struct config_file_entry
 	int min_nonwildcard;
 	int min_nonwildcard_simple;
 	int default_floodcount;
-	int client_flood;
 	int default_ident_timeout;
 	int use_egd;
 	int ping_cookie;
@@ -225,6 +224,13 @@ struct config_file_entry
 	int global_snotices;
 	int operspy_dont_care_user_info;
 	int use_propagated_bans;
+
+	int client_flood_max_lines;
+	int client_flood_burst_rate;
+	int client_flood_burst_max;
+	int client_flood_message_time;
+	int client_flood_message_num;
+
 };
 
 struct config_channel_entry
diff --git a/modules/m_info.c b/modules/m_info.c
index a52b272f..08e2f470 100644
--- a/modules/m_info.c
+++ b/modules/m_info.c
@@ -122,11 +122,35 @@ static struct InfoStruct info_table[] = {
 		"Prepend 'Client Exit:' to user QUIT messages"
 	},
 	{
-		"client_flood",
+		"client_flood_max_lines",
 		OUTPUT_DECIMAL,
-		&ConfigFileEntry.client_flood,
+		&ConfigFileEntry.client_flood_max_lines,
 		"Number of lines before a client Excess Flood's",
 	},
+	{
+		"client_flood_burst_rate",
+		OUTPUT_DECIMAL,
+		&ConfigFileEntry.client_flood_burst_rate,
+		"Rate at which burst lines are processed",
+	},
+	{
+		"client_flood_burst_max",
+		OUTPUT_DECIMAL,
+		&ConfigFileEntry.client_flood_burst_max,
+		"Number of lines to permit at client_flood_burst_rate",
+	},
+	{
+		"client_flood_message_num",
+		OUTPUT_DECIMAL,
+		&ConfigFileEntry.client_flood_message_num,
+		"Number of messages to allow per client_flood_message_time outside of burst",
+	},
+	{
+		"client_flood_message_time",
+		OUTPUT_DECIMAL,
+		&ConfigFileEntry.client_flood_message_time,
+		"Time to allow per client_flood_message_num outside of burst",
+	},
 	{
 		"connect_timeout",
 		OUTPUT_DECIMAL,
@@ -626,9 +650,9 @@ static struct InfoStruct info_table[] = {
 /* *INDENT-ON* */
 
 /*
-** m_info
-**  parv[1] = servername
-*/
+ ** m_info
+ **  parv[1] = servername
+ */
 static int
 m_info(struct Client *client_p, struct Client *source_p, int parc, const char *parv[])
 {
@@ -638,7 +662,7 @@ m_info(struct Client *client_p, struct Client *source_p, int parc, const char *p
 	{
 		/* safe enough to give this on a local connect only */
 		sendto_one(source_p, form_str(RPL_LOAD2HI),
-			   me.name, source_p->name, "INFO");
+				me.name, source_p->name, "INFO");
 		sendto_one_numeric(source_p, RPL_ENDOFINFO, form_str(RPL_ENDOFINFO));
 		return 0;
 	}
@@ -658,9 +682,9 @@ m_info(struct Client *client_p, struct Client *source_p, int parc, const char *p
 }
 
 /*
-** mo_info
-**  parv[1] = servername
-*/
+ ** mo_info
+ **  parv[1] = servername
+ */
 static int
 mo_info(struct Client *client_p, struct Client *source_p, int parc, const char *parv[])
 {
@@ -716,12 +740,12 @@ send_birthdate_online_time(struct Client *source_p)
 {
 	char tbuf[26]; /* this needs to be 26 - see ctime_r manpage */
 	sendto_one(source_p, ":%s %d %s :Birth Date: %s, compile # %s",
-		   get_id(&me, source_p), RPL_INFO, 
-		   get_id(source_p, source_p), creation, generation);
+			get_id(&me, source_p), RPL_INFO, 
+			get_id(source_p, source_p), creation, generation);
 
 	sendto_one(source_p, ":%s %d %s :On-line since %s",
-		   get_id(&me, source_p), RPL_INFO, 
-		   get_id(source_p, source_p), rb_ctime(startup_time, tbuf, sizeof(tbuf)));
+			get_id(&me, source_p), RPL_INFO, 
+			get_id(source_p, source_p), rb_ctime(startup_time, tbuf, sizeof(tbuf)));
 }
 
 /*
@@ -746,18 +770,18 @@ send_conf_options(struct Client *source_p)
 		if(infoptr->intvalue)
 		{
 			sendto_one(source_p, ":%s %d %s :%-30s %-5d [%-30s]",
-				   get_id(&me, source_p), RPL_INFO,
-				   get_id(source_p, source_p),
-				   infoptr->name, infoptr->intvalue, 
-				   infoptr->desc);
+					get_id(&me, source_p), RPL_INFO,
+					get_id(source_p, source_p),
+					infoptr->name, infoptr->intvalue, 
+					infoptr->desc);
 		}
 		else
 		{
 			sendto_one(source_p, ":%s %d %s :%-30s %-5s [%-30s]",
-				   get_id(&me, source_p), RPL_INFO,
-				   get_id(source_p, source_p),
-				   infoptr->name, infoptr->strvalue, 
-				   infoptr->desc);
+					get_id(&me, source_p), RPL_INFO,
+					get_id(source_p, source_p),
+					infoptr->name, infoptr->strvalue, 
+					infoptr->desc);
 		}
 	}
 
@@ -771,95 +795,95 @@ send_conf_options(struct Client *source_p)
 			/*
 			 * For "char *" references
 			 */
-		case OUTPUT_STRING:
-			{
-				char *option = *((char **) info_table[i].option);
+			case OUTPUT_STRING:
+				{
+					char *option = *((char **) info_table[i].option);
 
-				sendto_one(source_p, ":%s %d %s :%-30s %-5s [%-30s]",
-					   get_id(&me, source_p), RPL_INFO,
-					   get_id(source_p, source_p),
-					   info_table[i].name,
-					   option ? option : "NONE",
-					   info_table[i].desc ? info_table[i].desc : "<none>");
+					sendto_one(source_p, ":%s %d %s :%-30s %-5s [%-30s]",
+							get_id(&me, source_p), RPL_INFO,
+							get_id(source_p, source_p),
+							info_table[i].name,
+							option ? option : "NONE",
+							info_table[i].desc ? info_table[i].desc : "<none>");
 
-				break;
-			}
-			/*
-			 * For "char foo[]" references
-			 */
-		case OUTPUT_STRING_PTR:
-			{
-				char *option = (char *) info_table[i].option;
+					break;
+				}
+				/*
+				 * For "char foo[]" references
+				 */
+			case OUTPUT_STRING_PTR:
+				{
+					char *option = (char *) info_table[i].option;
 
-				sendto_one(source_p, ":%s %d %s :%-30s %-5s [%-30s]",
-					   get_id(&me, source_p), RPL_INFO,
-					   get_id(source_p, source_p),
-					   info_table[i].name,
-					   EmptyString(option) ? "NONE" : option,
-					   info_table[i].desc ? info_table[i].desc : "<none>");
+					sendto_one(source_p, ":%s %d %s :%-30s %-5s [%-30s]",
+							get_id(&me, source_p), RPL_INFO,
+							get_id(source_p, source_p),
+							info_table[i].name,
+							EmptyString(option) ? "NONE" : option,
+							info_table[i].desc ? info_table[i].desc : "<none>");
 
-				break;
-			}
-			/*
-			 * Output info_table[i].option as a decimal value.
-			 */
-		case OUTPUT_DECIMAL:
-			{
-				int option = *((int *) info_table[i].option);
+					break;
+				}
+				/*
+				 * Output info_table[i].option as a decimal value.
+				 */
+			case OUTPUT_DECIMAL:
+				{
+					int option = *((int *) info_table[i].option);
 
-				sendto_one(source_p, ":%s %d %s :%-30s %-5d [%-30s]",
-					   get_id(&me, source_p), RPL_INFO,
-					   get_id(source_p, source_p),
-					   info_table[i].name,
-					   option,
-					   info_table[i].desc ? info_table[i].desc : "<none>");
+					sendto_one(source_p, ":%s %d %s :%-30s %-5d [%-30s]",
+							get_id(&me, source_p), RPL_INFO,
+							get_id(source_p, source_p),
+							info_table[i].name,
+							option,
+							info_table[i].desc ? info_table[i].desc : "<none>");
 
-				break;
-			}
+					break;
+				}
 
-			/*
-			 * Output info_table[i].option as "ON" or "OFF"
-			 */
-		case OUTPUT_BOOLEAN:
-			{
-				int option = *((int *) info_table[i].option);
+				/*
+				 * Output info_table[i].option as "ON" or "OFF"
+				 */
+			case OUTPUT_BOOLEAN:
+				{
+					int option = *((int *) info_table[i].option);
 
-				sendto_one(source_p, ":%s %d %s :%-30s %-5s [%-30s]",
-					   get_id(&me, source_p), RPL_INFO,
-					   get_id(source_p, source_p),
-					   info_table[i].name,
-					   option ? "ON" : "OFF",
-					   info_table[i].desc ? info_table[i].desc : "<none>");
+					sendto_one(source_p, ":%s %d %s :%-30s %-5s [%-30s]",
+							get_id(&me, source_p), RPL_INFO,
+							get_id(source_p, source_p),
+							info_table[i].name,
+							option ? "ON" : "OFF",
+							info_table[i].desc ? info_table[i].desc : "<none>");
 
-				break;
-			}
-			/*
-			 * Output info_table[i].option as "YES" or "NO"
-			 */
-		case OUTPUT_BOOLEAN_YN:
-			{
-				int option = *((int *) info_table[i].option);
+					break;
+				}
+				/*
+				 * Output info_table[i].option as "YES" or "NO"
+				 */
+			case OUTPUT_BOOLEAN_YN:
+				{
+					int option = *((int *) info_table[i].option);
 
-				sendto_one(source_p, ":%s %d %s :%-30s %-5s [%-30s]",
-					   get_id(&me, source_p), RPL_INFO,
-					   get_id(source_p, source_p),
-					   info_table[i].name,
-					   option ? "YES" : "NO",
-					   info_table[i].desc ? info_table[i].desc : "<none>");
+					sendto_one(source_p, ":%s %d %s :%-30s %-5s [%-30s]",
+							get_id(&me, source_p), RPL_INFO,
+							get_id(source_p, source_p),
+							info_table[i].name,
+							option ? "YES" : "NO",
+							info_table[i].desc ? info_table[i].desc : "<none>");
 
-				break;
-			}
+					break;
+				}
 
-		case OUTPUT_BOOLEAN2:
-		{
-			int option = *((int *) info_table[i].option);
+			case OUTPUT_BOOLEAN2:
+				{
+					int option = *((int *) info_table[i].option);
 
-			sendto_one(source_p, ":%s %d %s :%-30s %-5s [%-30s]",
-				   me.name, RPL_INFO, source_p->name,
-				   info_table[i].name,
-				   option ? ((option == 1) ? "MASK" : "YES") : "NO",
-				   info_table[i].desc ? info_table[i].desc : "<none>");
-		}		/* switch (info_table[i].output_type) */
+					sendto_one(source_p, ":%s %d %s :%-30s %-5s [%-30s]",
+							me.name, RPL_INFO, source_p->name,
+							info_table[i].name,
+							option ? ((option == 1) ? "MASK" : "YES") : "NO",
+							info_table[i].desc ? info_table[i].desc : "<none>");
+				}		/* switch (info_table[i].output_type) */
 		}
 	}			/* forloop */
 
diff --git a/src/newconf.c b/src/newconf.c
index 2cb8824d..8f8dd380 100644
--- a/src/newconf.c
+++ b/src/newconf.c
@@ -2207,7 +2207,6 @@ static struct ConfEntry conf_general_table[] =
 	{ "burst_away",		CF_YESNO, NULL, 0, &ConfigFileEntry.burst_away		},
 	{ "caller_id_wait",	CF_TIME,  NULL, 0, &ConfigFileEntry.caller_id_wait	},
 	{ "client_exit",	CF_YESNO, NULL, 0, &ConfigFileEntry.client_exit		},
-	{ "client_flood",	CF_INT,   NULL, 0, &ConfigFileEntry.client_flood	},
 	{ "collision_fnc",	CF_YESNO, NULL, 0, &ConfigFileEntry.collision_fnc	},
 	{ "connect_timeout",	CF_TIME,  NULL, 0, &ConfigFileEntry.connect_timeout	},
 	{ "default_floodcount", CF_INT,   NULL, 0, &ConfigFileEntry.default_floodcount	},
@@ -2252,6 +2251,11 @@ static struct ConfEntry conf_general_table[] =
 	{ "use_whois_actually", CF_YESNO, NULL, 0, &ConfigFileEntry.use_whois_actually	},
 	{ "warn_no_nline",	CF_YESNO, NULL, 0, &ConfigFileEntry.warn_no_nline	},
 	{ "use_propagated_bans",CF_YESNO, NULL, 0, &ConfigFileEntry.use_propagated_bans	},
+	{ "client_flood_max_lines",	CF_INT,   NULL, 0, &ConfigFileEntry.client_flood_max_lines	},
+	{ "client_flood_burst_rate",	CF_INT,   NULL, 0, &ConfigFileEntry.client_flood_burst_rate	},
+	{ "client_flood_burst_max",	CF_INT,   NULL, 0, &ConfigFileEntry.client_flood_burst_max	},
+	{ "client_flood_message_num",	CF_INT,   NULL, 0, &ConfigFileEntry.client_flood_message_num	},
+	{ "client_flood_message_time",	CF_INT,   NULL, 0, &ConfigFileEntry.client_flood_message_time	},
 	{ "\0", 		0, 	  NULL, 0, NULL }
 };
 
diff --git a/src/packet.c b/src/packet.c
index fb8dcabb..6f14aa92 100644
--- a/src/packet.c
+++ b/src/packet.c
@@ -38,7 +38,6 @@
 static char readBuf[READBUF_SIZE];
 static void client_dopacket(struct Client *client_p, char *buffer, size_t length);
 
-
 /*
  * parse_client_queued - parse client queued messages
  */
@@ -124,6 +123,9 @@ parse_client_queued(struct Client *client_p)
 			{
 				if(client_p->localClient->sent_parsed >= client_p->localClient->allow_read)
 					break;
+				/* spb: Add second layer of throttling to n lines per second, even during burst */
+				if(client_p->localClient->actually_read >= ConfigFileEntry.client_flood_burst_rate)
+					break;
 			}
 
 			/* allow opers 4 times the amount of messages as users. why 4?
@@ -142,7 +144,9 @@ parse_client_queued(struct Client *client_p)
 			client_dopacket(client_p, readBuf, dolen);
 			if(IsAnyDead(client_p))
 				return;
-			client_p->localClient->sent_parsed++;
+
+			client_p->localClient->sent_parsed += ConfigFileEntry.client_flood_message_time;
+			client_p->localClient->actually_read++;
 		}
 	}
 }
@@ -188,15 +192,14 @@ flood_recalc(void *unused)
 			continue;
 		
 		if(IsFloodDone(client_p))
-			client_p->localClient->sent_parsed -= 2;
+			client_p->localClient->sent_parsed -= ConfigFileEntry.client_flood_message_num;
 		else
 			client_p->localClient->sent_parsed = 0;
 			
 		if(client_p->localClient->sent_parsed < 0)
 			client_p->localClient->sent_parsed = 0;
 
-		if(--client_p->localClient->actually_read < 0)
-			client_p->localClient->actually_read = 0;
+		client_p->localClient->actually_read = 0;
 
 		parse_client_queued(client_p);
 		
@@ -217,8 +220,7 @@ flood_recalc(void *unused)
 		if(client_p->localClient->sent_parsed < 0)
 			client_p->localClient->sent_parsed = 0;
 
-		if(--client_p->localClient->actually_read < 0)
-			client_p->localClient->actually_read = 0;
+		client_p->localClient->actually_read = 0;
 
 		parse_client_queued(client_p);
 	}
@@ -231,7 +233,6 @@ void
 read_packet(rb_fde_t * F, void *data)
 {
 	struct Client *client_p = data;
-	struct LocalUser *lclient_p = client_p->localClient;
 	int length = 0;
 	int lbuf_len;
 
@@ -288,8 +289,6 @@ read_packet(rb_fde_t * F, void *data)
 
 		lbuf_len = rb_linebuf_parse(&client_p->localClient->buf_recvq, readBuf, length, binary);
 
-		lclient_p->actually_read += lbuf_len;
-
 		if(IsAnyDead(client_p))
 			return;
 			
@@ -301,7 +300,7 @@ read_packet(rb_fde_t * F, void *data)
 			
 		/* Check to make sure we're not flooding */
 		if(!IsAnyServer(client_p) &&
-		   (rb_linebuf_alloclen(&client_p->localClient->buf_recvq) > ConfigFileEntry.client_flood))
+		   (rb_linebuf_alloclen(&client_p->localClient->buf_recvq) > ConfigFileEntry.client_flood_max_lines))
 		{
 			if(!(ConfigFileEntry.no_oper_flood && IsOper(client_p)))
 			{
diff --git a/src/s_conf.c b/src/s_conf.c
index e45a0e38..43acbdbe 100644
--- a/src/s_conf.c
+++ b/src/s_conf.c
@@ -784,7 +784,6 @@ set_default_conf(void)
 	ConfigFileEntry.min_nonwildcard_simple = 3;
 	ConfigFileEntry.default_floodcount = 8;
 	ConfigFileEntry.default_ident_timeout = 5;
-	ConfigFileEntry.client_flood = CLIENT_FLOOD_DEFAULT;
 	ConfigFileEntry.tkline_expire_notices = 0;
 
         ConfigFileEntry.reject_after_count = 5;
@@ -793,6 +792,12 @@ set_default_conf(void)
 	ConfigFileEntry.throttle_count = 4;
 	ConfigFileEntry.throttle_duration = 60;
 
+	ConfigFileEntry.client_flood_max_lines = CLIENT_FLOOD_DEFAULT;
+	ConfigFileEntry.client_flood_burst_rate = 5;
+	ConfigFileEntry.client_flood_burst_max = 5;
+	ConfigFileEntry.client_flood_message_time = 1;
+	ConfigFileEntry.client_flood_message_num = 2;
+
 	ServerInfo.default_max_clients = MAXCONNECTIONS;
 
 	if (!alias_dict)
@@ -858,9 +863,9 @@ validate_conf(void)
 				
 	}
 
-	if((ConfigFileEntry.client_flood < CLIENT_FLOOD_MIN) ||
-	   (ConfigFileEntry.client_flood > CLIENT_FLOOD_MAX))
-		ConfigFileEntry.client_flood = CLIENT_FLOOD_MAX;
+	if((ConfigFileEntry.client_flood_max_lines < CLIENT_FLOOD_MIN) ||
+	   (ConfigFileEntry.client_flood_max_lines > CLIENT_FLOOD_MAX))
+		ConfigFileEntry.client_flood_max_lines = CLIENT_FLOOD_MAX;
 
 	if(!split_users || !split_servers ||
 	   (!ConfigChannel.no_create_on_split && !ConfigChannel.no_join_on_split))