/*
 *		IP_MASQ_APP application masquerading module
 *
 *
 * 	$Id: ip_masq_app.c,v 1.2 2005/06/16 13:46:46 cgoh Exp $
 *
 * Author:	Juan Jose Ciarlante, <jjciarla@raiz.uncu.edu.ar>
 *
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License
 *	as published by the Free Software Foundation; either version
 *	2 of the License, or (at your option) any later version.
 *
 * Fixes:
 *	JJC			: Implemented also input pkt hook
 *	Miquel van Smoorenburg	: Copy more stuff when resizing skb
 *	Juan Jose Ciarlante	: reworked register() (new _init_xxxx()  functions for nicer interface)
 *	Juan Jose Ciarlante	: add INBOUND hooks (default to OUTBOUND)
 *
 * FIXME:
 *	- ip_masq_skb_replace(): use same skb if space available.
 *
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/skbuff.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/init.h>
#include <net/protocol.h>
#include <net/tcp.h>
#include <net/udp.h>
#include <asm/system.h>
#include <linux/stat.h>
#include <linux/proc_fs.h>
#include <net/ip_masq.h>

#define IP_MASQ_APP_TAB_SIZE  16 /* must be power of 2 */

/*
 * 	ip_masq_app->type holds all the values needed to hook
 * 	passing packet
 * 		flags: 	(uses only upper 8bits: f_bits)
 * 			INBOUND, OUTBOUND
 * 			FWMARK
 *
 * 	if (flags & IP_MASQ_APP_FWMARK)
 * 		[ f_bits][ fwmark (24 bits only)  ]
 * 	else
 * 		[ f_bits][ proto ][     port      ]
 * 	        
 * 	        <-----------  32 bits ------------>
 * 	
 */

#define IP_MASQ_APP_HASH(type) (((type>>16)^(type)) & (IP_MASQ_APP_TAB_SIZE-1))


EXPORT_SYMBOL(register_ip_masq_app);
EXPORT_SYMBOL(register_ip_masq_app_type);
EXPORT_SYMBOL(unregister_ip_masq_app);
EXPORT_SYMBOL(ip_masq_skb_replace);

/*
 * 	will hold masq app. hashed list heads
 */

struct ip_masq_app *ip_masq_app_base[IP_MASQ_APP_TAB_SIZE];

/*
 * 	"raw" ip_masq_app registration routine if you 
 * 	did ip_masq_app_init_xxxx() before to initialize (and check)
 * 	type value
 */

int register_ip_masq_app_type(struct ip_masq_app *mapp)  {
        unsigned long flags;
        unsigned hash;
#ifdef CONFIG_IP_MASQ_DEBUG
        if (!mapp) {
                IP_MASQ_ERR("register_ip_masq_app(): NULL arg\n");
                return -EINVAL;
        }
#endif
	if (!mapp->type) {
		IP_MASQ_DEBUG(1, "ip_masq_app_register_type(): ZERO type passed\n");
		return -EINVAL;
	}

        mapp->n_attach = 0;
        hash = IP_MASQ_APP_HASH(mapp->type);
	IP_MASQ_DEBUG(2, "ip_masq_app_register(): type=0x%x hash=0x%x\n", mapp->type, hash);

        save_flags(flags);
        cli();
        mapp->next = ip_masq_app_base[hash];
        ip_masq_app_base[hash] = mapp;
        restore_flags(flags);
	return 0;
}

/*
 * 	ip_masq_app registration routine
 *	port: host byte order.
 *	proto:  (flags<<8) | ipproto 
 */
int register_ip_masq_app(struct ip_masq_app *mapp, unsigned proto, __u16 port)
{
#ifdef CONFIG_IP_MASQ_DEBUG
        if (!mapp) {
                IP_MASQ_ERR("register_ip_masq_app(): NULL arg\n");
                return -EINVAL;
        }
#endif

	ip_masq_app_init_proto_port(mapp, IP_MASQ_APP_OUTBOUND, proto, port);
        return register_ip_masq_app_type(mapp);
}

