/* Copyright (C) 2003-2008 Eric Hsiao <erichs0608@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

/* Kernel module implementing a webcategory set type as a bitmap */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/skbuff.h>
#include <linux/errno.h>
#include <asm/uaccess.h>
#include <asm/bitops.h>
#include <linux/spinlock.h>

#include <net/ip.h>
#include <net/route.h>
#include <net/tcp.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/kthread.h>
#include <linux/err.h>
#include <linux/imq.h>
#include <linux/ksocket.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/random.h>
#include <linux/jhash.h>
#include <linux/time.h>
#include <linux/netfilter.h>

#include <linux/netfilter_ipv4/ip_set_webcategory.h>
#include <linux/file.h> 
#include <linux/fs.h>

#define CT_COMID 1
#define CT_PVER "1.0"
#define CT_KEY "0001I001Q1061K177I08"

#define HOST_HASH_BITS 10
#define HOST_HASH_SIZE (1<<HOST_HASH_BITS)
#define HOST_HASH_MASK (HOST_HASH_SIZE-1)
#define HOST_HASH_LOCK_BITS 5
#define HOST_HASH_LOCK_SIZE (1<<HOST_HASH_LOCK_BITS)
#define HOST_HASH_LOCK_MASK (HOST_HASH_LOCK_SIZE-1)
#define HOST_HASH_MAX_LEAF_SIZE 4

#define MAX_WEBCATEGORY_THREAD 1
#define MAX_HOST_LENGTH 256
#define MAX_REQUEST_BUFFER_LEN 768
#define MAX_WEBCATEGORY_GUEUE_SIZE 2048
#define tcp_v4_check(tcph, tcph_sz, s, d, csp) tcp_v4_check((tcph_sz), (s), (d), (csp))
#define NF_WCF_QMASK 0xffffff00
#define NF_WCF_FLAG_BIT 8
#define NF_QUEUE_NR_WCF(x, y) ((((x << NF_VERDICT_QBITS) | (y << NF_WCF_FLAG_BIT)) & NF_WCF_QMASK) | NF_QUEUE)

extern int ip_finish_output(struct sk_buff *skb);
mm_segment_t oldfs;

struct webcategory_kthread_data {
	struct task_struct *task;
	wait_queue_head_t wait_queue;
	ksocket_t socket;
};

struct webcategory_queue {
	unsigned int index;
	unsigned char flag;
	struct sk_buff *skb;
	struct list_head list;
};

struct webcategory_queue_head {
	__u32 qlen;
	spinlock_t lock;
	struct list_head list;
};

struct host_hash_node {
	char host[MAX_HOST_LENGTH];
	unsigned int category;
	struct hlist_node list;
};

struct host_hash {
	u32 initval;
	rwlock_t lock[HOST_HASH_LOCK_SIZE];
	struct hlist_head list[HOST_HASH_SIZE];
};

static int status = 0;
static int status_tmp = 0;
static char ct_param_address[20]="134.159.159.87";
static char ct_info[16]="";
static char ct_info1[16]="";
static int ct_info2 = 0;
static char ct_sn_tmp[16]="";
static char ct_sock_address[16];
static struct timeval tv;

static struct webcategory_kthread_data global_webcategory_kthread_data[MAX_WEBCATEGORY_THREAD];
static struct webcategory_queue_head global_webcategory_queue_head;
static struct host_hash global_host_hash;
static struct sockaddr_in ct_addr;


static inline void host_hash_init(struct host_hash *head)
{
	unsigned int i;
	for (i = 0; i < HOST_HASH_LOCK_SIZE; i++) {
		rwlock_init(&head->lock[i]);
	}
	for (i = 0; i < HOST_HASH_SIZE; i++) {
		INIT_HLIST_HEAD(&head->list[i]);
	}
	head->initval = random32();
}

static int host_hash_get(struct host_hash *head, char *host, int *category)
{
	struct host_hash_node *node;
	struct hlist_head *h;
	struct hlist_node *n;
	u32 hash;
	u32 hash_lock;
	int ret = 1;

	if(ct_info2 == 9){
		printk(IPFLOG_ALERT "[CSM]host_hash_get : Disable cache !! \n");
		return 1;
	}	
	hash = jhash2((u32 *)host, 16, head->initval) & HOST_HASH_MASK;
	hash_lock = hash & HOST_HASH_LOCK_MASK;
	h = &head->list[hash];
	read_lock_bh(&head->lock[hash_lock]);
	hlist_for_each(n, h) {
		node = hlist_entry(n, struct host_hash_node, list);
		if (strcmp(host, node->host) == 0) {
			*category = node->category;
			if(ct_info2 > 1){
				printk(IPFLOG_ALERT "[CSM]host_hash_get match ! category : %d node->host : %s \n",*category,node->host);
			}
			ret = 0;
			goto finish;
		}
	}
finish:
	read_unlock_bh(&head->lock[hash_lock]);
	return ret;
}

