From 717238d2a2864c51871a3f579f3d40f032c20f46 Mon Sep 17 00:00:00 2001
From: Jilles Tjoelker <jilles@stack.nl>
Date: Sun, 29 Aug 2010 01:26:00 +0200
Subject: [PATCH] Add target change for channels.

This has a separate enabling option channel::channel_target_change.

It applies to PRIVMSG, NOTICE and TOPIC by unvoiced unopped non-opers.

The same slots are used for channels and users.
---
 doc/example.conf         |  1 +
 doc/reference.conf       |  6 ++++++
 doc/tgchange.txt         | 29 ++++++++++++++++-------------
 include/s_conf.h         |  1 +
 include/tgchange.h       |  2 ++
 modules/core/m_message.c | 15 +++++++++++++++
 modules/m_topic.c        | 10 ++++++++++
 src/newconf.c            |  1 +
 src/s_conf.c             |  1 +
 src/tgchange.c           | 22 ++++++++++++++++++++--
 10 files changed, 73 insertions(+), 15 deletions(-)

diff --git a/doc/example.conf b/doc/example.conf
index e1fbf2d3..559388d6 100755
--- a/doc/example.conf
+++ b/doc/example.conf
@@ -337,6 +337,7 @@ channel {
 	kick_on_split_riding = no;
 	only_ascii_channels = no;
 	resv_forcepart = yes;
+	channel_target_change = yes;
 };
 
 serverhide {
diff --git a/doc/reference.conf b/doc/reference.conf
index 03fc0412..c7e72bd2 100755
--- a/doc/reference.conf
+++ b/doc/reference.conf
@@ -755,6 +755,12 @@ channel {
 	 * when a RESV is issued.
 	 */
 	resv_forcepart = yes;
+
+	/* channel target change: restrict how many channels users can
+	 * message per unit of time. IRC operators, channel operators and
+	 * voiced users are exempt.
+	 */
+	channel_target_change = yes;
 };
 
 
diff --git a/doc/tgchange.txt b/doc/tgchange.txt
index 483ca23c..faea0090 100644
--- a/doc/tgchange.txt
+++ b/doc/tgchange.txt
@@ -3,15 +3,17 @@ Lee H <lee -at- leeh.co.uk>
 ---------------------------
 
 Reworked by Jilles Tjoelker, February 2010.
+Channel target change added by Jilles Tjoelker, August 2010.
 
 If the server you are using uses the target change mechanism, then
-restrictions are placed on how many different users you can message in a set
-timeframe. This also applies to invites.
+restrictions are placed on how many different users and/or channels you can
+message in a set timeframe.  This also applies to invites (for the target
+user) and topic changes.
 
-Target change does not apply to channels, ctcp replies, messages to
-yourself or messages to services.
+Target change does not apply to ctcp replies, messages to yourself, messages
+to services and joins.
 
-You will have a set number of 'slots', each different client you message
+You will have a set number of 'slots', each different target you message
 will take up one slot.  A client doing a nick change will not use a new slot,
 however a client disconnecting from the server it is on and reconnecting
 will.  You will receive 1 new slot roughly every minute.
@@ -20,14 +22,14 @@ Additionally, clients that message or invite you are placed in one of a
 small number of special slots, in many cases allowing replies without using
 a slot.
 
-When all slots are filled, messages to new clients will not be accepted.
-Messages to clients already filling a slot will be accepted.  If all slots
+When all slots are filled, messages to new targets will not be accepted.
+Messages to targets already filling a slot will be accepted.  If all slots
 are full, you will receive the ERR_TARGCHANGE numeric, number 707 in the
 form:
-:<server> 707 <yournick> <targetnick> :Targets changing too fast, message dropped
+:<server> 707 <yournick> <target> :Targets changing too fast, message dropped
 
-The slots are operated in an LRU (least recently used), so the person you
-have talked to least recently will be replaced.
+The slots are operated in an LRU (least recently used), so the person or
+channel you have talked to least recently will be replaced.
 
 The number of slots in use will be kept through a reconnection, though the
 information in those slots will be dropped.  However, you will always
@@ -35,9 +37,10 @@ receive one free slot on a reconnection.  Other servers using this mechanism
 will also be made aware of details about slots.
 
 Target change does not apply if you are opped or voiced in a channel, and
-you are messaging a client within that channel.  This can be done explicitly
-using the CNOTICE and CPRIVMSG commands, see /quote help cnotice and /quote
-help cprivmsg, but is also implicit in a normal /msg, /notice or /invite.
+you are messaging that channel or a client within that channel.  The latter
+can be done explicitly using the CNOTICE and CPRIVMSG commands, see
+/quote help cnotice and /quote help cprivmsg, but is also implicit in a
+normal /msg, /notice or /invite.
 
 -- 
 $Id: tgchange.txt 6 2005-09-10 01:02:21Z nenolod $
diff --git a/include/s_conf.h b/include/s_conf.h
index 03e0d5fa..b3e60951 100644
--- a/include/s_conf.h
+++ b/include/s_conf.h
@@ -246,6 +246,7 @@ struct config_channel_entry
 	int kick_on_split_riding;
 	int only_ascii_channels;
 	int resv_forcepart;
+	int channel_target_change;
 };
 
 struct config_server_hide