/*
 * 	ip_masq_app unreg. routine.
 */

int unregister_ip_masq_app(struct ip_masq_app *mapp)
{
        struct ip_masq_app **mapp_p;
        unsigned hash;
        unsigned long flags;
        if (!mapp) {
                IP_MASQ_ERR("unregister_ip_masq_app(): NULL arg\n");
                return -EINVAL;
        }
        /*
         * only allow unregistration if it has no attachments
         */
        if (mapp->n_attach)  {
                IP_MASQ_ERR("unregister_ip_masq_app(): has %d attachments. failed\n",
                       mapp->n_attach);
                return -EINVAL;
        }
        hash = IP_MASQ_APP_HASH(mapp->type);

        save_flags(flags);
        cli();
        for (mapp_p = &ip_masq_app_base[hash]; *mapp_p ; mapp_p = &(*mapp_p)->next)
                if (mapp == (*mapp_p))  {
                        *mapp_p = mapp->next;
                        restore_flags(flags);
                        return 0;
                }

        restore_flags(flags);
        IP_MASQ_ERR("unregister_ip_masq_app(flags=0x%x, proto=%s,port=%u,fwmark=%d): not hashed!\n",
			
		IP_MASQ_APP_TYPE2FLAGS(mapp->type),
		masq_proto_name(IP_MASQ_APP_TYPE2PROTO(mapp->type)), 
		IP_MASQ_APP_TYPE2PORT(mapp->type),
		IP_MASQ_APP_TYPE2FWMARK(mapp->type));
        return -EINVAL;
}

/*
 *	get ip_masq_app object by its proto and port (host byte order).
 */

struct ip_masq_app * ip_masq_app_get_type(unsigned type)
{
        struct ip_masq_app *mapp;
        unsigned hash;

        hash = IP_MASQ_APP_HASH(type);
	IP_MASQ_DEBUG(2, "ip_masq_app_get(): type=0x%x hash=0x%x\n", type, hash);
        for(mapp = ip_masq_app_base[hash]; mapp ; mapp = mapp->next) {
                if (type == mapp->type) return mapp;
        }
        return NULL;
}

/*
 *	ip_masq_app object binding related funcs.
 */

/*
 * 	change ip_masq_app object's number of bindings
 */

static __inline__ int ip_masq_app_bind_chg(struct ip_masq_app *mapp, int delta)
{
        unsigned long flags;
        int n_at;
        if (!mapp) return -1;
        save_flags(flags);
        cli();
        n_at = mapp->n_attach + delta;
        if (n_at < 0) {
                restore_flags(flags);
                IP_MASQ_ERR("ip_masq_app: tried to set n_attach < 0 for (proto=%s,port==%d) ip_masq_app object.\n",
                       masq_proto_name(IP_MASQ_APP_TYPE2PROTO(mapp->type)),
                       IP_MASQ_APP_TYPE2PORT(mapp->type));
                return -1;
        }
        mapp->n_attach = n_at;
        restore_flags(flags);
        return 0;
}

/*
 *	Actually bind pointers and call contructor
 */

static struct ip_masq_app *do_bind_app(struct ip_masq *ms, struct ip_masq_app *mapp) 
{
	/*
	 *	don't allow binding if already bound
	 */
	if (ms->app != NULL) {
		IP_MASQ_ERR("ip_masq_bind_app() called for already bound object.\n");
		goto end;
	}
	IP_MASQ_DEBUG(1, "ip_masq_bind_app() : bound \"%s\" module\n", mapp->name);
	ms->app = mapp;
	if (mapp->masq_init_1) mapp->masq_init_1(mapp, ms);
	ip_masq_app_bind_chg(mapp, +1);
end:
        return ms->app;
}