static void host_hash_set(struct host_hash *head, char *host, int category)
{
	struct host_hash_node *node;
	struct hlist_head *h;
	struct hlist_node *n;
	u32 hash;
	u32 hash_lock;
	int hash_size;

	if(ct_info2 == 9){
		printk(IPFLOG_ALERT "[CSM]host_hash_set : Disable cache !! \n");
		return;
	}
	hash = jhash2((u32 *)host, 16, head->initval) & HOST_HASH_MASK;
	hash_lock = hash & HOST_HASH_LOCK_MASK;
	h = &head->list[hash];
	write_lock_bh(&head->lock[hash_lock]);
	hash_size = 0;
	hlist_for_each(n, h) {
		node = hlist_entry(n, struct host_hash_node, list);
		if (strcmp(host, node->host) == 0) {
			goto finish;
		}
		hash_size++;
	}
	if (hash_size < HOST_HASH_MAX_LEAF_SIZE) {
		node = kmalloc(sizeof(struct host_hash_node), GFP_ATOMIC);
		if (!node) {
			if(ct_info2 > 1){
				printk(IPFLOG_ALERT "[CSM]host_hash_set : alloc memory failed ! \n");
			}
			goto finish;
		}
		memset(node, 0, sizeof(struct host_hash_node));
		node->category = category;
		strcpy(node->host, host);
		hlist_add_head(&node->list, h);
	} else {
		if(ct_info2 > 1){
			printk(IPFLOG_ALERT "[CSM]host_hash_set : leaf size is full! \n");
		}
		hlist_del(&node->list);
		memset(node, 0, sizeof(struct host_hash_node));
		node->category = category;
		strcpy(node->host, host);
		hlist_add_head(&node->list, h);
	}
finish:
	write_unlock_bh(&head->lock[hash_lock]);
}

static inline void webcategory_queue_head_init(struct webcategory_queue_head *head)
{
	spin_lock_init(&head->lock);
	INIT_LIST_HEAD(&head->list);
	head->qlen = 0;
}

static struct webcategory_queue *webcategory_dequeue(struct webcategory_queue_head *head)
{
	struct webcategory_queue *result = NULL;

	spin_lock_bh(&head->lock);
	if (head->qlen > 0) {
		result = (struct webcategory_queue *) list_entry(head->list.next, struct webcategory_queue, list);
		list_del(head->list.next);
		head->qlen--;
	}
	spin_unlock_bh(&head->lock);
	return result;
}

static int webcategory_enqueue(struct webcategory_queue_head *head, struct webcategory_queue *queue)
{
	int ret;
	struct iphdr *ip = ip_hdr(queue->skb);
	int offset = ntohs(ip->frag_off) & IP_OFFSET;
	
	if (offset != 0) {
		return 0;
	}

	if (skb_is_nonlinear(queue->skb)) {
		return 0;
	}

	spin_lock_bh(&head->lock);
	if(head->qlen >= MAX_WEBCATEGORY_GUEUE_SIZE) {
		ret = 0;
	} else {
		list_add_tail(&queue->list, &head->list);
		head->qlen++;
		ret = 1;
	}
	spin_unlock_bh(&head->lock);
	return ret;
}

static char *strnistr(const char *s, const char *find, size_t slen)
{
	char c, sc;
	size_t len;

	if ((c = *find++) != '\0') 
	{
		len = strlen(find);
		do
		{
			do
			{
				if (slen < 1 || (sc = *s) == '\0')
				{
					return (NULL);
				}
				--slen;
				++s;
			}
			while (sc != c);
			
			if (len > slen)
			{
				return (NULL);
			}
		}
		while (strnicmp(s, find, len) != 0);
      	s--;
	}
	return ((char *)s);
}