diff --git a/include/tgchange.h b/include/tgchange.h
index e3e4fcce..b2cc684c 100644
--- a/include/tgchange.h
+++ b/include/tgchange.h
@@ -30,6 +30,8 @@
 struct Channel *find_allowing_channel(struct Client *source_p, struct Client *target_p);
 /* checks if source_p is allowed to send to target_p */
 int add_target(struct Client *source_p, struct Client *target_p);
+/* checks if source_p is allowed to send to chptr */
+int add_channel_target(struct Client *source_p, struct Channel *chptr);
 /* allows source_p to send to target_p */
 void add_reply_target(struct Client *source_p, struct Client *target_p);
 
diff --git a/modules/core/m_message.c b/modules/core/m_message.c
index 636bb8dc..cb4d51d1 100644
--- a/modules/core/m_message.c
+++ b/modules/core/m_message.c
@@ -510,6 +510,14 @@ msg_channel(int p_or_n, const char *command,
 	/* chanops and voiced can flood their own channel with impunity */
 	if((result = can_send(chptr, source_p, NULL)))
 	{
+		if(result != CAN_SEND_OPV && MyClient(source_p) &&
+		   !IsOper(source_p) &&
+		   !add_channel_target(source_p, chptr))
+		{
+			sendto_one(source_p, form_str(ERR_TARGCHANGE),
+				   me.name, source_p->name, chptr->chname);
+			return;
+		}
 		if(result == CAN_SEND_OPV ||
 		   !flood_attack_channel(p_or_n, source_p, chptr, chptr->chname))
 		{
@@ -533,6 +541,13 @@ msg_channel(int p_or_n, const char *command,
 			(!(chptr->mode.mode & MODE_NOPRIVMSGS) ||
 			 IsMember(source_p, chptr)))
 	{
+		if(MyClient(source_p) && !IsOper(source_p) &&
+		   !add_channel_target(source_p, chptr))
+		{
+			sendto_one(source_p, form_str(ERR_TARGCHANGE),
+				   me.name, source_p->name, chptr->chname);
+			return;
+		}
 		if(!flood_attack_channel(p_or_n, source_p, chptr, chptr->chname))
 		{
 			sendto_channel_opmod(client_p, source_p, chptr,
diff --git a/modules/m_topic.c b/modules/m_topic.c
index 55be75ae..cf7b2853 100644
--- a/modules/m_topic.c
+++ b/modules/m_topic.c
@@ -39,6 +39,7 @@
 #include "parse.h"
 #include "modules.h"
 #include "packet.h"
+#include "tgchange.h"
 
 static int m_topic(struct Client *, struct Client *, int, const char **);
 static int ms_topic(struct Client *, struct Client *, int, const char **);
@@ -114,6 +115,15 @@ m_topic(struct Client *client_p, struct Client *source_p, int parc, const char *
 			return 0;
 		}
 
+		if(MyClient(source_p) && !is_chanop_voiced(msptr) &&
+				!IsOper(source_p) &&
+				!add_channel_target(source_p, chptr))
+		{
+			sendto_one(source_p, form_str(ERR_TARGCHANGE),
+				   me.name, source_p->name, chptr->chname);
+			return 0;
+		}
+
 		if(((chptr->mode.mode & MODE_TOPICLIMIT) == 0 ||
 					is_chanop(msptr)) &&
 				(!MyClient(source_p) ||
diff --git a/src/newconf.c b/src/newconf.c
index 1769d35d..6e4fca20 100644
--- a/src/newconf.c
+++ b/src/newconf.c
@@ -2199,6 +2199,7 @@ static struct ConfEntry conf_channel_table[] =
 	{ "use_knock",		CF_YESNO, NULL, 0, &ConfigChannel.use_knock		},
 	{ "use_forward",	CF_YESNO, NULL, 0, &ConfigChannel.use_forward		},
 	{ "resv_forcepart",     CF_YESNO, NULL, 0, &ConfigChannel.resv_forcepart	},
+	{ "channel_target_change", CF_YESNO, NULL, 0, &ConfigChannel.channel_target_change	},
 	{ "\0", 		0, 	  NULL, 0, NULL }
 };
 
diff --git a/src/s_conf.c b/src/s_conf.c
index 48192c9e..f75472b0 100644
--- a/src/s_conf.c
+++ b/src/s_conf.c
@@ -772,6 +772,7 @@ set_default_conf(void)
 	ConfigChannel.no_join_on_split = NO;
 	ConfigChannel.no_create_on_split = YES;
 	ConfigChannel.resv_forcepart = YES;
+	ConfigChannel.channel_target_change = YES;
 
 	ConfigServerHide.flatten_links = 0;
 	ConfigServerHide.links_delay = 300;
diff --git a/src/tgchange.c b/src/tgchange.c
index 387b1917..d06ad41f 100644
--- a/src/tgchange.c
+++ b/src/tgchange.c
@@ -30,6 +30,8 @@
 #include "hash.h"
 #include "s_newconf.h"
 
+static int add_hashed_target(struct Client *source_p, uint32_t hashv);
+
 struct Channel *
 find_allowing_channel(struct Client *source_p, struct Client *target_p)
 {
@@ -48,9 +50,7 @@ find_allowing_channel(struct Client *source_p, struct Client *target_p)
 int
 add_target(struct Client *source_p, struct Client *target_p)
 {
-	int i, j;
 	uint32_t hashv;
-	uint32_t *targets;
 
 	/* can msg themselves or services without using any target slots */
 	if(source_p == target_p || IsService(target_p))
@@ -65,6 +65,24 @@ add_target(struct Client *source_p, struct Client *target_p)
 		return 1;
 
 	hashv = fnv_hash_upper((const unsigned char *)use_id(target_p), 32);
+	return add_hashed_target(source_p, hashv);
+}
+
+int
+add_channel_target(struct Client *source_p, struct Channel *chptr)
+{
+	uint32_t hashv;
+
+	hashv = fnv_hash_upper((const unsigned char *)chptr->chname, 32);
+	return add_hashed_target(source_p, hashv);
+}
+
+static int
+add_hashed_target(struct Client *source_p, uint32_t hashv)
+{
+	int i, j;
+	uint32_t *targets;
+
 	targets = source_p->localClient->targets;
 
 	/* check for existing target, and move it to the head */