/*
 *	Bind ip_masq to its ip_masq_app based on proto and dport ALREADY
 *	set in ip_masq struct. 
 *	Only called from ip_masq_new() when creating NEW masq tunnel,
 */

struct ip_masq_app * ip_masq_bind_app(struct ip_masq *ms)
{
        struct ip_masq_app * mapp;
	unsigned type;

	if (ms->protocol != IPPROTO_TCP && ms->protocol != IPPROTO_UDP)
		return NULL;

	IP_MASQ_DEBUG(1, "ip_masq_bind_app() : called for dst=%d.%d.%d.%d:%d src=%d.%d.%d.%d:%d masq=%d.%d.%d.%d:%d\n",
			NIPQUAD(ms->daddr), ntohs(ms->dport),
			NIPQUAD(ms->saddr), ntohs(ms->sport),
			NIPQUAD(ms->maddr), ntohs(ms->mport));

	/*
	 * 	Lookup "normal" case (client's active open inside)  	
	 */
	type = IP_MASQ_APP_TYPE_PP(IP_MASQ_APP_OUTBOUND, ms->protocol, ntohs(ms->dport));
        mapp = ip_masq_app_get_type(type);

	if (mapp==NULL) {
		/*	
		 *	lookup "reverse" case (server waiting inside,
		 *	for portfw and friends)
		 */
		type = IP_MASQ_APP_TYPE_PP(IP_MASQ_APP_INBOUND, ms->protocol, ntohs(ms->mport));
		mapp = ip_masq_app_get_type(type);
	}

        if (mapp != NULL) 
		mapp=do_bind_app(ms, mapp);
	return mapp;
}
/*
 *	Bind ip_masq to its ip_masq_app based on firewall mark
 */
struct ip_masq_app * ip_masq_bind_app_fwmark(struct ip_masq *ms, __u32 fwmark) {
	struct ip_masq_app *mapp;
	unsigned type;

	IP_MASQ_DEBUG(1, "ip_masq_bind_app_fwmark() : called for fwmark=0x%x, dst=%d.%d.%d.%d:%d src=%d.%d.%d.%d:%d masq=%d.%d.%d.%d:%d\n",
			fwmark,
			NIPQUAD(ms->daddr), ntohs(ms->dport),
			NIPQUAD(ms->saddr), ntohs(ms->sport),
			NIPQUAD(ms->maddr), ntohs(ms->mport));

	type = IP_MASQ_APP_TYPE_FWMARK(IP_MASQ_APP_OUTBOUND, fwmark);
        mapp = ip_masq_app_get_type(type);

	if (mapp==NULL) {
		/*	
		 *	lookup "reverse" case (server waiting inside,
		 *	for portfw and friends)
		 */
		type = IP_MASQ_APP_TYPE_FWMARK(IP_MASQ_APP_INBOUND, fwmark);
		mapp = ip_masq_app_get_type(type);
	}
        if (mapp != NULL) 
		mapp=do_bind_app(ms, mapp);
	return mapp;
}

/*
 * 	Unbind ms from type object and call ms destructor (does not kfree()).
 */

int ip_masq_unbind_app(struct ip_masq *ms)
{
        struct ip_masq_app * mapp;
        mapp = ms->app;

	if (ms->protocol != IPPROTO_TCP && ms->protocol != IPPROTO_UDP)
		return 0;

        if (mapp != NULL) {
                if (mapp->masq_done_1) mapp->masq_done_1(mapp, ms);
                ms->app = NULL;
                ip_masq_app_bind_chg(mapp, -1);
        }
        return (mapp != NULL);
}

/*
 *	Fixes th->seq based on ip_masq_seq info.
 */