static int
webcategory_host(const struct sk_buff *skb, char *host)
{
	struct iphdr *ip = ip_hdr(skb);
	int offset = ntohs(ip->frag_off) & IP_OFFSET;
	if (offset != 0) {
		return 0;
	}

	if (skb_is_nonlinear(skb)) {
		return 0;
	}
	
	if(ip->protocol == IPPROTO_TCP){
		struct tcphdr* tcp_hdr = (struct tcphdr*)(((unsigned char*)ip) + (ip->ihl*4));
		unsigned short payload_offset = (tcp_hdr->doff*4) + (ip->ihl*4);
		unsigned char* packet_data = ((unsigned char*)ip) + payload_offset;
		unsigned short packet_length = ntohs(ip->tot_len) - payload_offset;

		if(packet_length > 10) {
			if(strnicmp((char*)packet_data, "GET ", 4) == 0 || strnicmp((char*)packet_data, "POST ", 5) == 0 || strnicmp((char*)packet_data, "HEAD ", 5) == 0){
				//char path[512] = "";
				//char host[256] = "";
				//char url[784] = "http://";
				//int path_start_index;
				//int path_end_index;
				//int last_header_index;
				//char last_two_buf[2];
				//int end_found;
				char* host_match;
				/* get path portion of URL */
				/*
				path_start_index = (int)(strchr((char*)packet_data, ' ') - (char*)packet_data);
				while( packet_data[path_start_index] == ' ')
				{
					path_start_index++;
				}
				path_end_index= (int)(strchr( (char*)(packet_data+path_start_index), ' ') -  (char*)packet_data);
				if(path_end_index > 0) 
				{
					int path_length = path_end_index-path_start_index;
					path_length = path_length < 512 ? path_length : 511; // prevent overflow
					memcpy(path, packet_data+path_start_index, path_length);
					path[path_length] = '\0';
				}
				*/

				/* get header length */
				/*
				last_header_index = 2;
				memcpy(last_two_buf,(char*)packet_data, 2);
				end_found = 0;
				while(end_found == 0 && last_header_index < packet_length)
				{
					char next = (char)packet_data[last_header_index];
					if(next == '\n')
					{
						end_found = last_two_buf[1] == '\n' || (last_two_buf[0] == '\n' && last_two_buf[1] == '\r') ? 1 : 0;
					}
					if(end_found == 0)
					{
						last_two_buf[0] = last_two_buf[1];
						last_two_buf[1] = next;
						last_header_index++;
					}
				}*/

				/* get host portion of URL */
				host_match = strnistr( (char*)packet_data, "Host:", packet_length);
				if(host_match != NULL)
				{
					int host_end_index = 0;
					host_match = host_match + 5; // character after "Host:"
					while(host_match[0] == ' ')
					{
						host_match++;
					}
					
					while(host_match[host_end_index] != '\n' && host_match[host_end_index] != '\r' && host_match[host_end_index] != ' ' && ((char*)host_match - (char*)packet_data)+host_end_index < packet_length )
					{
						host_end_index++;
					}
					host_end_index = host_end_index < 256 ? host_end_index : 255; // prevent overflow
					memcpy(host, host_match, host_end_index);
					host[host_end_index] = '\0';
					return 1;
				}
				
				//strcpy(url, host);
				//strcat(url, path);
				return 0;
			}
		}	
	}
	return 0;
}

static unsigned int parse_url_code(char *buffer)
{
	char *row = NULL;
	
	while((row = strsep(&buffer, "\r\n")) != NULL)
	{
		if(strncmp(row, "x-ctch-categories:", 18) == 0)
		{
			if(ct_info2 > 1){
				char tmp[32];
				memcpy(tmp,row,31);
				tmp[31] = '\0';
				printk(IPFLOG_ALERT "[CSM]parse_url_code : %s \n",tmp);
			}
			row = row + 18;
			while(row[0] == ' ')
			{
				row++;
			}
			return simple_strtoull(row, NULL, 0);
		}
	}
	return -1;
}

