From c3f625a02cd891d526a42085d40121418cda27d4 Mon Sep 17 00:00:00 2001 From: Paras Singh Jain Date: Tue, 14 Apr 2015 21:50:08 +0530 Subject: [PATCH] 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: Paras Singh Jain --- net/netfilter/nf_conntrack_irc.c | 461 ++++++++++++++++++++++++++----- 1 file changed, 391 insertions(+), 70 deletions(-) diff --git a/net/netfilter/nf_conntrack_irc.c b/net/netfilter/nf_conntrack_irc.c index e40988a2f22f..ec859a23acf3 100644 --- a/net/netfilter/nf_conntrack_irc.c +++ b/net/netfilter/nf_conntrack_irc.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,18 @@ static unsigned int max_dcc_channels = 8; 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, @@ -59,7 +72,7 @@ static const char *const dccprotos[] = { }; #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 @@ -69,6 +82,20 @@ static const char *const dccprotos[] = { * 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) { @@ -103,6 +130,217 @@ static int parse_dcc(char *data, const char *data_end, __be32 *ip, 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 check_for_motd_message_from_IRC_server(char *data, const char *data_limit, + struct nf_conntrack_tuple *tuple, + struct nf_conn *ct, + int dir, struct irc_client_info *temp) +{ + 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; + return NF_ACCEPT; + } + return NF_DROP; +} + +static void process_nickname(struct irc_client_info *client_info, int i, + char *data) +{ + if (client_info && client_info->nickname) { + kfree(client_info->nickname); + client_info->nickname = kmalloc(i, GFP_ATOMIC); + if (client_info->nickname) { + client_info->nickname_len = i; + memcpy(client_info->nickname, data, + client_info->nickname_len); + client_info->conn_to_server = true; + } else { + list_del(&client_info->ptr); + no_of_clients--; + kfree(client_info); + } + } +} + +static int check_for_nick_message_from_IRC_server(char *data, const char *data_limit, + char *nick_end, + struct nf_conntrack_tuple *tuple, + struct nf_conn *ct, + int dir, struct irc_client_info *temp) +{ + int i; + + while (data < data_limit - (6 + MINLENNICK)) { + if (memcmp(data, "NICK :", 6)) { + data++; + continue; + } + data += 6; + nick_end = data; + i = 0; + while ((*nick_end != 0x0d) && + (*(nick_end + 1) != '\n')) { + nick_end++; + i++; + } + tuple = &ct->tuplehash[!dir].tuple; + temp = search_client_by_ip(tuple); + process_nickname(temp, i, data); + /*NICK during registration*/ + return NF_ACCEPT; + } + return NF_DROP; +} + +static bool parse_nick_command_from_client(char *data, const char *data_limit, + struct nf_conn *ct, int dir, int *ret) +{ + while (data < data_limit - (5 + MINLENNICK)) { + if (memcmp(data, "NICK ", 5)) { + data++; + continue; + } + data += 5; + *ret = handle_nickname(ct, dir, data); + return true; + } + return false; +} + +static bool parse_quit(char *data, const char *data_limit, + struct nf_conntrack_tuple *tuple, struct nf_conn *ct, + int dir, struct irc_client_info *temp) +{ + 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); + } + return true; + } + return false; +} + static int help(struct sk_buff *skb, unsigned int protoff, struct nf_conn *ct, enum ip_conntrack_info ctinfo) { @@ -111,20 +349,18 @@ static int help(struct sk_buff *skb, unsigned int protoff, const struct tcphdr *th; struct tcphdr _tcph; const char *data_limit; - char *data, *ib_ptr; + char *data, *ib_ptr, *for_print, *nick_end = NULL; int dir = CTINFO2DIR(ctinfo); struct nf_conntrack_expect *exp; - struct nf_conntrack_tuple *tuple; + struct nf_conntrack_tuple *tuple = NULL; __be32 dcc_ip; u_int16_t dcc_port; __be16 port; 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; + typeof(nf_nat_irc_hook) nf_nat_irc __kernel; + struct irc_client_info *temp = NULL; + 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) @@ -148,78 +384,162 @@ static int help(struct sk_buff *skb, unsigned int protoff, 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) + */ + + if (check_for_motd_message_from_IRC_server(data, data_limit, tuple, + ct, dir, temp) == NF_ACCEPT) { + 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)); + data = ib_ptr; + data_limit = ib_ptr + skb->len - dataoff; - 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 + */ + + if (check_for_nick_message_from_IRC_server(data, data_limit, nick_end, tuple, + ct, dir, temp) == NF_ACCEPT) { + 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; + if (parse_nick_command_from_client(data, data_limit, ct, dir, &ret)) + goto out; + + data = ib_ptr; + if (parse_quit(data, data_limit, tuple, ct, dir, 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 += strlen(dccprotos[i]); - pr_debug("DCC %s detected\n", dccprotos[i]); + 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)); - /* 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; - } + 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]); - pr_debug("DCC bound ip/port: %pI4:%u\n", - &dcc_ip, dcc_port); + /* 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; + } - /* 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; - } + pr_debug("DCC bound ip/port: %pI4:%u\n", + &dcc_ip, dcc_port); - exp = nf_ct_expect_alloc(ct); - if (exp == NULL) { - nf_ct_helper_log(skb, ct, - "cannot alloc expectation"); - ret = NF_DROP; + /* 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) + != 0) { + nf_ct_helper_log(skb, ct, + "cannot add expectation"); + ret = NF_DROP; + } + nf_ct_expect_put(exp); 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); - 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) != 0) { - nf_ct_helper_log(skb, ct, - "cannot add expectation"); - ret = NF_DROP; - } - nf_ct_expect_put(exp); - goto out; } } out: @@ -268,7 +588,8 @@ static int __init nf_conntrack_irc_init(void) kfree(irc_buffer); return ret; } - + no_of_clients = 0; + INIT_LIST_HEAD(&client_list.ptr); return 0; }