static __inline__ void masq_fix_seq(const struct ip_masq_seq *ms_seq, struct tcphdr *th)
{
        __u32 seq;

        seq = ntohl(th->seq);

	/*
	 * 	Adjust seq with delta-offset for all packets after
         * 	the most recent resized pkt seq and with previous_delta offset
         *	for all packets	before most recent resized pkt seq.
	 */

	if (ms_seq->delta || ms_seq->previous_delta) {
		if(after(seq,ms_seq->init_seq) ) {
			th->seq = htonl(seq + ms_seq->delta);
			IP_MASQ_DEBUG(1, "masq_fix_seq() : added delta (%d) to seq\n",ms_seq->delta);
		} else {
			th->seq = htonl(seq + ms_seq->previous_delta);
			IP_MASQ_DEBUG(1, "masq_fix_seq() : added previous_delta (%d) to seq\n",ms_seq->previous_delta);
		}
	}


}

/*
 *	Fixes th->ack_seq based on ip_masq_seq info.
 */

static __inline__ void masq_fix_ack_seq(const struct ip_masq_seq *ms_seq, struct tcphdr *th)
{
        __u32 ack_seq;

        ack_seq=ntohl(th->ack_seq);

        /*
         * Adjust ack_seq with delta-offset for
         * the packets AFTER most recent resized pkt has caused a shift
         * for packets before most recent resized pkt, use previous_delta
	 *
	 * Compare acks against  (remote SEQs space is +delta)
	 * 	(ms_seq->init_seq + ms_seq->delta), not ms_seq->init_seq
	 * 		-- R.R. 27/08/00
         */

        if (ms_seq->delta || ms_seq->previous_delta) {
                if(after(ack_seq,ms_seq->init_seq + ms_seq->delta)) {
                        th->ack_seq = htonl(ack_seq-ms_seq->delta);
                        IP_MASQ_DEBUG(1, "masq_fix_ack_seq() : subtracted delta (%d) from ack_seq\n",ms_seq->delta);

                } else {
                        th->ack_seq = htonl(ack_seq-ms_seq->previous_delta);
                        IP_MASQ_DEBUG(1, "masq_fix_ack_seq() : subtracted previous_delta (%d) from ack_seq\n",ms_seq->previous_delta);
                }
        }

}

/*
 *	Updates ip_masq_seq if pkt has been resized
 *	Assumes already checked proto==IPPROTO_TCP and diff!=0.
 */

static __inline__ void masq_seq_update(struct ip_masq *ms, struct ip_masq_seq *ms_seq, unsigned mflag, __u32 seq, int diff)
{
        /* if (diff == 0) return; */

        if ( !(ms->flags & mflag) || after(seq, ms_seq->init_seq))
        {
                ms_seq->previous_delta=ms_seq->delta;
                ms_seq->delta+=diff;
                ms_seq->init_seq=seq;
                ms->flags |= mflag;
        }
}

/*
 *	Output pkt hook. Will call bound ip_masq_app specific function
 *	called by ip_fw_masquerade(), assumes previously checked ms!=NULL
 *	returns (new - old) skb->len diff.
 */

int ip_masq_app_pkt_out(struct ip_masq *ms, struct sk_buff **skb_p, __u32 maddr)
{
        struct ip_masq_app * mapp;
        struct iphdr *iph;
	struct tcphdr *th;
        int diff;
        __u32 seq;

        /*
         *	check if application masquerading is bound to
         *	this ip_masq.
         *	assumes that once an ip_masq is bound,
         *	it will not be unbound during its life.
         */

        if ( (mapp = ms->app) == NULL)
                return 0;

        iph = (*skb_p)->nh.iph;
        th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);

        /*
         *	Remember seq number in case this pkt gets resized
         */

        seq = ntohl(th->seq);

        /*
         *	Fix seq stuff if flagged as so.
         */

        if (ms->protocol == IPPROTO_TCP) {
                if (ms->flags & IP_MASQ_F_OUT_SEQ)
                        masq_fix_seq(&ms->out_seq, th);
                if (ms->flags & IP_MASQ_F_IN_SEQ)
                        masq_fix_ack_seq(&ms->in_seq, th);
        }

        /*
         *	Call private output hook function
         */

        if ( mapp->pkt_out == NULL )
                return 0;

        diff = mapp->pkt_out(mapp, ms, skb_p, maddr);

        /*
         *	Update ip_masq seq stuff if len has changed.
         */

        if (diff != 0 && ms->protocol == IPPROTO_TCP)
                masq_seq_update(ms, &ms->out_seq, IP_MASQ_F_OUT_SEQ, seq, diff);

        return diff;
}