static int
webcategory_commtouch(struct webcategory_kthread_data *webcategory_kthread_data, char *host,unsigned int *category) 
{
	char msg[512];
	memset(msg, 0, 512);
	sprintf(msg,
		"x-ctch-request-id: %d\r\nx-ctch-request-type: classifyurl\r\nx-ctch-pver: %s\r\nx-ctch-key: %s\r\n\r\nx-ctch-url: %s\r\n",
		CT_COMID, CT_PVER, CT_KEY, host);
	if (strcmp(ct_sock_address, ct_param_address) != 0) {
		strcpy(ct_sock_address, ct_param_address);
		ct_addr.sin_addr.s_addr = inet_addr(ct_sock_address);
		//kclose(webcategory_kthread_data->socket);
		//webcategory_kthread_data->socket = ksocket(AF_INET, SOCK_DGRAM, 0);
	}
	webcategory_kthread_data->socket = ksocket(AF_INET, SOCK_DGRAM, 0);
	if (kconnect(webcategory_kthread_data->socket, (struct sockaddr *)&ct_addr, sizeof(struct sockaddr_in)) < 0){
		if(ct_info2 > 0){
			printk(IPFLOG_ALERT "[CSM]commtouch connect failed ! \n");
		}
		kclose(webcategory_kthread_data->socket);
		return 0;
	}
	ksetsockopt(webcategory_kthread_data->socket, SOL_SOCKET, SO_RCVTIMEO, (void *)&tv, sizeof(struct timeval));
	if (ksend(webcategory_kthread_data->socket, msg, strlen(msg), 0) < 0){
		if(ct_info2 > 0){
			printk(IPFLOG_ALERT "[CSM]commtouch send failed ! \n");
		}
		kclose(webcategory_kthread_data->socket);
		return 0;
	}
	if (krecv(webcategory_kthread_data->socket, msg, 512, 0) < 0){
		if(ct_info2 > 0){
			printk(IPFLOG_ALERT "[CSM]commtouch receive failed ! \n");
		}
		kclose(webcategory_kthread_data->socket);
		return 0;
	}
	*category = parse_url_code(msg);
	if(ct_info2 > 1){
		printk(IPFLOG_ALERT "[CSM]commtouch successed ! category : %d \n",*category);
	}
	kclose(webcategory_kthread_data->socket);
	return 1;
	
}

static int 
webcategory_nf_queue(struct sk_buff *skb, struct nf_info *info, unsigned queue_num, void *data)
{
	struct webcategory_queue *queue;
	u32 serialkey = 1;
	u32 ct_status,ct_sn,ct_hash,hash_val;	
	
	if (status == 0)
	{
		status_tmp = status;
		nf_reinject(skb, info, NF_ACCEPT);
		return 1;
	}

	if(strcmp(ct_info,ct_sn_tmp) || status != status_tmp){ //sn changed or status from 0 to 1
		serialkey = system_serial_low ? system_serial_low : 0x7F223344;

		ct_status = (0x7f223344 + 2130 + status);
		ct_sn = simple_strtoul(ct_info, NULL, 0);
		ct_hash = simple_strtoul(ct_info1, NULL, 16);

		if(ct_sn==0 && (serialkey ^ 0x7f223344) == ct_hash  ){
			//printk("\nwcf test mode ... \n");
		}else{

			hash_val = jhash_3words(ct_status,ct_sn ,serialkey,status);

			if(hash_val != ct_hash){
				status = 0;
				memset(ct_info,0,sizeof(ct_info));
				sprintf(ct_info,"00000000");
				nf_reinject(skb, info, NF_ACCEPT);
				return 1;
			}
			memset(ct_sn_tmp,0,sizeof(ct_sn_tmp));
			sprintf(ct_sn_tmp,"%s",ct_info);
			status_tmp = status;
		}
		
	}
	queue = kmalloc(sizeof(struct webcategory_queue), GFP_ATOMIC);
	if (!queue)
		return -1;
	queue->index = queue_num >> NF_WCF_FLAG_BIT;
	queue->flag = queue_num & ~NF_WCF_QMASK;
	queue->skb = skb;
	skb->nf_info = info;
	if (webcategory_enqueue(&global_webcategory_queue_head, queue)==0)
	{
		nf_reinject(skb, info, NF_ACCEPT);
		kfree(queue);
		return 1;
	}
	return 1;
}

