net: netfilter: IRC DCC for private clients

Added support for IRC DCC for private clients
by avoiding NAT if the DCC client is a
private client.

Change-Id: I786724e82768317e4f511733bc9247e81d2b60a6
Signed-off-by: Mohammed Javid <mjavid@codeaurora.org>
Acked-by: Sudhanshu Singh <sudhsing@qti.qualcomm.com>
diff --git a/net/netfilter/nf_conntrack_irc.c b/net/netfilter/nf_conntrack_irc.c
index 1972a14..dd26e88 100644
--- a/net/netfilter/nf_conntrack_irc.c
+++ b/net/netfilter/nf_conntrack_irc.c
@@ -19,6 +19,7 @@
 #include <linux/tcp.h>
 #include <linux/netfilter.h>
 #include <linux/slab.h>
+#include <linux/list.h>
 
 #include <net/netfilter/nf_conntrack.h>
 #include <net/netfilter/nf_conntrack_expect.h>
@@ -32,6 +33,18 @@
 static unsigned int dcc_timeout __read_mostly = 300;
 /* This is slow, but it's simple. --RR */
 static char *irc_buffer;
+struct irc_client_info {
+	char *nickname;
+	bool conn_to_server;
+	int nickname_len;
+	__be32 server_ip;
+	__be32 client_ip;
+	struct list_head ptr;
+	};
+
+static struct irc_client_info client_list;
+
+static unsigned int no_of_clients;
 static DEFINE_SPINLOCK(irc_buffer_lock);
 
 unsigned int (*nf_nat_irc_hook)(struct sk_buff *skb,
@@ -61,7 +74,7 @@
 };
 
 #define MINMATCHLEN	5
-
+#define MINLENNICK	1
 /* tries to get the ip_addr and port out of a dcc command
  * return value: -1 on failure, 0 on success
  *	data		pointer to first byte of DCC command data
@@ -71,6 +84,23 @@
  *	ad_beg_p	returns pointer to first byte of addr data
  *	ad_end_p	returns pointer to last byte of addr data
  */
+static struct irc_client_info *search_client_by_ip
+(
+	struct nf_conntrack_tuple *tuple
+)
+{
+	struct irc_client_info *temp, *ret = NULL;
+	struct list_head *obj_ptr, *prev_obj_ptr;
+
+	list_for_each_safe(obj_ptr, prev_obj_ptr, &client_list.ptr) {
+		temp = list_entry(obj_ptr, struct irc_client_info, ptr);
+		if ((temp->client_ip == tuple->src.u3.ip) &&
+		    (temp->server_ip == tuple->dst.u3.ip))
+			ret = temp;
+	}
+	return ret;
+}
+
 static int parse_dcc(char *data, const char *data_end, __be32 *ip,
 		     u_int16_t *port, char **ad_beg_p, char **ad_end_p)
 {
@@ -105,6 +135,105 @@
 	return 0;
 }
 