/*
 *	Input pkt hook. Will call bound ip_masq_app specific function
 *	called by ip_fw_demasquerade(), assumes previously checked ms!=NULL.
 *	returns (new - old) skb->len diff.
 */

int ip_masq_app_pkt_in(struct ip_masq *ms, struct sk_buff **skb_p, __u32 maddr)
{
        struct ip_masq_app * mapp;
        struct iphdr *iph;
	struct tcphdr *th;
        int diff;
        __u32 seq;

        /*
         *	check if application masquerading is bound to
         *	this ip_masq.
         *	assumes that once an ip_masq is bound,
         *	it will not be unbound during its life.
         */

        if ( (mapp = ms->app) == NULL)
                return 0;

        iph = (*skb_p)->nh.iph;
        th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);

        /*
         *	Remember seq number in case this pkt gets resized
         */

        seq = ntohl(th->seq);

        /*
         *	Fix seq stuff if flagged as so.
         */

        if (ms->protocol == IPPROTO_TCP) {
                if (ms->flags & IP_MASQ_F_IN_SEQ)
                        masq_fix_seq(&ms->in_seq, th);
                if (ms->flags & IP_MASQ_F_OUT_SEQ)
                        masq_fix_ack_seq(&ms->out_seq, th);
        }

        /*
         *	Call private input hook function
         */

        if ( mapp->pkt_in == NULL )
                return 0;

        diff = mapp->pkt_in(mapp, ms, skb_p, maddr);

        /*
         *	Update ip_masq seq stuff if len has changed.
         */

        if (diff != 0 && ms->protocol == IPPROTO_TCP)
                masq_seq_update(ms, &ms->in_seq, IP_MASQ_F_IN_SEQ, seq, diff);

        return diff;
}

/*
 *	/proc/ip_masq_app entry function
 */

int ip_masq_app_getinfo(char *buffer, char **start, off_t offset, int length, int dummy)
{
        off_t pos=0, begin=0;
        int len=0;
        struct ip_masq_app * mapp;
        unsigned idx;

	if (offset < 40)
		len=sprintf(buffer,"%-39s\n", "prot port    n_attach name");
	pos = 40;

        for (idx=0 ; idx < IP_MASQ_APP_TAB_SIZE; idx++)
                for (mapp = ip_masq_app_base[idx]; mapp ; mapp = mapp->next) {
			/*
			 * If you change the length of this sprintf, then all
			 * the length calculations need fixing too!
			 * Line length = 40 (3 + 2 + 7 + 1 + 7 + 1 + 2 + 17)
			 */
			pos += 40;
			if (pos < offset)
				continue;

                        len += sprintf(buffer+len, "%-3s  %-7u %-7d  %-17s\n",
                                       masq_proto_name(IP_MASQ_APP_TYPE2PROTO(mapp->type)),
                                       IP_MASQ_APP_TYPE2PORT(mapp->type), mapp->n_attach,
				       mapp->name);

                        if(len >= length)
                                goto done;
                }
done:
	begin = len - (pos - offset);
        *start = buffer + begin;
        len -= begin;
        if (len > length)
                len = length;
        return len;
}


#ifdef CONFIG_PROC_FS
static struct proc_dir_entry proc_net_ip_masq_app = {
	PROC_NET_IP_MASQ_APP, 3, "app",
	S_IFREG | S_IRUGO, 1, 0, 0,
	0, &proc_net_inode_operations,
	ip_masq_app_getinfo
};
#endif