void
reset_connect(struct sk_buff *oldskb, char *host, char *page, char *category)
{
       struct sk_buff *nskb;
       struct tcphdr *otcph, *tcph;
       struct rtable *rt;
	struct flowi flow_i;
       unsigned int otcplen;
       u_int16_t tmp_port;
       u_int32_t tmp_addr;
       char msg_info[MAX_REQUEST_BUFFER_LEN]; 
	int msg_len = 0;  //the actual length of msg_info
	u_int8_t tos_value;	
	char buf[MAX_REQUEST_BUFFER_LEN], buf_tmp[MAX_REQUEST_BUFFER_LEN]; 
	struct file *fp;

	oldfs = get_fs(); 
	set_fs(KERNEL_DS);
	fp=filp_open("/www/wcf_block",O_RDONLY,0);
	if (!IS_ERR(fp)) 
	{ 
		memset(buf,0,MAX_REQUEST_BUFFER_LEN);
		memset(buf_tmp,0,MAX_REQUEST_BUFFER_LEN);
		if (fp->f_op && fp->f_op->read){
			if(fp->f_op->read(fp,buf,MAX_REQUEST_BUFFER_LEN, &fp->f_pos) > 0){
				sprintf(buf_tmp, buf,
	  				NIPQUAD(ip_hdr(oldskb)->saddr),
			  		host, page,
	  				category);

					msg_len = sprintf(msg_info, "HTTP/1.1 %s\r\n\r\n%s","403 Forbidden",buf_tmp);					
			}
		}
		filp_close(fp,NULL); 
	}
	set_fs(oldfs); 
	if(msg_len == 0){
		msg_len = sprintf(msg_info, "HTTP/1.1 %s\r\n\r\n<html><head><title>%s</title></head><body><center><h3>%s%u.%u.%u.%u%s%s%s%s%s%s</h3></center></body></html>\n\n",
			  		"403 Forbidden","403 Forbidden", "<br><br><br>The requested Web page<br>from ", NIPQUAD(ip_hdr(oldskb)->saddr),
		  			"<br>to ", host, page,
	  				"<br>that is categorized with [", category,
	  				"]<br>has been blocked by Vigor2130 Web Content Filter.<br><br>Please contact your system administrator for further information.<br><br><br>[Powered by DrayTek]");
	}

       /* IP header checks: fragment, too short. */
       if (ip_hdr(oldskb)->frag_off & htons(IP_OFFSET)
            || oldskb->len < (ip_hdr(oldskb)->ihl<<2) + sizeof(struct tcphdr)){
        if(ct_info2 > 0){
			printk(IPFLOG_ALERT "[CSM]reset_connect : return ... fragment, too short \n");
		}
		return;
       }
       otcph = (struct tcphdr *)((u_int32_t*)ip_hdr(oldskb) + ip_hdr(oldskb)->ihl);
       otcplen = oldskb->len - ip_hdr(oldskb)->ihl*4;

       /* No RST for RST. either FIN for FIN*/
       if (otcph->rst || otcph->fin){
	   	if(ct_info2 > 0){
			printk(IPFLOG_ALERT "[CSM]reset_connect : return ... No RST for RST \n");
		}
		return;
		}

       /* Check checksum. */
       if (tcp_v4_check(otcph, otcplen, ip_hdr(oldskb)->saddr,
                         		ip_hdr(oldskb)->daddr,
                         		csum_partial((char *)otcph, otcplen, 0)) != 0){
        if(ct_info2 > 0){
			printk(IPFLOG_ALERT "[CSM]reset_connect : return ... checksum \n");
		}
		return;
       	}

	   /* Copy skb (even if skb is about to be dropped, we can't just
           clone it because there may be other things, such as tcpdump,
           interested in it) */
       nskb = skb_copy(oldskb, GFP_ATOMIC);
       if (!nskb){
	   	if(ct_info2 > 0){
			printk(IPFLOG_ALERT "[CSM]reset_connect : return ... Copy skb \n");
		}
		return;
       }
        
       /* This packet will not be the same as the other: clear nf fields */
       nf_conntrack_put(nskb->nfct);
       nskb->nfct = NULL;
       //nskb->nfcache = 0;
       nskb->mark = 0;
       tcph = (struct tcphdr *)((u_int32_t*)ip_hdr(nskb) + ip_hdr(nskb)->ihl);

       /* Swap source and dest */
       tmp_addr = ip_hdr(nskb)->saddr;
       ip_hdr(nskb)->saddr = ip_hdr(nskb)->daddr;
       ip_hdr(nskb)->daddr = tmp_addr;
       tmp_port = tcph->source;
       tcph->source = tcph->dest;
       tcph->dest = tmp_port;

       /* change the total length field of ip header */
       tcph->doff = sizeof(struct tcphdr)/4;
	   if(nskb->len > (ip_hdr(nskb)->ihl*4 + sizeof(struct tcphdr) + msg_len)){	   		
	   		skb_trim(nskb, ip_hdr(nskb)->ihl*4 + sizeof(struct tcphdr) + msg_len);
	   }else{			
			if(skb_tailroom(nskb) > (ip_hdr(nskb)->ihl*4 + sizeof(struct tcphdr) + msg_len)- nskb->len)
				skb_put(nskb,(ip_hdr(nskb)->ihl*4 + sizeof(struct tcphdr) + msg_len)- nskb->len);
			else
				skb_put(nskb,skb_tailroom(nskb));
	   }
       ip_hdr(nskb)->tot_len = htons(nskb->len);   

	   /* Set flags FIN ACK*/
       ((u_int8_t *)tcph)[13] = 0;
       tcph->fin = 1;
       tcph->ack = 1; 
       tcph->seq = otcph->ack_seq ? otcph->ack_seq : 1;
       tcph->ack_seq = otcph->seq ? otcph->seq : 1;
        
       tcph->window = 0;
       tcph->urg_ptr = 0;

       /* Add alert mesg here*/
       strncpy( (char *) tcph + 20 , msg_info, msg_len );
        
        
       /* Adjust TCP checksum */
       tcph->check = 0;        
       tcph->check = tcp_v4_check(tcph, nskb->len - ip_hdr(nskb)->ihl*4,
                                   ip_hdr(nskb)->saddr,
                                   ip_hdr(nskb)->daddr,
                                   csum_partial((char *)tcph,
                                                nskb->len - ip_hdr(nskb)->ihl*4, 0));
       /* Adjust IP TTL, DF */
       ip_hdr(nskb)->ttl = MAXTTL;

	/* Set DF, id = 0 */
       ip_hdr(nskb)->frag_off = htons(IP_DF);
       ip_hdr(nskb)->id = 0;

       /* Adjust IP checksum */
       ip_hdr(nskb)->check = 0;
       ip_hdr(nskb)->check = ip_fast_csum((unsigned char *)ip_hdr(nskb), 
                                           ip_hdr(nskb)->ihl);
       /* Routing: if not headed for us, route won't like source */
/*
	if (ip_route_output(&rt, nskb->nh.iph->daddr,
                            0,
                            RT_TOS(nskb->nh.iph->tos) | RTO_CONN,
                            0) != 0)
*/
	memset(&flow_i, 0, sizeof(flow_i));
	memcpy(&(flow_i.nl_u.ip4_u.daddr), &(ip_hdr(nskb)->daddr), sizeof(flow_i.fl4_dst));
	tos_value = RT_TOS(ip_hdr(nskb)->tos) | RTO_CONN;
	memcpy(&(flow_i.nl_u.ip4_u.tos), &tos_value, sizeof(flow_i.fl4_tos));

	if(ip_route_output_key(&rt, &flow_i) !=0)
		goto free_nskb;
	
       dst_release(nskb->dst);
       nskb->dst = &rt->u.dst;
       /* "Never happens" */
/*
       if (nskb->len > nskb->dst->pmtu)
       	goto free_nskb;
*/
       ip_finish_output(nskb);

	   if(ct_info2 > 1){
			printk(IPFLOG_ALERT "[CSM]reset_connect : return ... done \n");
		}
       	return;

free_nskb:
		if(ct_info2 > 0){
			printk(IPFLOG_ALERT "[CSM]reset_connect : return ... kfree_skb \n");
		}
       kfree_skb(nskb);
}