+static bool mangle_ip(struct nf_conn *ct,
+		      int dir, char *nick_start)
+{
+	char *nick_end;
+	struct nf_conntrack_tuple *tuple;
+	struct irc_client_info *temp;
+	struct list_head *obj_ptr, *prev_obj_ptr;
+
+	tuple = &ct->tuplehash[dir].tuple;
+	nick_end = nick_start;
+	while (*nick_end != ' ')
+		nick_end++;
+	list_for_each_safe(obj_ptr, prev_obj_ptr,
+			   &client_list.ptr) {
+		temp = list_entry(obj_ptr,
+				  struct irc_client_info, ptr);
+		/*If it is an internal client,
+		 *do not mangle the DCC Server IP
+		 */
+		if ((temp->server_ip == tuple->dst.u3.ip) &&
+		    (temp->nickname_len == nick_end - nick_start) &&
+		    (memcmp(nick_start, temp->nickname,
+		    temp->nickname_len) == 0))
+			return false;
+	}
+	return true;
+}
+
+static int handle_nickname(struct nf_conn *ct,
+			   int dir, char *nick_start)
+{
+	char *nick_end;
+	struct nf_conntrack_tuple *tuple;
+	struct irc_client_info *temp;
+	int i, j;
+	bool add_entry = true;
+
+	nick_end = nick_start;
+	i = 0;
+	while (*nick_end != '\n') {
+		nick_end++;
+		i++;
+	}
+	tuple = &ct->tuplehash[dir].tuple;
+	/*Check if the entry is already
+	 * present for that client
+	 */
+	temp = search_client_by_ip(tuple);
+	if (temp) {
+		add_entry = false;
+		/*Update nickname if the client is not already
+		 * connected to the server.If the client is
+		 * connected, wait for server to confirm
+		 * if nickname is valid
+		 */
+		if (!temp->conn_to_server) {
+			kfree(temp->nickname);
+			temp->nickname =
+				kmalloc(i, GFP_ATOMIC);
+			if (temp->nickname) {
+				temp->nickname_len = i;
+				memcpy(&temp->nickname,
+				       nick_start, temp->nickname_len);
+			} else {
+				list_del(&temp->ptr);
+				no_of_clients--;
+				kfree(temp);
+			}
+		}
+	}
+	/*Add client entry if not already present*/
+	if (add_entry) {
+		j = sizeof(struct irc_client_info);
+		temp = kmalloc(j, GFP_ATOMIC);
+		if (temp) {
+			no_of_clients++;
+			tuple = &ct->tuplehash[dir].tuple;
+			temp->nickname_len = i;
+			temp->nickname =
+				kmalloc(temp->nickname_len, GFP_ATOMIC);
+			if (!temp->nickname) {
+				kfree(temp);
+				return NF_DROP;
+			}
+			memcpy(temp->nickname, nick_start,
+			       temp->nickname_len);
+			memcpy(&temp->client_ip,
+			       &tuple->src.u3.ip, sizeof(__be32));
+			memcpy(&temp->server_ip,
+			       &tuple->dst.u3.ip, sizeof(__be32));
+			temp->conn_to_server = false;
+			list_add(&temp->ptr,
+				 &client_list.ptr);
+		} else {
+			return NF_DROP;
+		}
+	}
+	return NF_ACCEPT;
+}
 static int help(struct sk_buff *skb, unsigned int protoff,
 		struct nf_conn *ct, enum ip_conntrack_info ctinfo)
 {
@@ -113,7 +242,7 @@
 	const struct tcphdr *th;
 	struct tcphdr _tcph;
 	const char *data_limit;
-	char *data, *ib_ptr;
+	char *data, *ib_ptr, *for_print, *nick_end;
 	int dir = CTINFO2DIR(ctinfo);
 	struct nf_conntrack_expect *exp;
 	struct nf_conntrack_tuple *tuple;
@@ -123,10 +252,8 @@
 	int i, ret = NF_ACCEPT;
 	char *addr_beg_p, *addr_end_p;
 	typeof(nf_nat_irc_hook) nf_nat_irc;
-
-	/* If packet is coming from IRC server */
-	if (dir == IP_CT_DIR_REPLY)
-		return NF_ACCEPT;
+	struct irc_client_info *temp;
+	bool mangle = true;
 
 	/* Until there's been traffic both ways, don't look in packets. */
 	if (ctinfo != IP_CT_ESTABLISHED && ctinfo != IP_CT_ESTABLISHED_REPLY)
@@ -150,80 +277,223 @@
 	data = ib_ptr;
 	data_limit = ib_ptr + skb->len - dataoff;
 
-	/* strlen("\1DCC SENT t AAAAAAAA P\1\n")=24
-	 * 5+MINMATCHLEN+strlen("t AAAAAAAA P\1\n")=14 */
-	while (data < data_limit - (19 + MINMATCHLEN)) {
-		if (memcmp(data, "\1DCC ", 5)) {
-			data++;
-			continue;
+	/* If packet is coming from IRC server
+	 * parse the packet for different type of
+	 * messages (MOTD,NICK etc) and process
+	 * accordingly
+	 */
+	if (dir == IP_CT_DIR_REPLY) {
+		/* strlen("NICK xxxxxx")
+		 * 5+strlen("xxxxxx")=1 (minimum length of nickname)
+		 */
+
+		while (data < data_limit - 6) {
+			if (memcmp(data, " MOTD ", 6)) {
+				data++;
+				continue;
+			}
+			/* MOTD message signifies successful
+			 * registration with server
+			 */
+			tuple = &ct->tuplehash[!dir].tuple;
+			temp = search_client_by_ip(tuple);
+			if (temp && !temp->conn_to_server)
+				temp->conn_to_server = true;
+			ret = NF_ACCEPT;
+			goto out;
 		}
-		data += 5;
-		/* we have at least (19+MINMATCHLEN)-5 bytes valid data left */
 
-		iph = ip_hdr(skb);
-		pr_debug("DCC found in master %pI4:%u %pI4:%u\n",
-			 &iph->saddr, ntohs(th->source),
-			 &iph->daddr, ntohs(th->dest));
-
-		for (i = 0; i < ARRAY_SIZE(dccprotos); i++) {
-			if (memcmp(data, dccprotos[i], strlen(dccprotos[i]))) {
-				/* no match */
+		/* strlen("NICK :xxxxxx")
+		 * 6+strlen("xxxxxx")=1 (minimum length of nickname)
+		 * Parsing the server reply to get nickname
+		 * of the client
+		 */
+		data = ib_ptr;
+		data_limit = ib_ptr + skb->len - dataoff;
+		while (data < data_limit - (6 + MINLENNICK)) {
+			if (memcmp(data, "NICK :", 6)) {
+				data++;
 				continue;
 			}
-			data += strlen(dccprotos[i]);
-			pr_debug("DCC %s detected\n", dccprotos[i]);
-
-			/* we have at least
-			 * (19+MINMATCHLEN)-5-dccprotos[i].matchlen bytes valid
-			 * data left (== 14/13 bytes) */
-			if (parse_dcc(data, data_limit, &dcc_ip,
-				       &dcc_port, &addr_beg_p, &addr_end_p)) {
-				pr_debug("unable to parse dcc command\n");
-				continue;
-			}
-
-			pr_debug("DCC bound ip/port: %pI4:%u\n",
-				 &dcc_ip, dcc_port);
-
-			/* dcc_ip can be the internal OR external (NAT'ed) IP */
-			tuple = &ct->tuplehash[dir].tuple;
-			if (tuple->src.u3.ip != dcc_ip &&
-			    tuple->dst.u3.ip != dcc_ip) {
-				net_warn_ratelimited("Forged DCC command from %pI4: %pI4:%u\n",
-						     &tuple->src.u3.ip,
-						     &dcc_ip, dcc_port);
-				continue;
-			}
-
-			exp = nf_ct_expect_alloc(ct);
-			if (exp == NULL) {
-				nf_ct_helper_log(skb, ct,
-						 "cannot alloc expectation");
-				ret = NF_DROP;
-				goto out;
+			data += 6;
+			nick_end = data;
+			i = 0;
+			while ((*nick_end != 0x0d) &&
+			       (*(nick_end + 1) != '\n')) {
+				nick_end++;
+				i++;
 			}
 			tuple = &ct->tuplehash[!dir].tuple;
-			port = htons(dcc_port);
-			nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT,
-					  tuple->src.l3num,
-					  NULL, &tuple->dst.u3,
-					  IPPROTO_TCP, NULL, &port);
-
-			nf_nat_irc = rcu_dereference(nf_nat_irc_hook);
-			if (nf_nat_irc && ct->status & IPS_NAT_MASK)
-				ret = nf_nat_irc(skb, ctinfo, protoff,
-						 addr_beg_p - ib_ptr,
-						 addr_end_p - addr_beg_p,
-						 exp);
-			else if (nf_ct_expect_related(exp) != 0) {
-				nf_ct_helper_log(skb, ct,
-						 "cannot add expectation");
-				ret = NF_DROP;
+			temp = search_client_by_ip(tuple);
+			if (temp && temp->nickname) {
+				kfree(temp->nickname);
+				temp->nickname = kmalloc(i, GFP_ATOMIC);
+				if (temp->nickname) {
+					temp->nickname_len = i;
+					memcpy(temp->nickname, data,
+					       temp->nickname_len);
+					temp->conn_to_server = true;
+				} else {
+					list_del(&temp->ptr);
+					no_of_clients--;
+					kfree(temp);
+					ret = NF_ACCEPT;
+				}
 			}
-			nf_ct_expect_put(exp);
+			/*NICK during registration*/
+			ret = NF_ACCEPT;
 			goto out;
 		}
 	}
+
+	else{
+		/*Parsing NICK command from client to create an entry
+		 * strlen("NICK xxxxxx")
+		 * 5+strlen("xxxxxx")=1 (minimum length of nickname)
+		 */
+		data = ib_ptr;
+		data_limit = ib_ptr + skb->len - dataoff;
+		while (data < data_limit - (5 + MINLENNICK)) {
+			if (memcmp(data, "NICK ", 5)) {
+				data++;
+				continue;
+			}
+			data += 5;
+			ret = handle_nickname(ct, dir, data);
+			goto out;
+		}
+
+		data = ib_ptr;
+		while (data < data_limit - 6) {
+			if (memcmp(data, "QUIT :", 6)) {
+				data++;
+				continue;
+			}
+			/* Parsing QUIT to free the list entry
+			 */
+			tuple = &ct->tuplehash[dir].tuple;
+			temp = search_client_by_ip(tuple);
+			if (temp) {
+				list_del(&temp->ptr);
+				no_of_clients--;
+				kfree(temp->nickname);
+				kfree(temp);
+			}
+			ret = NF_ACCEPT;
+			goto out;
+		}
+		/* strlen("\1DCC SENT t AAAAAAAA P\1\n")=24
+		 * 5+MINMATCHLEN+strlen("t AAAAAAAA P\1\n")=14
+		 */
+		data = ib_ptr;
+		while (data < data_limit - (19 + MINMATCHLEN)) {
+			if (memcmp(data, "\1DCC ", 5)) {
+				data++;
+				continue;
+			}
+			data += 5;
+			/* we have at least (19+MINMATCHLEN)-5
+			 *bytes valid data left
+			 */
+			iph = ip_hdr(skb);
+			pr_debug("DCC found in master %pI4:%u %pI4:%u\n",
+				 &iph->saddr, ntohs(th->source),
+				 &iph->daddr, ntohs(th->dest));
+
+			for (i = 0; i < ARRAY_SIZE(dccprotos); i++) {
+				if (memcmp(data, dccprotos[i],
+					   strlen(dccprotos[i]))) {
+					/* no match */
+					continue;
+				}
+				data += strlen(dccprotos[i]);
+				pr_debug("DCC %s detected\n", dccprotos[i]);
+
+				/* we have at least
+				 * (19+MINMATCHLEN)-5-dccprotos[i].matchlen
+				 *bytes valid data left (== 14/13 bytes)
+				 */
+				if (parse_dcc(data, data_limit, &dcc_ip,
+					      &dcc_port, &addr_beg_p,
+					      &addr_end_p)) {
+					pr_debug("unable to parse dcc command\n");
+					continue;
+				}
+
+				pr_debug("DCC bound ip/port: %pI4:%u\n",
+					 &dcc_ip, dcc_port);
+
+				/* dcc_ip can be the internal OR
+				 *external (NAT'ed) IP
+				 */
+				tuple = &ct->tuplehash[dir].tuple;
+				if (tuple->src.u3.ip != dcc_ip &&
+				    tuple->dst.u3.ip != dcc_ip) {
+					net_warn_ratelimited("Forged DCC command from %pI4: %pI4:%u\n",
+							     &tuple->src.u3.ip,
+							     &dcc_ip, dcc_port);
+					continue;
+				}
+
+				exp = nf_ct_expect_alloc(ct);
+				if (!exp) {
+					nf_ct_helper_log(skb, ct,
+							 "cannot alloc expectation");
+					ret = NF_DROP;
+					goto out;
+				}
+				tuple = &ct->tuplehash[!dir].tuple;
+				port = htons(dcc_port);
+				nf_ct_expect_init(exp,
+						  NF_CT_EXPECT_CLASS_DEFAULT,
+						  tuple->src.l3num,
+						  NULL, &tuple->dst.u3,
+						  IPPROTO_TCP, NULL, &port);
+
+				nf_nat_irc = rcu_dereference(nf_nat_irc_hook);
+
+				tuple = &ct->tuplehash[dir].tuple;
+				for_print = ib_ptr;
+				/* strlen("PRIVMSG xxxx :\1DCC
+				 *SENT t AAAAAAAA P\1\n")=26
+				 * 8+strlen(xxxx) = 1(min length)+7+
+				 *MINMATCHLEN+strlen("t AAAAAAAA P\1\n")=14
+				 *Parsing DCC command to get client name and
+				 *check whether it is an internal client
+				 */
+				while (for_print <
+				       data_limit - (25 + MINMATCHLEN)) {
+					if (memcmp(for_print, "PRIVMSG ", 8)) {
+						for_print++;
+						continue;
+					}
+					for_print += 8;
+					mangle = mangle_ip(ct,
+							   dir, for_print);
+					break;
+				}
+				if (mangle &&
+				    nf_nat_irc &&
+				    ct->status & IPS_NAT_MASK)
+					ret = nf_nat_irc(skb, ctinfo,
+							 protoff,
+							 addr_beg_p - ib_ptr,
+							 addr_end_p
+							 - addr_beg_p,
+							 exp);
+
+				else if (mangle &&
+					 nf_ct_expect_related(exp)
+					 != 0) {
+					nf_ct_helper_log(skb, ct,
+							 "cannot add expectation");
+					ret = NF_DROP;
+				}
+				nf_ct_expect_put(exp);
+				goto out;
+			}
+		}
+	}
  out:
 	spin_unlock_bh(&irc_buffer_lock);
 	return ret;
@@ -266,7 +536,8 @@
 		kfree(irc_buffer);
 		return ret;
 	}
-
+	no_of_clients = 0;
+	INIT_LIST_HEAD(&client_list.ptr);
 	return 0;
 }