/*
 *	Initialization routine
 */

__initfunc(int ip_masq_app_init(void))
{
#ifdef CONFIG_PROC_FS
	ip_masq_proc_register(&proc_net_ip_masq_app);
#endif
        return 0;
}

/*
 *	Replace a segment (of skb->data) with a new one.
 *	FIXME: Should re-use same skb if space available, this could
 *	       be done if n_len < o_len, unless some extra space
 *	       were already allocated at link level :P .
 */

static struct sk_buff * skb_replace(struct sk_buff *skb, int pri, char *o_buf, int o_len, char *n_buf, int n_len)
{
        int maxsize, diff, o_offset;
        struct sk_buff *n_skb;
	int offset;

	maxsize = skb->truesize;

        diff = n_len - o_len;
        o_offset = o_buf - (char*) skb->data;

	if (maxsize <= n_len) {
	    if (diff != 0) {
		memcpy(skb->data + o_offset + n_len,o_buf + o_len,
		       skb->len - (o_offset + o_len));
	    }

	    memcpy(skb->data + o_offset, n_buf, n_len);

	    n_skb    = skb;
	    skb->len = n_len;
	    skb->end = skb->head+n_len;
	} else {
                /*
                 * 	Sizes differ, make a copy.
                 *
                 *	FIXME: move this to core/sbuff.c:skb_grow()
                 */

                n_skb = alloc_skb(MAX_HEADER + skb->len + diff, pri);
                if (n_skb == NULL) {
                        IP_MASQ_ERR("skb_replace(): no room left (from %p)\n",
                               __builtin_return_address(0));
                        return skb;

                }
                skb_reserve(n_skb, MAX_HEADER);
                skb_put(n_skb, skb->len + diff);

                /*
                 *	Copy as much data from the old skb as possible. Even
                 *	though we're only forwarding packets, we need stuff
                 *	like skb->protocol (PPP driver wants it).
                 */
                offset = n_skb->data - skb->data;
                n_skb->nh.raw = skb->nh.raw + offset;
                n_skb->h.raw = skb->h.raw + offset;
                n_skb->dev = skb->dev;
                n_skb->mac.raw = skb->mac.raw + offset;
                n_skb->pkt_type = skb->pkt_type;
                n_skb->protocol = skb->protocol;
                n_skb->ip_summed = skb->ip_summed;
		n_skb->dst = dst_clone(skb->dst);

                /*
                 * Copy pkt in new buffer
                 */

                memcpy(n_skb->data, skb->data, o_offset);
                memcpy(n_skb->data + o_offset, n_buf, n_len);
                memcpy(n_skb->data + o_offset + n_len, o_buf + o_len,
                       skb->len - (o_offset + o_len) );

                /*
                 * Problem, how to replace the new skb with old one,
                 * preferably inplace
                 */

                kfree_skb(skb);
        }
        return n_skb;
}

/*
 *	calls skb_replace() and update ip header if new skb was allocated
 */

struct sk_buff * ip_masq_skb_replace(struct sk_buff *skb, int pri, char *o_buf, int o_len, char *n_buf, int n_len)
{
        int diff;
        struct sk_buff *n_skb;
        unsigned skb_len;

        diff = n_len - o_len;
        n_skb = skb_replace(skb, pri, o_buf, o_len, n_buf, n_len);
        skb_len = skb->len;

        if (diff)
        {
                struct iphdr *iph;
                IP_MASQ_DEBUG(1, "masq_skb_replace(): pkt resized for %d bytes (len=%d)\n", diff, skb->len);
                /*
                 * 	update ip header
                 */
                iph = n_skb->nh.iph;
                iph->check = 0;
                iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
                iph->tot_len = htons(skb_len + diff);
        }
        return n_skb;
}