static int 
webcategory_task(void *data)
{
	struct webcategory_kthread_data *webcategory_kthread_data = data;
	struct webcategory_queue *queue;
	char host[MAX_HOST_LENGTH];
	unsigned int category = 0;
	int ktest_res;

	while(!kthread_should_stop())
	{
		while ((queue = webcategory_dequeue(&global_webcategory_queue_head)) != NULL) {
			memset(host, 0, MAX_HOST_LENGTH);
			if (webcategory_host(queue->skb, host) == 0) {
				nf_reinject(queue->skb, queue->skb->nf_info, NF_ACCEPT);
			}
			else {
				if (host[0] == '\0') {
					nf_reinject(queue->skb, queue->skb->nf_info, NF_ACCEPT);
				} else {
					category = 0;
					if(ct_info2 > 1){
						printk(IPFLOG_ALERT "[CSM]Category : %d host : %s \n",category,host);
					}
					if(host_hash_get(&global_host_hash, host, &category) != 0) {
						if (webcategory_commtouch(webcategory_kthread_data, host, &category) != 0)
							host_hash_set(&global_host_hash, host, category);
					}
					if(category <= 0 || category >= WEBCATEGORY_MAX){
						printk(IPFLOG_ALERT "[CSM]Category : %d, URL : %s, out of range ... set it to Uncategorised_Sites \n",category,host);
						category = WEBCATEGORY_UNCATEGORISED_SITES;
					}
					if(ct_info2 > 1){
						printk(IPFLOG_ALERT "[CSM]Category : %d, host : %s, set MATCH DROP : %d \n",
							category,host,(queue->flag & IPSET_MATCH_DRP));
					}
					if ((ktest_res = ip_set_testip_kernel(queue->index, queue->skb, &category)) &&
					    ((queue->flag & IPSET_MATCH_DRP) != 0)) {
					    	//printk(KERN_DEBUG "WCF Drop URL: %s\n", host);
					    if(ct_info2 > 0){
							printk(IPFLOG_ALERT "[CSM]Category : %d, host : %s, ktest_res : %d Packet should be BLOCK ! \n",
								category,host,ktest_res);
						}
						reset_connect(queue->skb, host, "", category_name[category]);
						//kfree_skb(queue->skb);
						nf_reinject(queue->skb, queue->skb->nf_info, NF_DROP);
					} else {
						//printk(KERN_DEBUG "WCF Pass URL: %s\n", host);
						if(ct_info2 > 0){
							printk(IPFLOG_ALERT "[CSM]Category : %d, host : %s, ktest_res : %d Packet should be ACCEPT ! \n",
								category,host,ktest_res);
						}
						nf_reinject(queue->skb, queue->skb->nf_info, NF_ACCEPT);
					}
					if(ktest_res && ((queue->flag & IPSET_MATCH_LOG) != 0)) {
						printk(IPFLOG_ALERT "[CSM]WCF %s %u.%u.%u.%u -> %s that is categorized with [%s]\n",
						       queue->flag & IPSET_MATCH_DRP ? "Blocking" : "Pass",
						       NIPQUAD(ip_hdr(queue->skb)->saddr),
						       host,
						       category_name[category]);
					}		    
				}
			}
			kfree(queue);
		}
		interruptible_sleep_on_timeout(&webcategory_kthread_data->wait_queue, 1);
	}
	return 0;
}

static int
webcategory_utest(struct ip_set *set, const void *data, u_int32_t size)
{
	struct ip_set_webcategory *map = set->data;
	const struct ip_set_req_webcategory *req = data;
	ip_set_ip_t port = req->ip;

	if (port < map->first_ip || port > map->last_ip)
		return -ERANGE;
	
	DP("set: %s, port: %u", set->name, port);
	return !!test_bit(port - map->first_ip, map->members);	
}

static int
webcategory_ktest(struct ip_set *set, const struct sk_buff *skb, const u_int32_t *flags)
{
	const struct ip_set_webcategory *map = set->data;
	ip_set_ip_t port = *flags;

	if (port < map->first_ip || port > map->last_ip)
		return 0;
	
	return !!test_bit(port - map->first_ip, map->members);
}


static int
webcategory_uadd(struct ip_set *set, const void *data, u_int32_t size)
{
	struct ip_set_webcategory *map = set->data;
	const struct ip_set_req_webcategory *req = data;
	ip_set_ip_t port = req->ip;

	if (port < map->first_ip || port > map->last_ip)
		return -ERANGE;
	if (test_and_set_bit(port - map->first_ip, map->members))
		return -EEXIST;
	
	DP("set: %s, port %u", set->name, port);
	return 0;	
	
}

static int
webcategory_kadd(struct ip_set *set, const struct sk_buff *skb, const u_int32_t *flags)
{
	return 0;
}

static int
webcategory_udel(struct ip_set *set, const void *data, u_int32_t size)
{
	struct ip_set_webcategory *map = set->data;
	const struct ip_set_req_webcategory *req = data;
	ip_set_ip_t port = req->ip;

	if (port < map->first_ip || port > map->last_ip)
		return -ERANGE;
	if (!test_and_clear_bit(port - map->first_ip, map->members))
		return -EEXIST;
	
	DP("set: %s, port %u", set->name, port);
	return 0;	
	
}

static int
webcategory_kdel(struct ip_set *set, const struct sk_buff *skb, const u_int32_t *flags)
{
	return 0;
}


static inline int
__webcategory_create(const struct ip_set_req_webcategory_create *req,
		 struct ip_set_webcategory *map)
{
	if (req->to - req->from > MAX_RANGE) {
		ip_set_printk("range too big, %d elements (max %d)",
			      req->to - req->from + 1, MAX_RANGE+1);
		return -ENOEXEC;
	}
	return bitmap_bytes(req->from, req->to);
}

BITMAP_CREATE(webcategory)
BITMAP_DESTROY(webcategory)
BITMAP_FLUSH(webcategory)

static inline void
__webcategory_list_header(const struct ip_set_webcategory *map,
		      struct ip_set_req_webcategory_create *header)
{
}

BITMAP_LIST_HEADER(webcategory)
BITMAP_LIST_MEMBERS_SIZE(webcategory, ip_set_ip_t, (map->last_ip - map->first_ip + 1),
			 test_bit(i, map->members))

static void
webcategory_list_members(const struct ip_set *set, void *data, char dont_align)
{
	const struct ip_set_webcategory *map = set->data;
	uint32_t i, n = 0;
	ip_set_ip_t *d;
	
	if (dont_align) {
		memcpy(data, map->members, map->size);
		return;
	}
	
	for (i = 0; i < map->last_ip - map->first_ip + 1; i++)
		if (test_bit(i, map->members)) {
			d = data + n * IPSET_ALIGN(sizeof(ip_set_ip_t));
			*d = map->first_ip + i;
			n++;
		}
}

IP_SET_TYPE(webcategory, IPSET_DATA_SINGLE)

static struct nf_queue_handler nfqh = {
	.name  = "webcategory",
	.outfn = webcategory_nf_queue,
};
static int __init ip_set_webcategory_init(void)
{
	int i,j;
	int err;
	
	init_max_page_size();	
	err = ip_set_register_set_type(&ip_set_webcategory);
	if (err)
		goto err0;

	host_hash_init(&global_host_hash);

	tv.tv_sec = 1;
	tv.tv_usec = 0;
	memset(&ct_addr, 0, sizeof(struct sockaddr_in));
	memset(ct_sock_address, 0, 16);
	strcpy(ct_sock_address, ct_param_address);
	ct_addr.sin_family = AF_INET;
	ct_addr.sin_port = htons(5678);
	ct_addr.sin_addr.s_addr = inet_addr(ct_sock_address);
	
	webcategory_queue_head_init(&global_webcategory_queue_head);
	for(i = 0; i < MAX_WEBCATEGORY_THREAD; i++) {		
		global_webcategory_kthread_data[i].task = kthread_create(webcategory_task, 
			&global_webcategory_kthread_data[i], "webcategoryd-%d", i);
		err = IS_ERR(global_webcategory_kthread_data[i].task);
		if (err)
			goto err1;
		init_waitqueue_head(&global_webcategory_kthread_data[i].wait_queue);
		//global_webcategory_kthread_data[i].socket = ksocket(AF_INET, SOCK_DGRAM, 0);
		wake_up_process(global_webcategory_kthread_data[i].task);
	}
	err = nf_register_queue_handler(PF_INET, &nfqh);
	if (err) {
		goto err1;
	}
	return err;
err1:
	for(j = 0; j < i; j++) {
		kthread_stop(global_webcategory_kthread_data[i].task);
		kclose(global_webcategory_kthread_data[i].socket);
	}
	ip_set_unregister_set_type(&ip_set_webcategory);
err0:
	return err;
}

static void __exit ip_set_webcategory_fini(void)
{
	int i;
	for(i = 0; i < MAX_WEBCATEGORY_THREAD; i++) {
		kthread_stop(global_webcategory_kthread_data[i].task);
		//kclose(global_webcategory_kthread_data[i].socket);
	}
	ip_set_unregister_set_type(&ip_set_webcategory);
}
module_param(status, int, S_IRUGO|S_IWUSR);
module_param_string(ct_param_address, ct_param_address, 20, S_IRUGO|S_IWUSR);
module_param_string(ct_info, ct_info, 32, S_IRUGO|S_IWUSR);
module_param_string(ct_info1, ct_info1, 32, S_IRUGO|S_IWUSR);
module_param(ct_info2, int, S_IRUGO|S_IWUSR);
module_init(ip_set_webcategory_init);
module_exit(ip_set_webcategory_fini);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Eric Hsiao <erichs0608@gmail.com>");
MODULE_DESCRIPTION("webcategory type of IP sets");
